How to trigger next() in express Router.route and controller pattern? - javascript

I can't seem to figure out how to implement 'next()' after the request lands to one of my api routes. I want to do logging after everything is done, and not before my api routes. I have implemented the controller pattern on my routing. I can only do logging for now on errors, but I want to do logging on all requests:
//------ api/index.js
import routes from './routes/index.route';
app.use('/api', routes);
// ??? HOW TO HANDLE next() called in "/api routes"
// only handles incoming requests not found in "/api routes"
app.use((req, res, next) => {
// logic here...
next(error);
});
// global error handler
app.use((error, req, res, next) => {
// logging logic here...
});
//------ routes/index.route.js
import express from 'express';
import userRoutes from './user.route';
import bookRoutes from './book.route';
const router = express.Router();
router.use('/user', userRoutes);
router.use('/book', bookRoutes);
//------ routes/book.route.js
import express from 'express';
import bookCtrl from './../controllers/book.controller';
const router = express.Router();
router.route('/:bookId').get(bookCtrl.getBook);
//------ controllers/book.controller.js
export const getBook = async (req, res, next) => {
const book = // get book logic here;
return res.status(httpStatus.OK).json(book);
// HOW DO I TRIGGER next()?
// I tried this approach as well:
// res.status(httpStatus.OK).json(book);
// next();
//however, it would say, "Error: Can't set headers after they are sent."
}
Thank you so much.

I figured out a way to trigger next which is not sending a response immediately instead assigning it to res.locals and calling next. Here are the changes based on my above code:
//------ api/index.js
// handle next() event coming from "/api routes"
// and also handles incoming requests not found in "/api routes"
app.use((req, res, next) => {
if (res.locals.status) {
const { status, responseData } = res.locals;
res.status(status).json(responseData);
// logging logic here...
} else {
// error logic here...
// forward this error into the global error handler
next(error);
}
});
// controllers/book.controller.js
export const getBook = async (req, res, next) => {
// get book logic here..
const book = // get book logic here;
res.locals = {
status: httpStatus.OK,
responseData: {
book,
},
};
next();
}
Thanks to #destoryer for the res.json tip! :)

Just replace
return res.status(httpStatus.OK).json(book);
// HOW DO I TRIGGER next()?
with
res.status(httpStatus.OK).json(book);
next();
And it will work.

Related

chain middleware functions in custom function

I know that I can chain middleware functions after passing in the route like
const express = require('express');
const router = express.Router();
router.post('/', middlewareFunction1, middlewareFunction2, controllerFunction);
module.exports = router;
I would like to know if it's possible to call only one function (called gateway)
router.post('/', gatewayFunction1);
and this function is able to chain all those methods
const controller = require('../controllers/controller');
function gatewayFunction1(request, response, next) {
// validate route
// do other middleware stuff
// call controller.function1
}
module.exports = {
gatewayFunction1,
};
Why would I do that? I was thinking about separating the middleware logic from the routes. This gateway should just get executed after routing and before calling the router.
I tried to return an array of functions (example code)
function gatewayFunction1(request, response, next) {
return [(request, response, next) => {
console.log('called middleware 1');
next();
}, (request, response, next) => {
console.log('called middleware 2');
next();
}, (request, response, next) => {
response.status(200).json({
message: 'that worked',
});
}];
}
but when I call this api route I get no response
Could not get any response
so it keeps loading forever. Is there a way to chain these middleware functions within another function?
Your gatewayFunction1 does nothing except returns an array.
Just use router.
const express = require('express');
const gatewayFunction1 = express.Router();
gatewayFunction1.use(middlewareFunction1, middlewareFunction2, controllerFunction);
module.exports = gatewayFunction1;
Then
const gatewayFunction1 = require('...'); // where you define gatewayFunction1
router.post('/', gatewayFunction1);
Middleware should be a function and you are returning an array.If next function is not called it will get stuck. I don't like the whole idea combining them but I think the best way is to import all your middleware functions in one function and call them individually then use that function as your combined middleware.

Express Nested Router not calling sub functions

I have the following router.use calls in one of my routers
router.use("/:collection/", (req) => {
return require(`./${req.params.collection}`);
});
and that calls in this example, example.js
example.js is as follows:
const header = require("../../header"); //gets our header that declares everything
const router = header.express.Router(); //makes our router for collections requests
console.log("123");
///The Following is when a name is requested
router.get("/test", (req, res, next) => {
console.log("test");
res.json({msg:"hi"});
next();
});
module.exports = router; //makes our router avialable
you'd expect when:
http://localhost:3000/api/example/test
is request that it would write in the console something to the effect of:
123
test
and I would get the response:
{msg:"hi"}
Instead the console gets just:
123
written and there is no response.
It seems the
router.get
in the example.js is never called, can someone tell me why?
I fixed it, instead of
router.use("/:collection/", (req) => {
return require(`./${req.params.collection}`);
});
I use
router.get("/:collection", (req, res) => {
//this is my other call that will do stuff in the parent file
//we don't call next because it is already matched, otherwise we call next
});
router.use("/:collection/", (req, res, next) =>{ //says if it gets here pass on the info
router.use("/:collection/", require(`./${req.params.collection}`)); //then route
next();
});

ExpressJS 4 middleware problems

