Heroku and multer - node.js

I made a webapp that lets you upload a file to the server using multer. It works like it should when the server is run locally, but when I deployed it on Heroku, it seems I ran into a 500 internal server error.
Has anyone dealt with this before?
What are the options?
Webapp is here: https://dupefinder.herokuapp.com/
Github repo is here: https://github.com/ExtDASH/herkodeploy
2018-09-19T19:38:48.310177+00:00 app[web.1]: POST /uploads 500 148 - 181.170 ms
2018-09-19T19:38:48.310830+00:00 app[web.1]: Error: ENOENT: no such file or directory, open 'uploads/csv1537385928295.csv'
2018-09-19T19:38:48.311255+00:00 heroku[router]: at=info method=POST path="/uploads" host=dupefinder.herokuapp.com request_id=ff1aaa34-f36c-49cf-bd4e-4a936fb48a2c fwd="24.52.32.175" dyno=web.1 connect=1ms service=188ms status=500 bytes=404 protocol=https
and here is the console error in the browser:
main.js:146 POST https://dupefinder.herokuapp.com/uploads 500 (Internal Server Error)
reader.onload # main.js:146
load (async)
readFile # main.js:131
invoker # vue.js:2029
Vue.$emit # vue.js:2538
click # VBtn.ts:108
invoker # vue.js:2029
fn._withTask.fn._withTask # vue.js:1828
I'm using an XMLHttpRequest POST request:
readFile: function(){
const input = document.querySelector('#myFile')
const reader = new FileReader()
reader.onload = function() {
let csvfile = new Blob([reader.result], { type: 'text/csv' })
app.uploadingFile = true
const form = new FormData()
let sendName = input.files[0].name.split(/\W+/g)
form.append('Ncsv', csvfile, `${sendName[0]}.csv`)
const xhr = new XMLHttpRequest()
xhr.open('POST', '/uploads', true)
xhr.onreadystatechange = function() {
if(this.readyState == XMLHttpRequest.DONE && this.status == 200) {
form.delete('Ncsv')
}
}
xhr.send(form)
}
reader.readAsText(input.files[0])
which goes to an app.post route in my server.js file:
const express = require('express')
const connect = require('connect')
const morgan = require('morgan')
const bodyParser = require('body-parser')
const mongoose = require('mongoose')
const fs = require('fs-extra')
const multer = require('multer')
const getRouter = require('./routes/ourNums')
const nFs = require('./fileSchema.js')
const namesRouter = require('./routes/namesRouter.js')
const computeRouter = require('./routes/computeRouter.js')
// const uploadRouter = require('./routes/uploadRouter') unused for now
const filesRouter = require('./routes/filesRouter')
const path = require('path')
const app = express()
var storage = multer.diskStorage({
destination: function (req, file, cb) {
fs.ensureFile(file)
.then(() => {
console.log('done')
}
cb(null, __dirname+'/uploads')
},
filename: function (req, file, cb) {
var newName = file.originalname.split(/\W+/g)
var fullName = `${newName[0]}${Date.now()}.csv`
cb(null, fullName)
},
})
var upload = multer({ storage: storage })
app.use(express.json({limit: '50mb'}))
app.use(bodyParser.urlencoded({extended: false}))
app.use(bodyParser.json())
app.use(bodyParser.json({ limit: '50mb' }))
app.use(morgan('tiny')) //watching for changes
// app.use(express.static(`${__dirname}/client/index.html`))
app.post('/uploads', upload.single('Ncsv'), function (req, res, next) {
var fileName = req.file.filename
nFs.create({
name: fileName
})
.then(data => res.status(200).send())
.catch(e => {
req.error = e
console.log(e)
next()
})
})
before, I didn't use fs for anything (and as you can see here, fs.ensureFile isn't doing anything to fix the 500 error), I just included it to begin with so I could play around with it. Running the server locally, this works. I click my upload button on my client and it sends whatever file i selected as a blob, runs it through multer, and creates the file in a /server/uploads/ directory
Edit: I just tried using multer.memoryStorage() and got the same 500 internal server error.

I'm not sure why your uploads aren't being saved; you should be able to save them temporarily.
But this won't work long-term. Heroku's filesystem is ephemeral: any changes you make will be lost the next time your dyno restarts, which happens frequently (at least once per day).
Heroku recommends storing uploads on something like Amazon S3. Here's a guide for doing it specifically with Node.js.
Once you've stored your files on S3 you should be able to retrieve them using an appropriate library or possibly over HTTP, depending on how you've configured your bucket.
If you still wish to use multer, check out multer-s3.

Related

Cannot post request to nodejs route on ECS but can locally (404 error)

I’ve been having an issue with deploying my nodejs App on AWS ECS Fargate. Running the app locally on my device with nodemon or building the app and running the build file is successful and I can ping my routes using postman. The issue happens when I deploy this same exact code on AWS; using postman, to do a POST request, I get a 404 error. Please note, I'm running a Node:14 container.
For reference, my nodejs code is structured in a way where there’s a main route.js file containing all routes, then there are specific route files, for example listingRoute.js, contains all the sub-routes then there are controllers (.js files) containing all the logic where I export the function and tie it with the route in the listingRoute.js example.
Here's what my main Route.js file looks like:
const express = require('express');
const error = require('../Middleware/error');
const listingRoute = require('../Routes/listingRoute');
module.exports = function (app) {
//Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: false , limit : '20mb' }));
app.use('/listing', listingRoute);
//The final middleware to be called in case of an unhandled error.
app.use(error);
process.on('uncaughtException', function(err) {
// Handle the error safely
console.log(err)
})
};
My listingRoute file
const express = require("express");
const route = express.Router();
const listingController = require("../Controllers/listingController");
require('dotenv').config();
route.post("/create", listingController.createListing)
route.post("/update", listingController.updateListing)
route.post("/read", listingController.getListing)
route.post("/delete", listingController.deleteListing)
...
...
...
...
...
route.post("/getMostPopular" , listingController.getMostPopular)
route.post("/getByCategory" , listingController.getByCategory)
route.post("/getAllTOS" , TOSController.getTOSByListing)
route.post("/getTOS" , TOSController.getTOSByID)
route.post("/updateTOS" , TOSController.updateTOS)
route.post("/deleteTOS" , TOSController.deleteTOS)
route.post("/createTOS" , TOSController.createTOS)
route.post("/getListingsByIDs" , listingController.getListingsByIDs)
route.post("/cacheImagesNewCDN" , listingController.cacheImagesNewCDN)
module.exports = route;
My listingController file
const listingModel = require('../Models/listingModel');
const moment = require('moment')
const axios = require('axios');
var fs = require('fs');
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
var fs = require('fs');
//tested
const createListing =async (req, res) => {
try {
//some logic here
}
catch (err) {
console.log(err)
return res.status(500).json({ error: err.message });
}
}
const updateListing = async (req, res) => {
try {
//some logic here
}
catch (err) {
return res.status(500).json({ error: err.message });
}
}
module.exports = {
getListing,
updateListing,
deleteListing,
createListing,
listingwithViews,
advertisedListings,
filterListings,
pressedOnBookNow,
cacheImages,
recommendListings,
getCacheMetaData,
addIndoorAmenity,
missingFromFilter,
adjustCreativeStudios,
listingsToCSV,
getAllListing,
getDiscountedListings,
addRevenueToListings,
getMostPopular,
getByCategory,
getListingsByIDs,
cacheImagesNewCDN,
getOwnersPhones
}
All the routes starting from getMostPopular till the end of the list give an error 404 not found although I have done the same procedure to all of them. Any ideas why this is happening? If you feel this isn't enough information to help diagnose, let me know and i'd be happy to provide more details. You're help would be beyond appreciated, thanks!

NodeJS: Files only update after restarting the express server

I am using nodejs with express for a small backend application which just returns a json file from another directory.
Example scenario:
My json files are in the directory "/var/data", so e.g. "/var/data/hello.json". If I start the nodejs backend with "node index.js" everything works as expected.
But if I change the contents of a json file, I still get the old version from the backend.
How can I set this up, so that my backend nodejs server detects these file changes in another directory without restarting it?
index.js:
const express = require('express');
const fs = require('fs');
const app = express();
app.use(express.json());
const myDataPath = "/var/data/";
app.get("/:id", (request, response) => {
let id = request.params.id;
let path = myDataPath + id + ".json";
if (fs.existsSync(path)) {
response.json(require(path));
} else {
response.sendStatus(404);
}
});
Issue is likely with using "require", this is a guess, but maybe require doesn't run twice for optimization reasons.
const express = require('express');
const fs = require('fs');
const app = express();
app.use(express.json());
const myDataPath = './var/data/';
app.get('/:id', (request, response) => {
let id = request.params.id;
let path = myDataPath + id + '.json';
console.log(path);
if (fs.existsSync(path)) {
response.json(JSON.parse(fs.readFileSync(path)));
} else {
response.sendStatus(404);
}
});
app.listen(1025);
The above code snippet worked on my testing example, I used readFileSync to retrieve the data, uncached, and the response changes when the file is modified, without needing to restart the app.

Image upload on cloudinary not working in heroku

I am using multer and cloudinary for image upload in node.js app. When I am in development mode, it works perfectly fine but after deploying to heroku, I am getting error in browser console as
the server responded with a status of 503 (Service Unavailable). When I check heroku logs it showed as
at=error code=H12 desc="Request timeout" method=POST path="/api/users/upload/avatar"
I have already added my environment variables in heroku. What could be a problem?
My config file
const {config,uploader } = require('cloudinary').v2
const cloudinaryConfig = (req,res,next) => {
config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.API_KEY,
api_secret: process.env.API_SECRET,
});
next()
}
module.exports ={cloudinaryConfig,uploader}
Multer file
const DatauriParser = require('datauri/parser');
const parser = new DatauriParser();
const storage = multer.memoryStorage();
const multerUploads = multer({ storage,fileFilter:(req,file,cb)=>{
let ext = path.extname(file.originalname);
if (ext !== ".jpg" && ext !== ".jpeg" && ext !== ".png") {
cb(new Error("File type is not supported"), false);
return;
} cb(null,true)
} }).single('image');
const dataUri = req => parser.format(path.extname(req.file.originalname).toString(),req.file.buffer)
module.exports = {multerUploads,dataUri}
My upload controller
const uploadImage = async(req,res)=>{
const folder = req.path.split('/',3)[2]
if(!req.file){
throw new Error('Choose a picture to upload')
}
if(req.file){
const file = dataUri(req).content
const result = await uploader.upload(file,{
folder:`MobiHub/${folder}`,
width: 300,
height:300,
crop:'fill',
gravity: "faces"
})
const image = result.secure_url
res.json({image})
}
}
Setting the Cloudinary environment variables (e.g., process.env.CLOUD_NAME) on the Heroku config vars may help.
Or simply try to add the CLOUDINARY_URL environment variable (can be found on the Cloudinary dashboard), then you won't need to explicitly declare them on cloudinary.config().

