Express.js Mastery / Express: Letโ€™s Start Building The Natours API!

Creating and Mounting Multiple Routers

๐Ÿ“– Context

The ultimate goal is to split the current single file into multiple files:

  • ๐Ÿ“„ One file for tour routes

  • ๐Ÿ“„ One file for user routes

  • ๐ŸŽฎ One file for tour handlers/controllers

  • ๐ŸŽฎ One file for user handlers/controllers

Before that split can happen, a separate router needs to be created for each resource (tours and users). This lecture covers how to create and mount these routers โ€” a bit more advanced, but a key step toward better file organization.


๐Ÿ”น Step 1: Understanding the Current Setup

  • Right now, all four routes (2 for tours, 2 for users) are defined on the same router โ€” which is really the app object itself.

  • To eventually separate tour routes from user routes into different files, each resource needs its own router.


๐Ÿ”น Step 2: Creating a Router for Tours

const tourRouter = express.Router();

This creates a new router instance, saved into a variable. It's used in place of app for the tour-specific routes:

tourRouter
  .route('/')
  .get(getAllTours)
  .post(createTour);

tourRouter
  .route('/:id')
  .get(getTour)
  .patch(updateTour)
  .delete(deleteTour);

๐Ÿ’ก Why the Route Paths Changed

  • Previously these were /api/v1/tours and /api/v1/tours/:id.

  • Now they're simplified to just / and /:id.

  • This is because tourRouter will only ever be reached after a request already matches /api/v1/tours โ€” so within the router, / effectively means /api/v1/tours, and /:id means /api/v1/tours/:id.

  • Think of tourRouter as a small sub-application (mini app) โ€” its own internal routes are relative to wherever it gets mounted.


๐Ÿ”น Step 3: Mounting the Router (Connecting It to the App)

app.use('/api/v1/tours', tourRouter);

๐Ÿ’ก Why this works

  • A router created via express.Router() is itself a genuine middleware function.

  • app.use() is used to apply that middleware โ€” but only for requests matching the specified URL (/api/v1/tours).

  • This process โ€” attaching a router to a specific base URL โ€” is called mounting.

๐Ÿ”„ How a Request Flows Through This

  1. A request comes in for /api/v1/tours/:id (e.g. a specific tour).

  2. It travels through the middleware stack.

  3. When it reaches app.use('/api/v1/tours', tourRouter), the URL matches, so tourRouter (the sub-app) takes over.

  4. Inside tourRouter, the remaining part of the URL (/:id) is matched against the router's own routes.

  5. The corresponding handler function runs based on the HTTP method used.


๐Ÿ”น Step 4: Doing the Same for Users

const userRouter = express.Router();

userRouter
  .route('/')
  .get(getAllUsers)
  .post(createUser);

userRouter
  .route('/:id')
  .get(getUser)
  .patch(updateUser)
  .delete(deleteUser);
  
  app.use('/api/v1/users', userRouter);

โš ๏ธ Important Ordering Note

  • Mounting a router (app.use(...)) must come after the router itself has been declared (const userRouter = express.Router()) โ€” a variable can't be used before it's defined.

๐Ÿ”„ Same Request-Flow Logic Applies

  • ๐Ÿ‘ค A request to /api/v1/users/:id matches the mount point /api/v1/users, enters userRouter, and then matches /:id inside that sub-application.


๐Ÿ”น Step 5: Testing

  • โœ… Sending a request for all tours โ€” still works correctly.

  • โœ… Sending a request for all users โ€” still works correctly.

๐ŸŽ‰ This confirms both routers are correctly created and mounted, and the refactor didn't break any existing functionality.


โœ… Key Takeaway

  • express.Router() creates a router for a specific resource.

  • A router is middleware and is mounted using app.use().

  • Routes inside the router are relative to the mount path.

  • Create the router before mounting it.

  • This setup prepares the project for separating routes and controllers into different files.


โžก๏ธ What's Next

  • ๐Ÿ“‚ Separating the tour router, user router, and their respective controller functions into their own individual files.