I have a basic express setup that includes the following:
app.all('/api/:controller/:id?', [ api, response.success, response.error ])
app.get('*', (req, res) => {
console.log('This should only fire when no route matched')
res.sendFile(path.resolve(pubRoot, 'index.html'))
})
In the code for the middleware on the first route the api module simply resolves a promise at this point (or rejects on 404) and then calls next, this makes it into the response middleware which looks like this:
const response = {
success: (req, res, next) => {
res.status(200).send(res.data)
},
error: (error, req, res, next) => {
const statusCode = error.statusCode || 500
res.status(statusCode).send(error.message || 'Internal Server Error')
}
}
This works fine on a successful request and I get the data (and status code 200) back. It also works on error (returning 404 and Not Found as expected), however, it continues on to the wildcard route. I get the console.log along with Error: Can't set headers after they are sent.
UPDATE: Below is the code for the api module:
const api = (req, res, next) => {
const controller = req.params.controller
const event = { body: req.body, id: req.params.id, query: req.query }
// Handle non-existent controller
if (!controllers[controller]) {
next(new HTTPError(404))
return
}
// Controller found, process
controllers[controller][req.method.toLowerCase()](event)
.then((result) => {
res.data = result
next()
})
.catch((err) => {
next(err)
})
}
Related
I would like some basic error handling on every route, so if there is ever an exception, the API at least responds with 500.
According to this pattern, you still need to include a try/catch block in every route:
app.post('/post', async (req, res, next) => {
const { title, author } = req.body;
try {
if (!title || !author) {
throw new BadRequest('Missing required fields: title or author');
}
const post = await db.post.insert({ title, author });
res.json(post);
} catch (err) {
next(err) // passed to the error-handling middleware
}
});
That seems a little repetitive. Is there a higher-level way where exceptions are automatically caught everywhere and passed to the middleware?
I mean, it would obviously be possible for me to just define my own appGet():
function appGet(route, cb) {
app.get(route, async (req, res, next) => {
try {
await cb(req, res, next);
} catch (e) {
next(e);
}
});
}
Is there some built in version of this?
You can use express-promise-router package.
A simple wrapper for Express 4's Router that allows middleware to return promises. This package makes it simpler to write route handlers for Express when dealing with promises by reducing duplicate code.
E.g.
app.ts:
import express from 'express';
import Router from 'express-promise-router';
import bodyParser from 'body-parser';
const router = Router();
const app = express();
const port = 3000;
app.use(bodyParser.json());
app.use(router);
router.post('/post', async (req, res) => {
const { title, author } = req.body;
if (!title || !author) {
throw new Error('Missing required fields: title or author');
}
const post = { title, author };
res.json(post);
});
router.use((err, req, res, next) => {
res.status(500).send(err.message);
});
app.listen(port, () => console.log(`Server started at http://localhost:${port}`));
You don't need try/catch statement block anymore.
Test result:
I think the better approach would be to divide the services and the controllers which is demonstrated below.
Add post service:
async function addPostService (title, author) => {
if (!title || !author)
throw new BadRequest('Missing required fields: title or author');
return await db.post.insert({ title, author });
};
Add post controller:
function addPost(req, res, next){
const { title, author }= req.body;
addPostService
.then((post) => {
res.json(post);
})
.catch(next) // will go through global error handler middleware
}
Now, we can make a global error handler middleware which will catch the error thrown by any controller throughout the app.
function globalErrorHandler(err, req, res, next){
switch(true){
case typeof err === 'string':
// works for any errors thrown directly
// eg: throw 'Some error occured!';
return res.status(404).json({ message: 'Error: Not found!'});
// our custom error
case err.name = 'BadRequest':
return res.status(400).json({ message: 'Missing required fields: title or author!'})
default:
return res.status(500).json({ message: err.message });
}
}
And, don't forget to use the error handler middleware right before starting the server.
// ....
app.use(globalErrorHandler);
app.listen(port, () => { console.log('Server started...')});
I am new to express (or any JS backend) so sorry if question was already answered, or kind of stupid.
I have registered endpoint.
app.get('/hello-world'), async (req, res) => {
try {
// do something
sendResponse({"Message": "Hello World"}, res);
} catch (e) {
handleError(e, res);
}
});
Where sendResponse and handleError are doing just setting status and body / additional exception metadata using res.status().json()
Is there any way to make response handling more simple by registering some response handler and write the logic of response / exception handling at one place?
What I have in mind is this:
Change example endpoint logic to:
app.get('/hello-world'), async (req, res) => {
return {"Message": "Hello World"}
// or throw new error
});
and some repsonse handler which will handle result of function
resposeHandler(payload, err, res) {
if (err) {
res.status(500).json(err) // just as an example
} else {
res.status(200).json(payload)
}
}
You can create a function wrapper to catch all the errors and send them to the error middleware:
const errorHandler = (routeHandler) =>
(req, res, next) => {
const routeHandlerReturn = routeHandler(req, res, next)
return Promise.resolve(routeHandlerReturn).catch(next)
}
Then you can reutilize it in all your controllers, making the app much cleaner:
app.get('/hello-world', errorHandler(async function(req, res, next) {
sendResponse({"Message": "Hello World"}, res);
});
If some error is thrown, it will be handled in your error handler middleware:
// index.js
app.use((err, req, res, next) => {
res.status(err.status || 500)
res.json({
message: err.message || 'Internal Error',
error: err.error
})
})
create two middlewares, one for error handle and the other one for success response
// error handling when error occured
function errorHandler(req,res,next) => {
return res.status(err.status || 500).json({
success: false,
message: err.message
});
};
// success response and return data
function successHandler(successMsg, successData) => {
return (req,res,next) => {
return res.status(200).json({
success: true,
message: successMsg,
data: successData
});
};
};
register them in express
const app = express();
app.get('/someroute', successHandler('it is endpoint of someroute!', 'your data here'))
app.use(errorHandler)
use errorHandler after you call and define the route
I am trying to pass data from controller to route.
I want to change the status code from a controller to route. let say if in controller status 200 then change it into 400 from routes
or
just simple print hello or something from routes after the response
here is controller from controller file
contact controller. js
exports.index = function(req, res) {
Contact.get(function(err, contacts) {
if (err) {
res.json({
status: "error",
message: err
});
}
res.json({
status: "success",
message: "Contacts retrieved successfully",
data: contacts
});
});
};
here is the route of it from route file
contact router.js
var contactController = require('./contactController');
// Contact routes
router.route('/contacts')
.get(contactController.index)
Follow this article to design you application using express router
https://scotch.io/tutorials/learn-to-use-the-new-router-in-expressjs-4
define your controller like this
exports.index = function(req, res, next) {
Contact.get(function(err, contacts) {
if (err) {
next(null,{
status: "error",
message: err
});
}
next({
status: "success",
message: "Contacts retrieved successfully",
data: contacts
},null);
});
};
Define main app file like this
var contactController = require('./contactController');
var router = express.Router();
// apply the routes to our application
// route middleware that will happen on every request
router.use(function(req, res, next) {
// continue doing what we were doing and go to the route
next();
});
// about page route (http://localhost:8080/about)
router.get('/contacts', function(req, res) {
//here you can call your controller js method
contactController.index(req,res, function(data, err){
//change anything you want here and set into res.
if(err){
//change status and data
}
else{
//change status and data
}
})
});
Don't end the request-response cycle in the controller, just return the result from the controller instead of ending the cycle.
const httperror = require('http-errors');
exports.index = async function(parameter) {
Contact.get(function(err, contacts) {
if (err) {
throw new httperror(400, "Error occured!");
}
return {
status: "success",
message: "Contacts retrieved successfully",
data: contacts
}
});
};
request should start from the route and response should be sent from the route
const contactController = require('./contactController');
router.get('/contacts', function (req, res, next) {
contactController.index()
.then(result => {
res.json(result)
}).catch((error) => {
res.status(200).json({"Error":"Returned success code 200 even though error
occured"});
})
});
Following is the code I am referring to,
axios.get('http://localhost:1337/')
.then( response => {
var dataSet = response.data;
// URL Handle for Data page
app.get('/data', (req, res) => {
res.render('data', {
data = dataSet
});
});
})
.catch( error => {
console.log('An error occurred: ' + error);
});
// URL Handle for Contact page
app.get('/contact', (req, res) => {
res.render('contact');
});
// URL Handle for Error page
app.all('*', (req, res) => {
res.render('error');
});
Now, as I am using Axios for fetching the response from the database, I am also rendering the data in that desired page where I want the response of the API.
But there's a problem when I am using "app.all" handler for the wrong URL handle, it redirects to the error page.
I need a solution in which there can be an exception which can be added to the "app.all" handler and whenever the handle is '/data' it does not redirect to '/error'.
Looks like your route for /data is registered after the async call of axios is completed.
Try, putting axios call inside route handler
app.get('/data', (req, res) => {
axios.get('http://localhost:1337/').then( response => {
var dataSet = response.data;
res.render('data', {
data: dataSet
});
})
.catch( error => {
console.log('An error occurred: ' + error);
});
});
// URL Handle for Contact page
app.get('/contact', (req, res) => {
res.render('contact');
});
// URL Handle for Error page
app.all('*', (req, res) => {
res.render('error');
});
You should use axios in controller, so code should look like this:
app.get('/data', async (req, res, next) => {
try{
const { body } = await axios.get('http://localhost:1337/')
res.render('data', {
data : body
});
}catch(error){
next(error)
}
});
As You see, if there is error You should call next function with error instance and then create middleware as errorHandler to catch it.
Here's the response-hander.js middleware file. I have 2 function that extend the res object normal behavior. If I use these function in a normal route everything works fine, the problem shows when I try to call one of these in another middleware.
var responseHandler;
responseHandler = function(req, res, next) {
res.jsonSuccess = function(results) {
res.status(200).json({
status: "OK",
results: results
});
};
res.jsonBadRequest = function(message) {
res.status(400).json({
status: "BAD_REQUEST",
error_message: message
});
};
next();
};
module.exports = responseHandler;
Here's the error-handler.js middleware:
var errorHandler;
errorHandler = function(error, req, res, next){
if (error instanceof Error) {
res.jsonBadRequest("Error.");
}
}
module.exports = errorHandler;
The error:
TypeError: res.jsonBadRequest is not a function
Example:
This works
app.get("/my/route", function(req, res) {
return res.jsonBadRequest("Test");
});
This doesn't
app.get("/my/route", function(req, res) {
// the error middleware gets calld but dosn't find my function.
throw new Error();
});