Node.js - Serving file for download using express - javascript

I am creating an endpoint that serves a file generated dynamically. I have written a controller which generates the file after certain operation based on request param fileId.
I am throwing some errors if anything goes wrong while file generation or it is an invalid request. I have used Promise.reject() for throwing error and on successful file generation returning the response {fileName, filePath} as Promise from the controller.
import express from 'express'
import downloadFile from '../controller/file.controller'
const router = express.Router()
router.post('/file/download/:fileId', (req, res) => {
downloadFile(req.fileId).then((fileResp) => {
res.download(fileResp.filePath, fileResp.fileName, function (error) {
if (error) {
console.log('Downloading error')
} else {
console.log('Downloading success')
}
})
}).catch((error) => {
res.status(error.status).json({message: error.message})
})
})
I have observed that file is being served on requesting endpoint but it will be empty of size zero bytes.
I have tried the same thing without Promise which works well. In this approach, I have changed my errors from Promise.reject() to throw error and response from Promise to an object
import express from 'express'
import downloadFile from '../controller/file.controller'
const router = express.Router()
router.post('/file/download/:fileId', (req, res) => {
const fileResp = downloadFile(req.fileId)
res.download(fileResp.filePath, fileResp.fileName, function (error) {
if (error) {
console.log('Downloading error')
} else {
console.log('Downloading success')
}
})
})
I am unable to find the issue in the 1st approach. Is it Promise which is causing the issue or I am doing something wrong?

Related

ENOENT error on xml2js but file did exists

const xml2js = require('xml2js');
const fs = require('fs');
fs.readFile('https://www.tcmb.gov.tr/kurlar/today.xml', (err, data) => {
if(err) console.log(err);
var data = data.toString().replace("\ufeff", "");
xml2js.parseStringPromise(data, (err, res) => {
if(err){
console.log(err);
} else {
console.log(res);
}
});
});
This is my code in nodejs I try to get data on a https link with xml2js first by using the way it says in the npm page pf xml2js it gives some error and when I chechk on web I find solution of using with fs but still geting this error
I know the directory exists because if you go to link used in code it shows something but in code just gives error if someone can help I will be very happy
fs can only access file in your system, you should request the URL using http/https or even better try axios
const axios = require('axios')
axios.get('https://www.tcmb.gov.tr/kurlar/today.xml').then((response) => {
const data = response.data
xml2js.parseString(data, (err, res) => {
if(err){
console.log(err);
} else {
console.log(res);
}
})
})

Express Error Handling: Sending a Message to Frontend

I'm having some trouble error handling my authentication API calls. When I send the 500 status from Express, my frontend (Vue in this case) only picks up the message Request failed with status code 500 rather than something more helpful for triage like this is the worst error ever (in the example below).
In the below example, when I call '/post' from the API, I throw an error which is handled by my custom middleware. The middleware successfully handles the error and sends the appropriate status to my frontend, but I can't figure out how to send useful messages (e.g. 'this is the worst error ever') / access them in the front end.
Is this a common use case? Am I doing anything obviously wrong? Does the message I send come up in the (err) parameter, or do I need to add a resp parameter?
Express Route for '/login'
router.post('/login', (req, res, next) => {
throw Error('this is the worst error ever')
})
Custom express error handler
app.use(function(err, req, res, next) {
res.status(err.status || 500).send({
error: {
status: err.status || 500,
message: err.message || 'Internal Server Error',
},
});
});
Handle the API Response in Vue
login (e) {
e.preventDefault();
UserService.login(this.username, this.password) //this is simple axios post call
.catch((err) => {
this.loginStatus = err.message
return
})
}
Found the answer to this for those that find this helpful. The err that is caught has a response variable. This is where the data is sent via the express send command. See corrected code below for the frontend:
login (e) {
e.preventDefault();
UserService.login(this.username, this.password) //this is simple axios post call
.catch((err) => {
this.loginStatus = err.response.data.message
return
})
}
I think you need to add:
Throw new Error()
instead of
Throw Error
If you are making an asynchronous calling you can do this
app.get('/', function (req, res, next) {
fs.readFile('/file-does-not-exist', function (err, data) {
if (err) {
next(err) // Pass errors to Express.
} else {
res.send(data)
}
})
})
Code should look like this:
router.post('/login', (req, res, next) => {
throw new Error('this is the worst error ever')
})
Check this in express documentation

Can not make mongoose find().then() to work

