Reputation: 5781
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:
Upvotes: 1
Views: 1498
Reputation: 5781
RobbyD set me on the right track. This is what I've ended up with:
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);
function setupHandler(req, res, next) {
res.locals.standardRes = {
"some": "data"
};
res.locals.doResponse = (res) => {
// ...
res.json(res.locals.standardRes);
};
next();
}
function getEmployees(req, res, next) {
somethingThatReturnsAPromise().then(data => {
// add to res.locals.standardRes here
res.locals.doResponse(res);
}).catch(err => {
next(err);
});
}
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.
Upvotes: 0
Reputation: 513
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.
Upvotes: 4