A Complete Guide to Express Router

By Hemanta Sundaray on 2022-12-21

Express provides functionality to better organize our code by breaking down a single large file into multiple smaller files - Routers.

You can thik of Routers as mini versions of Express applications.

Note: Router is just a stripped-down version of the const app = express() object.

They provide functionality for handling route matching, requests, and sending responses, but they don’t start a separate server or listen on their own ports.

Let’s learn how to use routers to keep our code clean and our files short.

Consider the following example:

LearnExpress/server.js
const express = require("express");

const app = express();

app.post("users/sign-in", (req, res) => {
  // Logic for logging in a user
});

app.post("users/sign-up", (req, res) => {
  // Logic for registering a user
});

app.get("/posts", (req, res) => {
  res.send("Here are the posts");
});

app.post("/posts", (req, res) => {
  // Save the new post in the database
});

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

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

We have a REST API that manages two types of resources: users & posts. The first two routes manage user authentication and the other two routes manage posts.

Let’s move these routes into their own files.

Inside the LearnExpress folder, create a folder called routes, and inside the routes folder, create two files: userRoutes.js & postRoutes.js.

routes/postRoutes.js

Let’s start with postRoutes.js.

First, let’s import the Express library and create an instance of an Express application. Next, we create an instance of a router by invoking the Router() method on the top-level Express import. A router instance is a complete routing & middleware system.

Then we bring in all post-related routes from the server.js file into this file. Finally, we export the router using module.exports, so that other files can access the postRouter.

LearnExpress/routes/postRoutes.js
const express = require("express");

const app = express();

const postRouter = express.Router();

postRouter.get("/", (req, res) => {
  res.send("Here are the posts.");
});

postRouter.post("/", (req, res) => {
  // Create a new post
});

module.exports = postRouter;

routes/userRoutes.js

We follow the same steps for moving users related routes from server.js into userRoutes.js.

LearnExpress/routes/userRoutes.js
const express = require("express");

const app = express();

const userRouter = express.Router();

userRouter.post("sign-in", (req, res) => {
  // Logic for logging in a user
});

userRouter.post("sign-up", (req, res) => {
  // Logic for registering a user
});

module.exports = userRouter;

We now have an application with two routers: postRouter & userRouter.

To use these routers, we require them in server.js and mount them at certain paths using app.use() and pass them as the second argument.

server.js

The refactored version of the server.js file now looks like the following:

LearnExpress/server.js
const express = require("express");
const postRouter = require("./router/postRoutes");
const userRouter = require("./router/userRoutes");

const app = express();

app.use("/users", userRouter);
app.use("/posts", postRouter);

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

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

The postRouter will now be used for all paths that begin with /posts, and the userRouter will be used for all paths that begin with /users.

Note that inside userRouter, all matching routes are assumed to have /users prepended, as userRouter is mounted at that path. So userRouter.post("/sign-up") matches the full path /users/sign-up. Similarly, inside postRouter, all matching routes are assumed to have /posts prepended, as postRouter is mounted at that path. So postRouter.post("/") matches the full path /posts/.

Let’s understand what happens when a GET request comes for /posts.

The path /posts does not match the path /users in the first app.use(), so the Express server moves on to the next app.use(), which matches /posts. Then Express’s route matching algorithm enters postRouter’s routes to search for full path matches. Since postRouter.post("/") is mounted at /posts, the two paths together match the entire request path /users/sign-in, so the route matches and route handler callback is invoked, sending back the text Here are the posts.

Navigate to the URL http://localhost:5000/posts and we see the response:

Posts Route

We learned how to use the Router class provided by Express to organize routes in different modules and make our app’s main file (server.js) more readable & maintainable.

There are further improvements that we can make to our code.

Notice that all our route handler functions in both userRouter & postRouter are anonymous functions.

Note: An anonymous function is a function without a name.

LearnExpress/routes/postRoutes.js
const express = require("express");

const app = express();

const postRouter = express.Router();

postRouter.get("/", (req, res) => {
  res.send("Here are the posts.");
});

postRouter.post("/", (req, res) => {
  // Create a new post
});

module.exports = postRouter;

The problem with anonymous functions is that we can use them only in the place we have defined them. Named functions, on the other hand, can be defined once and used in multiple places. The best practice is to abstract named functions into external modules based on the type of resource or functionality they manage.

Inside the LearnExpress folder, create a folder called controllers, and inside controllers create two files: userController.js & postController.js.

Our current folder structure looks like the following:

Controllers

controllers/userController.js

Copy the following code snippet in the userController.js file.

LearnExpress/controllers/userController.js
export const signIn = (req, res) => {
  // Logic for logging in a user
};

export const signUp = (req, res) => {
  // Logic for registering a user
};

controllers/postController.js

And copy the following code snippet in postController.js.

LearnExpress/controllers/postController.js
export const posts = (req, res) => {
  res.send("Here are the posts.");
};

export const newPost = (req, res) => {
  // Create a new post
};

The refactored versions of userRoutes.js & postRoutes.js look like the following:

LearnExpress/routes/postRoutes.js
const express = require("express");
const { signIn, signUp } = require("../controllers/userController");

const app = express();

const userRouter = express.Router();

userRouter.post("sign-in", signIn);

userRouter.post("sign-up", signUp);

module.exports = userRouter;
LearnExpress/routes/userRoutes.js
const express = require("express");
const { posts, newPost } = require("../controllers/postController");

const app = express();

const postRouter = express.Router();

postRouter.get("/", posts);

postRouter.post("/", newPost);

module.exports = postRouter;

As you can see, our route definitions are now much more cleaner and readable.

Join the Newsletter