If you use Express routing in Node.js, then you'll probably use route params. Let's imagine a GET
request comes in for a user, how do you know which user is being requested?
Route params!
app.get('/users/:userId', (req, res) => {
res.send(req.params.userId);
})
That :userId
part of the route is a route parameter. You can start any part of your route with :
to tell express that it's a route param.
So the actual request path won't be /users/:userId
- it will be /users/12345
or /users/65434543
or whatever the user ID is for the request.
Ok, that's the intro, and you probably know all of that. You have your router set up in Express, but req.params
is undefined. Annoying!
Let's fix it.
Check you set the route param correctly
app.get('/users/userId', (req, res) => {
res.send(req.params.userId); // undefined
})
See the problem with the code above? Check it against the first example!
We are missing the :
before userId
. And that means it's not set up as a route param.
Make sure you have your routes set up correctly!
Here's another common mistake.
app.get('/users/:userId', (req, res) => {
res.send(req.params.user); // undefined
})
Did you spot the problem?
The parameter is userId
, but we used req.params.user
.
Are your route parameters set up correctly with no typos? Ok, now things get interesting!
Define the param on the route it's needed
A common reason why you might get undefined
for your route params is when you use a router. Let's say you have all of your user routes defined in a userRoutes
file.
routes.js
const express = require('express');
const app = express();
const userRoutes = require('./userRoutes');
app.use('/user/:userId', userRoutes);
userRoutes.js
const express = require('express');
const router = express.Router();
// get the user
router.get('/', (req, res) => {
res.send(req.params.userId); // undefined
});
module.exports = router;
In the routes.js
file, we have the Express app, and in the userRoutes.js
file we have a separate router for route paths that start with '/user/:userId'
.
Any GET
requests to /users/12345
will go via the '/user/:userId'
route and to the userRoutes
router, BUT the router does not have access to the userId
route param because it was defined on the parent router!
A quick fix for this is to only define route params on the router where they are needed.
Our example will instead look like this:
routes.js
const express = require('express');
const app = express();
const userRoutes = require('./userRoutes');
app.use('/user', userRoutes);
userRoutes.js
const express = require('express');
const router = express.Router();
// get the user
router.get('/:userId', (req, res) => {
res.send(req.params.userId); // 12345
});
module.exports = router;
Yay! Now it works!
But wait, what about if you NEED the route params in the parent and the child?
Use mergeParams
to get params from the parent router
Ok, so far we've seen how to define route params on the router they are needed, but things don't always shape up like that.
For example, in a recent project, I was building an API that took a version route param. It was needed for all the routers, and I didn't fancy adding :version
to every child router. I could just add it in one place, like this:
const express = require('express');
const app = express();
const userRoutes = require('./userRoutes');
app.use(':version/user', userRoutes);
The GET
request will now be /v1/users/12345
, with v1
being the version. The use case is so I can add different versions to my API.
Adding the route parameter here solves two problems.
- I want the version to come before the feature (
/user
) in the path so that I can have other features and routes in my app (/books
,/teams
, etc). - The child routers will have many routes, and I don't want to add
:/version
to each one - I might forget!
But we are back to the same old problem in our userRoutes.js
router.
const express = require('express');
const router = express.Router();
// get the user
router.get('/:userId', (req, res) => {
console.log(req.params.version); // undefined
res.send(req.params.userId); // 12345
});
module.exports = router;
We can access the userId
route param because that's defined within the router. But get undefined
for the version
param, because it's on the parent router.
I've mentioned a couple of valid reasons why, for this route param, it's not practical to move it down to the child router, so what can I do?
mergeParams
!
When you create a new router object with Express, you can give some options, and one of those options is mergeParams
.
mergeParams
defaults to false, so you need to pass it with a true
value if you want to get the params from the parent router.
const express = require('express');
const router = express.Router({ mergeParams: true });
Now let's see it in action in our example:
routes.js
const express = require('express');
const app = express();
const userRoutes = require('./userRoutes');
app.use(':version/user', userRoutes);
userRoutes.js
const express = require('express');
const router = express.Router({ mergeParams: true });
// get the user
router.get('/:userId', (req, res) => {
console.log(req.params.version); // v1
res.send(req.params.userId); // 12345
});
module.exports = router;
Boom! No more undefined!
❗If you need to pass a param through multiple routers, remember to add the option { mergeParams: true }
to each router it needs to go through.