Faced an issue with Mongoose.
import express from 'express';
import Countries from '../models/countries.mjs';
const router = new express.Router();
router.get('/countries-data', async (req, res) => {
try {
let countries =
await Countries.find({})
.select(
'-_id specimenDate dailyLabConfirmedCases changeInDailyCases')
.sort('specimenDate');
if (!countries) return res.status(500).send();
res.json(countries);
} catch (err) {
res.status(500).send();
}
});
This code works as expected but I decided to remove the async/await and use find().then() instead:
import express from 'express';
import Countries from '../models/countries.mjs';
const router = new express.Router();
router.get('/countries-data', (_, res) => {
Countries.find({})
.select('-_id specimenDate dailyLabConfirmedCases changeInDailyCases')
.sort('specimenDate')
.then((countries) => {
if (!countries) throw Error('no data');
res.json(countries);
})
.catch(res.status(500).send());
});
This one rise an exception while trying to send the json data:
UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I really don't know what I'm doing wrong. And why the catch isn't getting the promise exception? Any suggestion?
I think the problem arises from the response you sent in the catch block since the catch takes in a callback function which returns an error if there is any.
Try this:
import express from 'express';
import Countries from '../models/countries.mjs';
const router = new express.Router();
router.get('/countries-data', (_, res) => {
Countries.find({})
.select('-_id specimenDate dailyLabConfirmedCases changeInDailyCases')
.sort('specimenDate')
.then((countries) => {
if (!countries) throw Error('no data');
res.json(countries);
})
// refactor your code like this
.catch((err)=>{
res.status(500).send(err)
});
});

Return JSON from Express error handling middleware instead of HTML

I am having a bit of an issue returning a JSON response from my Express handling middleware. Currently, I am getting an HTML error page in Postman. On my actual client, I only return a 500 error from the fetch request in the console. The JSON data that should be the error message does not come through as anticipated.
Here is my error handling function. It simply passes the error as a JSON response back to the client. Anytime next(some_error) is called in my controller routes, Express pipes them through this error handling function:
const express = require('express');
const router = express.Router();
exports.errorHandler = (err, req, res, next) => {
res.setHeader('Content-type', 'application/json');
res.status(500).json({ err });
};
module.exports = router;
Here is a portion of the controller route that I am throwing an intentional error in to test the error handling middleware:
if (isMatch) {
const payload = { id: user._id };
jwt.sign(
payload,
JWT_SECRET_KEY,
{ expiresIn: 900000 },
(err, token) => {
if (err) {
const error = new Error(JWT_FAILED);
error.httpStatusCode = 400;
return next(error);
}
payload.token = `Bearer ${token}`;
return res.status(200).json({
success: true,
accountant: payload
});
}
);
} else {
const error = new Error(PASSWORD_INCORRECT);
error.genericError =
'The provided password did not match the database.';
error.httpStatusCode = 400;
return next(error);
}
This is the page I am getting in response for reference:
I am not sure what I am doing wrong, I usually don't have an issue sending a JSON response back from Express. I have a hunch the errors handled by Express require an extra step somewhere to not default to returning as HTML and not JSON.
This fixed my issue. I removed router and added module.exports = errorHandler and this resolved the issue. Express was not calling my errorHandler middleware function. It was just seeing a next(some_error) in my controller routes and then returning the error it's default way. I assumed my errorHandler function was returning this when in fact, my function was never even called.
This is the updated error handling middleware:
const errorHandler = (err, req, res, next) => {
res.json({ err: 'and error' });
};
module.exports = errorHandler;
This now sends back JSON. Phewwww.

Database is updated sucessfully, but API returns 500 error

I am currently working on a project that is using Javascript with Node.js, Express, SuperAgent and KnexJS (database framework for Sqlite3). My problem is this:
When I submit data for updates via my API route using the PUT method, my database is updating successfully, but my console returns this error:
PUT http://localhost:3000/user/contracts/ 500 (unknown)
Error: unknown
at Request.<anonymous> (client.js:423)
at Request.Emitter.emit (index.js:133)
at XMLHttpRequest.xhr.onreadystatechange (client.js:735)
Here is some snippets of my API, Routes, and DB code.
api.js
const request = require('superagent')
const updateUserContract = (callback, id, contractData) => {
request
.put('http://localhost:3000/user/contracts/' + id)
.set('Content-Type', 'application/json')
.send(contractData)
.end(function (err, res) {
if (err) {
callback(err)
} else {
callback(null, "Status: 200")
}
})
}
module.exports = { updateUserContract }
routes.js
router.put('/contracts/:id', function (req, res) {
var id = req.params.id
var signatureUrl = req.body.signature_url
db.signContract(id, signatureUrl).then((result) => {
res.sendStatus(result)
})
.catch((err) => {
res.status(500).send(err)
})
})
db.js
function signContract (id, signatureUrl) {
return knex('contracts').where('id', id)
.update({ signature_url: signatureUrl }).into('contracts')
}
Was answered by #Sombriks. "about your error, you're sending sql status as if it where an http status. my best guess is, it's being "promoted" to an error 500. try simply req.send("OK"), it will deliver status 200 as default".

Categories

Resources