Unable to access images from upload folder in React-Node

In my MERN app, I am trying to access the images on the client-side that has been saved on the local database using multer.
The structure of my backend folder goes like this:
--- api
--- controllers
--- model
--- routes
--- config
--- db.js
--- appConfig.js
--- utils
--- uploads
--- multerMiddleware.js
--- app.js
The image uploading and storing to local DB works completely fine. When a new data is created, the data received by the client in API response contains the URL of the image uploaded so that it can be accessed again (like for displaying image thumbnail).
My code goes like:
App.js
const express = require("express");
const path = require('path');
const app = express();
const directory = path.join(__dirname, '/uploads');
app.use('/uploads', express.static(directory));
require("./config/db/db")();
require("./config/appRoutes/appRoutes")(app);
module.exports = app;
multerFile.js
const multer = require('multer');
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, __dirname + '/uploads')
},
filename: function (req, file, cb) {
const fileName = file.originalname.toLowerCase().split(' ').join('-');
cb(null, fileName);
}
});
const upload = multer({
storage
});
module.exports = upload;
controller
exports.createService = async (req, res) => {
const service_name = req.body.main_name;
const url = req.protocol + '://' + req.get('host');
let service_pic;
if (req.file) {
service_pic = url + '/utils/uploads/' + req.file.filename;
}
try {
const service = new Services({
_id: new mongoose.Types.ObjectId(),
service_name,
service_pic
});
const new_service = await service.save();
res.status(201).json({ message: "New data created", result: new_service });
} catch (error) {
console.log(error);
res.status(500).json({ message: "Internal server error", error });
}
}
With the route, http://localhost:5000/services/all, I get the JSON data as:
{
createdAt: "2020-09-07T08:25:11.581Z"
service_name: "TEST"
service_pic: "http://localhost:5000/utils/uploads/testio-logo-rgb1.png"
updatedAt: "2020-09-07T08:25:11.581Z"
}
When I try to access http://localhost:5000/utils/uploads/testio-logo-rgb1.png, it always returns an error: "error":{"message":"Route Not found"}}. The images are gettings stored properly into the /uploads folder, but still not accessible on the client.
I am not sure what thing is going wrong. Any help to resolve this is appreciated.
Change '/uploads' to 'utils/uploads'
// app.js
const directory = path.join(__dirname, 'utils/uploads');
app.use('/uploads', express.static(directory));
This block of code means: you've set up a static-assets serving endpoint at /uploads. Everytime a request hits this endpoint, your server will look up to the folder /utils/uploads.
An example request would be: http://localhost:5000/uploads/testio-logo-rgb1.png
I believe your URL is incorrect,
http://localhost:5000/utils/uploads/testio-logo-rgb1.png
should be
http://localhost:5000/uploads/testio-logo-rgb1.png

