BlogNode.js

Why are my Express params undefined?! (and how to fix it)

Written by Codemzy on June 30th, 2023

Are your Express route params `undefined`? If you are not getting your route parameter values where you need them, this blog post should help you out of `undefined` hell with three easy fixes!

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.

  1. 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).
  2. 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.

Preserve the req.params values from the parent router. If the parent and the child have conflicting param names, the child’s value take precedence.

- Express express.Router([options])

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.