I have two resources, employees and employee groups. I'm trying to implement a nice URL structure like:
GET /employees List employees.
GET /employees/123 Get employee 123.
GET /employees/groups List employee groups.
GET /employees/groups/123 Get employee group 123.
Using ExpressJS I have:
router.get('/employees', (req, res, next) => { next(); });
router.get('/employees/:id', (req, res, next) => { next(); });
router.get('/employees/groups', (req, res, next) => { next(); });
router.get('/employees/groups/:id', (req, res, next) => { next(); });
router.all('*', (req, res) => { res.send('...'); });
This doesn't work, because Express can't tell the difference between /employees/:id and /employees/groups. It thinks groups is an id because /employees/:id comes first.
I did have URL's like:
GET /employees
GET /employees/123
GET /employees-groups
GET /employees-groups/123
Which works, but doesn't have the nice resource/sub-resource format. The groups are groups of employees and so I'd like the URL's to match that.
If I were getting the groups for an employee it would be fine (/employees/:id/groups), but I'm getting all groups, which are employee groups.
How could I set up Express routes to route properly while still keeping the URL structure I want..?
I guess I need a way for Express to distinguish between an id and a sub-resource. Is there any way to do that..?
UPDATE
I obviously should've said that I'm using next() in each handler, because I need Express to move onto another middleware function, one that controls the response of all requests. It's this other middleware function that actually sends a response. So I need:
Handler for the route.
Handler for all requests.
Express searches for the first route that matches and handles it with the provided function.
Try the other way around:
router.get('/employees', (req, res) => {});
router.get('/employees/groups', (req, res) => {});
router.get('/employees/groups/:id', (req, res) => {});
router.get('/employees/:id', (req, res) => {});
Now express will work its way trough the routes, '/employees/123' will only match on the last route, so that one will be used by express. '/employees/groups' will be matched sooner by the second route and that one will be used.
Very simple but these things can cost you some time figuring out.
RobbyD set me on the right track. This is what I've ended up with:
index.js
router.all('*', setupHandler);
router.get('/employees', getEmployees);
router.get('/employees/groups', getGroups);
router.get('/employees/groups/:id', getGroup);
router.get('/employees/:id', getEmployee);
router.use(errorHandler);
setupHandler()
function setupHandler(req, res, next) {
res.locals.standardRes = {
"some": "data"
};
res.locals.doResponse = (res) => {
// ...
res.json(res.locals.standardRes);
};
next();
}
getEmployees()
function getEmployees(req, res, next) {
somethingThatReturnsAPromise().then(data => {
// add to res.locals.standardRes here
res.locals.doResponse(res);
}).catch(err => {
next(err);
});
}
errorHandler()
function errorHandler(err, req, res, next) {
console.log('err', err);
// add to res.locals.standardRes here
// set correct res.status here
res.locals.doResponse(res);
}
So the handlers are in the order in RobbyD's answer. I've used res.locals to hold a response function (doResponse(res)) to call from each handler. If there's an error I call next(err) as normal to move to errorHandler().
I guess it's all about getting the right flow from middleware to middleware and sending the response at the right time.
Related
I was trying to make a routes for each ID I using a forEach loop but It stay loading until timeout reaches, all expected values are in place, all good but the second route is not running, I was fighting it despretly until now. I made sure there is a problem.
server.js
const router = require('express').Router();
function isAuthorized(req, res, next) {
if (req.user) {
next();
}
else {
res.redirect('/login')
}
}
let myguild = [];
router.get(`*`, isAuthorized, (req, res) => {
res.status(200);
console.log("wow");
console.log(req.user.guilds.length)
req.user.guilds.forEach(guild => {
myguild.push(guild);
})
console.log("Finished");
myguild.forEach(guild => {
console.log('Started')
router.get(guild.id, (req, res) => { // here is the problem
console.log("uh")
res.send("HAMBURGER")
console.log(req, res, guild)
})
console.log("Outed")
})
});
module.exports = router;
output:
wow
23
Finished
Started
Outed
Started
Outed
Started
Outed
Star... 'there is more but this is enough'
It should behave and run within server/${guild.id} but got (failed) request
Any Ideas?
You might need to redesign the API to better fit what you're trying to accomplish. If you already know which guilds are available then you'd need to create those before the server is initialized.
Even if they come from a database or are dynamic, you can loop through the guild "options" and create endpoints then provide access to them only if the user is qualified.
const { guilds } = require('./config')
const guildHandler = (req, res) => {
// Assuming you're doing more here
res.send('Hamburger')
}
guilds.forEach(guild => router.get(`/guilds/${guildId}`, guildHandler)
Or if you are NOT doingg something different in the middleware for each guild then you could just have a single route for guild.
router.get('/guilds/:guildId, guildHandler)
Not really sure what you're trying to accomplish but checkout out the Express docs. They solve most use cases fairly easily.
https://expressjs.com/en/api.html#req
You never call res.end() from your outer res.get() handler, so the request never completes.
And, with respect, creating route handlers like that in a loop is a mistake. It will lead to real performance trouble when your app gets thousands of guilds.
You'll want to use just one route, with a named route parameter, something like this.
const createError = require('http-errors')
router.get(':guildid', isAuthorized, (req, res, next) => {
const guildid = req.params.guildid
if (req.user.guilds.includes(guild)) {
console.log("uh")
res.send("HAMBURGER").end()
console.log(req, res, guildid)
} else {
next(createError(404, guildId + ' not found'))
}
})
Thanks for everyone helped.
Inspired answer
Final Result:
server.js
router.get('/:guildid', isAuthorized, (req, res, next) => {
console.log('started')
const guildid = req.params.guildid
if (req.user.guilds.some(guild => guild.id === guildid)) {
console.log('uh')
res.send("HAMBURGER").end()
} else {
res.sendStatus(404);
}
})
I have the following case:
There is a list of routes in the form
var lst = ["route1/:foo", "route2/:bar", "route3/:bar/route4/:baz", ..] // this list has like 200 entries
I have the following code
app.use(lst, function (req, res) {
// here I want to know which route the middleware was invoked on
// req.route.path doesn't work unless I use app.METHOD()
// req.originalUrl would give me route1/200, instead of route1/:foo
})
What I tried so far:
Using the router stack as in app._router.stack, my routes aren't even registered there - I don't see route1/:foo, route2/:bar and route3/:bar/route4/:baz
Hook into the express router:
var Route = express.Route;
let defaultImplementation = Route.prototype.dispatch;
function foo(req, res) {
console.log('Called route ', req.route.path); // still doesn't trigger on the routes in lst, only parent routes
}
Route.prototype.dispatch = function handle(req, res, next) {
foo(req, res); // req.route is available here
defaultImplementation.call(this, req, res, next);
};
By the way, I'm passing those routes and using them along with http-proxy-middleware https://github.com/chimurai/http-proxy-middleware, so if you have any clues on how do achieve that with that library as well, I'd be very grateful as I couldn't find out.
As stated, multiple callback functions can be provided and behave like middleware to handle a request. They can be in the form of a function, an array of functions, or combinations of both, as shown in the following examples.
For example:
app.get('/example/b', function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res) {
res.send('Hello from B!')
})
What's the purpose of this? Can't we simply use:
app.get('/example/b', function (req, res) {
console.log('the response will be sent by the next function ...')
res.send('Hello from B!')
})
The multiple functions would more likely be used when you already have a previously defined function that you probably intend to use in multiple places. For example:
app.get("/somePath", checkAuth, function(req, res) {
// you know it's already authenticated here
});
app.get("/someOtherPath", checkAuth, function(req, res) {
// you know it's already authenticated here
});
function checkAuth(req, res, next) {
if (some logic here) {
// allow handler chain to continue
next();
} else {
// auth error
res.status(401).end();
}
}
Of course, you could also use middleware for checking authentication, but the above example allows you to target just a few specific routes with some middleware that you may use in multiple places.
As you have already observed, if you don't intend to use the function anywhere else, then you may as well just put the logic into your one handler.
Yes you can, the purpose is for example, to handle errors, the middleware sequence in express allows you to use this way. For example, see this way to set up the express config:
app.use(logger.connectLogger());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(routes);
app.use(errorConnect);
http.createServer(app).listen(config.port, function () {
logger.getLogger().info('My backend listening on port: ' + config.port);
});
My routes module have all the matching route -> callback:
// Methods exposed for backend API.
router.get('/status', ips.getStatus);
router.route('/ip')
.post(ips.keepIps)
.get(ips.getIps)
// NOT ALLOWED
.put(returnNotAllowed)
.delete(returnNotAllowed);
// Methods exposed and used by IP's frontend.
router.route('/front/ip')
.get(front.getIpsByFront)
.post(front.keepIpsByFront);
router.post('/login', login.login);
....
For example in one of those callbacks, I have the next way to manage an incoming request:
/**
* Login user from frontend.
*/
exports.login = function(req, res, next) {
var username = req.body.username + '#System',
password = req.body.password,
server = req.body.server,
auth = 'Basic ' + new Buffer(username + ':' + password).toString('base64');
loginApi.login(auth, server)
.then(function(result) {
res.statusCode = 200;
res.send(result);
})
.catch(function(error) {
next({
statusCode: 403,
message: 'Error in login'
});
});
};
When I catch an error, I call next with a custom error object, and after this, if you back and watch the config (first paragraph) you can see that I added to the express middleware an error manage with errorConnect. In my opinion this is a usefull way to understand what you are asking because if I understand well you had doubts with next()
I am using Express.js as http server. Defined all my routes.
Most endpoints need to verify session before returning a response. E.g. below code serves users in the system and list of services respectively:
function getUsers(req, res, next) {
verifyUser(req, res, next, function () {
//serve users
});
}
function getServices(req, res, next) {
verifyUser(req, res, next, function () {
//serve services
});
}
You probably noticed there is a verifyUser function which validates the session. Which is as below.
function verifyUser(req, res, next, callback) {
var sessionKey = req.cookies.sessionKey;
var user = users.userBySession(sessionKey);
if (user) {
callback(req, res, next, user);
} else {
res.status(401).send({
message: 'Unauthorized'
});
}
}
As you can see I keep passing in req, res and next parameters along with a callback whenever I use this function.
I tried to use apply function to make it easier. Changed my getUsers function like this:
function getUsers(req, res, next) {
verifyUser
.apply(null, arguments, function () {
//serve users
});
}
The problem with this approach is callback is not passed into verifyUser function. And I don't really like passing null as scope with each call.
How can I achieve this by writing less and better code ? Any ideas?
You could use bind to create a 'partial function':
// create bound responseHelper object
var responseHelper = verifyUser.bind(null, req, res, next);
// usage
responseHelper(getUsersCallback); // same as verifyUser(req, res, next, getusersCallBack);
I think you're looking to turn verifyUser into a middleware function.
function verifyUser (req, res, next) {
var user = // yadda yadda session stuff
if (user) {
req.user = user; // [1] what you do to the req object here...
} else {
return res.status(401).send({ message: "No way Smokey Joe"});
/**
* alternatively, do something like
* var err = new Error("Not authorized");
* err.statusCode = 401;
* return next(err);
*
* this will kick off Express' error handling mechanism,
* which you should read about in the docs (see the link below)
*/
}
next();
// very important to call next after this verifyUser has done its job
// if you don't, the next middleware won't go off,
// and the request will just hang
}
function getUsers (req, res, next) {
// [2] will show up on the req object here, assuming you chain these
// two functions together as middleware
}
app.get("/users", verifyUser, getUsers);
app.get("/services", verifyUser, getServices);
// here's a route that needs no session auth, so no need to verifyUser
app.get("/latest-posts", getLatestPosts);
When you tell Express to use a function or attach a function to a route path via get('/my/route', hanlderFun) or some such, you've basically turned handlerFun into a middleware.
You can define however many middleware as handlers on a route as you like, and they'll all execute in turn as long as you keep calling next.
app.post("/checkout", verifyUser, tallyCart, checkInventory, doPayment, sendInvoice);
The job of next is to pass control from the current middelware to the next one. It's an object
You can do other stuff with next, too, which you should read up on in the docs.
http://expressjs.com/en/guide/writing-middleware.html
http://expressjs.com/en/guide/using-middleware.html
The docs on routing have good info on middleware as well:
http://expressjs.com/en/guide/routing.html
For extra credit, check out error handling middleware, too:
http://expressjs.com/en/guide/error-handling.html
I have this middleware:
app
.use('/:lang?', middleware.setLanguage)
.use('/thanks/:lang?', middleware.setLanguage)
.use('/forum/:lang?', middleware.setLanguage);
I want use a function called setLanguage in every route that have lang?, and currently this code is working, but i can believe dry that.
Someone knows how to? I search in the documentation, but didn't find anything..
Use .param to process the parameters up front so that you don't have to do it on every route that uses it. For example, here's one that starts building a query with moongoose for a product crud:
app.param('product_id', function (req, res, next, product_id) {
req.product_id = new ObjectId(product_id);
req.product = Product.findOne(req.product_id);
next();
});
// update product
app.put('/products/:product_id', function (req, res) {
Product.findOneAndUpdate(req.product_id, req.body, function (err, product) {
res.json(product.toObject());
});
});
// get product by id
app.get('/products/:product_id', function (req, res) {
req.product.lean().exec(function (err, product) {
res.json(product);
});
});
None of my routes have to get the product id and convert it to an ObjectId because i did that up front for all routes with .param.