res.download(NodeJS) not triggering a download on the browser

I've been struggling with this for a while and can't seem to find an answer, I'm developing a website with a budgeting option, I'm sending an object from the client to the server, and that server is using PDFKit to create a PDF version of the budget, once it's created I want to actually send back that PDF to the client and trigger a download, this is what I've done
Client-side code:
let data = {
nombre: this.state.name,
email: this.state.email,
telefono: this.state.phone,
carrito: this.props.budget.cart,
subTotal: this.props.budget.subTotal,
IVA: this.props.budget.tax,
total: this.props.budget.subTotal + this.props.budget.tax
}
axios({
method: 'post',
url: 'http://localhost:1337/api/budget',
data: data
})
.then((response) => {
console.log('This is the response', response);
window.open('/download')
})
.catch((error) => {
alert(error);
})
So that data goes to my server-side code perfectly and it looks like this
const pdf = require('pdfkit');
const fs = require('fs');
const path = require('path');
exports.makePDFBudget = (req, res) => {
let myDoc = new pdf;
myDoc.pipe(fs.createWriteStream(`PDFkit/budget.pdf`));
myDoc.font('Times-Roman')
.fontSize(12)
.text(`${req.body.name} ${req.body.phone} ${req.body.email} ${req.body.cart} ${req.body.subTotal} ${req.body.total} ${req.body.tax}`);
myDoc.end()
}
That's creating my PDF, what I want now is that once it's created and the response is sent back to the client, the client opens a new window with the URL "/download" which is set to download that PDF, but that's not happening for some reason, it opens up the new window but the download never starts and it throws absolutely no error I'm my Node console or browser console
this is how I send my file to the client
const fs = require('fs');
const path = require('path');
exports.downloadPDFBudget = (req, res) => {
res.download(__dirname + 'budget.pdf', 'budget.pdf');
}
And this is how my server index looks like
const bodyParser = require('body-parser');
const express = require('express');
const app = express();
const api = express.Router();
const { makePDFBudget } = require('./PDFkit/makePDFBudget.js');
const { downloadPDFBudget } = require('./PDFkit/downloadPDFBudget.js')
app.use(express.static(__dirname + '/../public'));
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json({extended: true}));
api.route('/budget')
.post(makePDFBudget)
api.route('/download')
.get(downloadPDFBudget)
app.use('/api', api);
const port = 1337;
app.listen(port);
console.log('Listening on port ', port);
module.exports = app;
I just solved it, the port in which I was running my client obviously was different from the one I was running my server, so I had to open a window to my server's port to trigger the download, I realized this because I threw a console log on the function that was supposed to do the res.download it wasn't showing up. Thanks!
I guess the main problem here:
res.download(__dirname + 'budget.jpg', 'budget.pdf');
Make a correct file name. Your file is pdf, not jpg.
At this code res.end(Buffer.from('budget.pdf')) you sending string, not file content. But headers like you want to send a file.
The last. Your application designed like you will have only one user. Could you add userId to file names? Or use DB for storing data and generate pdf on request without storing a file to the file system.

Resources