I'm trying to wrap my head arround the middleware in ExpressJS 4.
If I understood correctly is that middleware are applied in the order of declaration and that you can "bind" them at different level.
Here I'm trying to bind a middleware at router level.
function middleware(req, res, next) {
console.log("middleware");
res.write("middleware");
next();
}
function handler(req, res) {
console.log("OK");
res.status(200).send('OK');
}
const router1 = express.Router();
const router2 = express.Router();
router1.get("/1", handler);
router2.get("/2", handler);
I would except the following to print OK when calling /test/1 and middleware on /test/2.
app.use("/test/", router2.use(middleware), router1);
But the output seems to be inverted and is equivalent to:
app.use("/test/", router2, middleware, router1);
What I really want is that only the first router to use the middleware.
In other word scope the use of the middleware to the first controller.
I could easily swap the order of router1 and router2 but my other requirement is because my router2 use in fact a route that catch all requests (/:id) I need to have it last.
What I'm missing here and how can I do what I want ?
EDIT for clarification:
What I ultimely want is something along this:
/
|-test/
|-route // use middleware
|-something // use middleware
|-another // use middleware
...
|-:id // without middleware
That's why I have a router with many routes that are under router1 where I want the middleware.
And router2 with a catch-all without the middleware.
You can set a middleware when defining the routes:
router1.get("/1", middleware, handler);
router2.get("/2", handler);
The 1st will use the middleware and the second not.
BTW I would suggest the followings: do not create separate router for each route, only one is enough.
const router = express.Router();
router.get("/1", handler);
router.get("/2", handler);
app.use("/test/", router);
As router1 and router2 are bound to a common route, they will be executed in order, along with their middlewares until one of the routers route matches with the requested path/route.
In your case, as you can't swap the order of the routers, you can create a middleware that wraps the router, checks if the requested path/route exists in the router and if so, returns it, otherwise just skip it.
var unlessMatch = function(router) {
let routerPaths = [];
// Retrieve, create a regex and store every route of the Router
router.stack.forEach(layer => {
if (layer.route) {
routerPaths.push(layer.route.path.replace(/\/?(:[^\/]+)(\/?)/g, "/[^\/]+"));
}
});
return function(req, res, next) {
// Check if requested route exists in the router
routerPaths.every(path => {
return new RegExp('^' + path + '(\/)?$').test(req.path) ? router(req, res, next) : true;
});
return next();
};
};
function middleware(req, res, next) {
console.log("middleware");
next();
}
function handler(req, res) {
console.log("OK");
res.status(200).send('OK');
}
const router1 = express.Router();
const router2 = express.Router();
// Bind middleware to router2
router2.use(middleware);
router1.get("/1", handler);
router2.get("/2", handler);
// Wrap router2 into unlessMatch middleware/function so that router2's middlewares
// are not executed if no route matches with the requested one
app.use("/test/", unlessMatch(router2), router1);

Using Express module as middleware

I'm new to Express and trying to use middleware to handle a POST request. If I expose the endpoint, and make a request to the API, everything works fine.
Working Correctly
api/index.js
app.post('/api/endpoint', (req, res, next) => {
next();
});
server.js
app.use(function() {
console.log('hello'); // => hello
});
But when I try to replace the middleware function with a module that exports a function, the function never gets invoked.
Not Working
api/index.js
app.post('/api/endpoint', (req, res, next) => {
next();
});
server.js
const makeExternalRequest = require('./server/makeExternalRequest');
...
console.log(makeExternalRequest, typeof makeExternalRequest);
// => [Function] 'function'
app.use(makeExternalRequest);
server/makeExternalRequest.js
module.exports = function(err, req, res, next) {
console.log('hello', err);
}
The function in server/makeExternalRequest.js is never invoked, and nothing logs... Am I using app.use(...) incorrectly?
Express middleware requires three arguments, the third of which is a function you call when you're done to move the request along to the next handler:
module.exports = function (req, res, next) {
console.log('hello');
next();
};
Without calling the third parameter, your request will just remain pending and a response will never be sent. Also, be sure you call app.use before any handler that would return the response. If the response is sent first, then your middleware will never be reached.

Manually chaining Express middleware

I currently use 2 middlewares:
Express-jwt which extracts/validates a JsonWebToken from a request and my own middleware that checks that the JWT contains specific information (permissions).
I want to conditionally use those middlewares together (based on whether there's a specific swagger attribute on a route).
I want to do something like this:
let expressjwt = function(req, res, next) { ... };
let jwtValidator = function(req, res, next) { ... };
app.use((res, req, next) => {
if(req.swagger.someAttribute) {
expressjwt(req, res, jwtValidator(req, res, next));
// The issue here is that jwtValidator will get called even if
// expressjwt produces an error
} else {
next();
}
});
It sounds like the question is - "how do you conditionally call service B only if service A succeeds."
This is one of main goals of promises - it allows you to chain together async calls and have them conditionally "resolve." I can post a code sample if needed.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
I ended up wrapping my first middleware in a Promise using Promise.fromCallback, from memory, something like this:
if (req.swagger.someAttribute) {
Promise.fromCallback(cb => expressjwt(req, res, cb))
.then(() => {
return jwtValidator(req, res, next);
})
.catch(next); // Or deal with the rejection
} else {
next();
}
Promise.fromCallback is useful because next() is only called with arguments if the middleware failed and thus will become promise.reject

Categories