Bit-Doctor
Bit-Doctor

Reputation: 53

ExpressJS 4 middleware problems

I'm trying to wrap my head arround the middleware in ExpressJS 4.

If I understood correctly is that middleware are applied in the order of declaration and that you can "bind" them at different level.

Here I'm trying to bind a middleware at router level.

function middleware(req, res, next) {
  console.log("middleware");
  res.write("middleware");
  next();
}
function handler(req, res) {
 console.log("OK");
 res.status(200).send('OK');
}

const router1 = express.Router();
const router2 = express.Router();

router1.get("/1", handler);
router2.get("/2", handler);

I would except the following to print OK when calling /test/1 and middleware on /test/2.

app.use("/test/", router2.use(middleware), router1);

But the output seems to be inverted and is equivalent to:

app.use("/test/", router2, middleware, router1);

What I really want is that only the first router to use the middleware. In other word scope the use of the middleware to the first controller.

I could easily swap the order of router1 and router2 but my other requirement is because my router2 use in fact a route that catch all requests (/:id) I need to have it last.

What I'm missing here and how can I do what I want ?

EDIT for clarification:

What I ultimely want is something along this:

/
 |-test/
    |-route         // use middleware
    |-something     // use middleware  
    |-another       // use middleware
    ...
    |-:id           // without middleware

That's why I have a router with many routes that are under router1 where I want the middleware. And router2 with a catch-all without the middleware.

Upvotes: 2

Views: 208

Answers (2)

H4ris
H4ris

Reputation: 713

As router1 and router2 are bound to a common route, they will be executed in order, along with their middlewares until one of the routers route matches with the requested path/route.

In your case, as you can't swap the order of the routers, you can create a middleware that wraps the router, checks if the requested path/route exists in the router and if so, returns it, otherwise just skip it.

var unlessMatch = function(router) {
    let routerPaths = [];

    // Retrieve, create a regex and store every route of the Router
    router.stack.forEach(layer => {
        if (layer.route) {
            routerPaths.push(layer.route.path.replace(/\/?(:[^\/]+)(\/?)/g, "/[^\/]+"));
        }
    });

    return function(req, res, next) {
        // Check if requested route exists in the router
        routerPaths.every(path => {
            return new RegExp('^' + path + '(\/)?$').test(req.path) ? router(req, res, next) : true;
        });

        return next();
    };
};

function middleware(req, res, next) {
    console.log("middleware");
    next();
}

function handler(req, res) {
    console.log("OK");
    res.status(200).send('OK');
}

const router1 = express.Router();
const router2 = express.Router();

// Bind middleware to router2
router2.use(middleware);

router1.get("/1", handler);
router2.get("/2", handler);

// Wrap router2 into unlessMatch middleware/function so that router2's middlewares
// are not executed if no route matches with the requested one
app.use("/test/", unlessMatch(router2), router1);

Upvotes: 1

Gergo
Gergo

Reputation: 2290

You can set a middleware when defining the routes:

router1.get("/1", middleware, handler);
router2.get("/2", handler);

The 1st will use the middleware and the second not.

BTW I would suggest the followings: do not create separate router for each route, only one is enough.

const router = express.Router();

router.get("/1", handler);
router.get("/2", handler);

app.use("/test/", router);

Upvotes: 1

Related Questions