How many ways can app.param() be used?

Question

How many ways can app.param() be used?

Answer

app.param() has one purpose, it helps us condense routes with the same route parameter and passing the request through the same callback function. for example, if we remember from this lesson, we set up a parameter trigger using app.param() to check with the :snackId parameter if the snack exists in our array if it does it will be returned and if it doesn’t it will return a 404 status response.

Most commonly we will implement this method to implement logic to check for the existence of the data requested related to the route parameter, in which case the item will be either return, sent through next(), or assigned as a property of the request object to be used in the next middleware callback. ie:

app.param('user', function(req, res, next, id) {

  // try to get the user details from the User model and attach it to the request object
  User.find(id, function(err, user) {
    if (err) {
      next(err);
    } else if (user) {
      req.user = user;
      next();
    } else {
      next(new Error('failed to load user'));
    }
  });
});

this way if I had:

app.get('user/:user', (req, res, next) => {
  console.log(req.user);
})

because the callback from param() is trigger first when the route hits that parameter, req.user will have already the user object assigned to it if it exists.

Now, app.param() is not meant to substitute app.use() they both assigned middleware to routes, but param is only concern with route parameters not the route itself, so we need to be aware of that difference.

1 Like

I understand the concept of app.params, but I’m not able to make this work in practice.

From what I understand, this is what is supposed to happen:

  1. the route matches up with app.params
  2. req and res are sent to app.params, which is executed first.

What I did to try to make this work is create an array of objects called User, like this:

const User = [
    {id: 1, username: 'test user 01'},
   {id: 2, username: 'test user 02'}
    ];

My question is this:

Inside the app.param example, how does the .find() method work here? I’m getting two different responses depending on whether I use a slash or no slash before ‘user’.

When I add a GET route to ‘user/:user’ (notice no initial slash), app.params, returns “cannot GET user”.

I’m using Postman to send the GET request to http://localhost:port/user/1

When I add a GET route to ‘/user/:user’ (notice the initial slash), app.params tells me that 1 is not a function-- which i understand because the .find() method is looking for a callback function and 1 is not a callback function.

I’m calling .find() on my object array named User. Is this wrong? I don’t understand what I’m missing here.

Thanks in advance,

  • Chadd

Hello :slight_smile:

This will be a pretty long post, but hopefully, it will be helpful.


I just want to make sure that by route you mean the name of the parameter. This distinction is important because app.param might be used in multiple routes (if these routes are using the same parameter), for example:

app.param('name_of_the_parameter', function (req, res, next, value_of_the_parameter) {
    // body
});

app.get('/users/:name_of_the_parameter', (req, res) => {
    // body
});

app.put('/users/:name_of_the_parameter', (req, res) => {
    // body
});

requests to both of our routes will come through the app.params, because both routes use name_of_the_parameter parameter.


This error simply means that there are no routes that match the request you made. And this is because in http://localhost:port/user/1 there is an initial slash :slight_smile:


I would like to ask you to read my answer to one of the questions -> FAQ: Router Parameters - router.param(). There are few sentences about find method, which are also applicable here.

Here the situation is pretty similar. We don’t know what User is in the example provided by @axelkaban. From the comment, we know that User represents a model. So data of our application is stored in the external database.

Because we don’t know what User really is, we also have no idea what a find method actually is. So copying code from this example will simply not work.


But we can make your code work.

In your code, User is an array of objects. There is an Array.prototype.find() method which you accidentally used here, but it works differently than the find method in the example. Here you can find reference for Array.prototype.find() method -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find.

So, given that we have an id of the user we want to find, we can do this like so:

let user = User.find(
    (element) => {
        return element.id == id;
    }
);

If there is an element in User that matches id then this element will be assigned to the user variable, if there are no elements with our id then the variable user will be equal to undefined.

In the example provided by @axelkaban we see that if the user is found then we simply assign this value to the req.user and we call next(), but if the user is not found then we call next() with new Error as an argument. We have to do something similar, so this might look like this:

app.param('user', function (req, res, next, id) {
    let user = User.find(
        (element) => {
            return element.id == id;
        }
    );

    if (!user) {
        next(new Error('failed to load user'));
    } else {
        req.user = user;
        next();
    }
});

So our finished code should look like this:

const express = require('express');
const app = express();
const bodyParser = require('body-parser');

app.use(express.static('public'));

const PORT = process.env.PORT || 4001;

const User = [{
        id: 1,
        username: 'test user 01'
    },
    {
        id: 2,
        username: 'test user 02'
    }
];

app.use(bodyParser.json());

app.param('user', function (req, res, next, id) {
    let user = User.find(
        (element) => {
            return element.id == id;
        }
    );

    if (!user) {
        next(new Error('failed to load user'));
    } else {
        req.user = user;
        next();
    }
});

app.get('/users/:user', (req, res) => {
    if (req.user) {
        res.send(req.user);
    }
    // we should also handle the error!
});

app.listen(PORT, () => {
    console.log(`Server is listening on port ${PORT}`);
});

Request to http://localhost:port/user/1 will result in this output:

{"id":1,"username":"test user 01"}

You might notice that for the resource name I have decided to use users instead of user. In REST APIs we usually use plural names for resources.

5 Likes

Hello :slight_smile:

Beautifully explained, thank you!

I’ve seen this exact same example in the Express documentation, a tutorial and a book.

I started to get curious about how .find was working here-- since the only .find that I knew of was the array method (which isn’t the case here, as you explained that .find is a method belonging to something else that isn’t specified).

Google searches didn’t get me any closer to finding an answer on my own but it’s clear now and I understand.

Again, thank you, I really appreciate you :slight_smile:

  • Chadd
1 Like

You are very welcome :slight_smile:


It is a common practice to use fictional methods in the examples. Express might communicate with a wide range of databases, each database might have a different driver with a different interface (set of classes and methods). That is why it is common to use fictional, but plausible methods in the examples.

For me, the find method in this example strongly reminds me of the method findById in the Mongoose:

Using fictional methods might not be the best practice, but it serves a purpose:

  • examples are not strictly linked with a specific database / database driver - which saves us from explaining that this is just an example, you can use a different database;
  • shortens up the example code and allows us to focus on the described topic (like in this example - on app.param itself;
  • because the method is plausible it does not introduce any wrong statements.
1 Like

Thank for the nice example…