Skip to main content

Express Router Middleware

Introduction

Express Router middleware is a powerful feature that helps you organize your Express application by breaking it down into modular, reusable components. Routers act as mini-applications capable of performing middleware and routing functions. This modular approach makes your code more maintainable, especially as your application grows in complexity.

In this tutorial, we'll explore how to use Express Router middleware to structure your applications better, apply middleware to specific routes, and create a more organized codebase.

What is Express Router?

Express Router is a class that helps you create modular, mountable route handlers. Think of it as a "mini-application" that is capable of performing middleware and routing functions. The Router instance is a complete middleware and routing system; for this reason, it is often referred to as a "mini-app".

Creating a router instance is as simple as:

javascript
const express = require('express');
const router = express.Router();

Basic Router Setup

Let's start with a simple example of how to set up and use Express Router:

javascript
// File: routes/users.js
const express = require('express');
const router = express.Router();

// Define routes for the user's resource
router.get('/', (req, res) => {
res.send('Get all users');
});

router.get('/:id', (req, res) => {
res.send(`Get user with ID: ${req.params.id}`);
});

router.post('/', (req, res) => {
res.send('Create a new user');
});

// Export the router
module.exports = router;

Now, in your main application file:

javascript
// File: app.js
const express = require('express');
const usersRouter = require('./routes/users');

const app = express();

// Mount the router on a specific path
app.use('/users', usersRouter);

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

With this setup, when a request comes to /users, it will be handled by the users router. For example:

  • GET /users → "Get all users"
  • GET /users/123 → "Get user with ID: 123"
  • POST /users → "Create a new user"

Router-Level Middleware

One of the key benefits of using Express Router is the ability to define middleware that applies only to routes defined by the router. This allows you to isolate middleware logic to specific parts of your application.

Applying Middleware to All Routes in a Router

Here's how to apply middleware to all routes in a specific router:

javascript
// File: routes/admin.js
const express = require('express');
const router = express.Router();

// Authentication middleware specific to admin routes
const authenticateAdmin = (req, res, next) => {
// Check if user is an admin
if (req.headers.authorization === 'admin-secret-key') {
next(); // User is authenticated, continue to the route handler
} else {
res.status(401).send('Unauthorized: Admin access required');
}
};

// Apply the middleware to all routes in this router
router.use(authenticateAdmin);

router.get('/dashboard', (req, res) => {
res.send('Admin Dashboard');
});

router.get('/users', (req, res) => {
res.send('Admin User Management');
});

module.exports = router;

In the main app:

javascript
// File: app.js
const express = require('express');
const adminRouter = require('./routes/admin');

const app = express();

app.use('/admin', adminRouter);

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

Now, every request to paths starting with /admin will first go through the authenticateAdmin middleware.

Applying Middleware to Specific Routes

You can also apply middleware to specific routes within a router:

javascript
// File: routes/products.js
const express = require('express');
const router = express.Router();

// Middleware to check if product exists
const checkProductExists = (req, res, next) => {
const productId = req.params.id;
// In a real app, you would check your database
if (productId === '1234') {
next(); // Product exists, continue to the route handler
} else {
res.status(404).send('Product not found');
}
};

// Apply middleware only to routes that need it
router.get('/', (req, res) => {
res.send('List of all products');
});

// The middleware only applies to this specific route
router.get('/:id', checkProductExists, (req, res) => {
res.send(`Product details for ID: ${req.params.id}`);
});

router.post('/', (req, res) => {
res.send('Create a new product');
});

module.exports = router;

Nested Routers

You can also nest routers to create more complex route hierarchies:

javascript
// File: routes/api/v1/products.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
res.send('API v1 Products list');
});

module.exports = router;
javascript
// File: routes/api/index.js
const express = require('express');
const v1ProductsRouter = require('./v1/products');

const router = express.Router();

router.use('/v1/products', v1ProductsRouter);

// Add API version middleware
router.use('/v1', (req, res, next) => {
console.log('API v1 request received');
next();
});

module.exports = router;
javascript
// File: app.js
const express = require('express');
const apiRouter = require('./routes/api');

const app = express();

app.use('/api', apiRouter);

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

This creates a route structure like:

  • /api/v1/products → "API v1 Products list"

Real-World Example: Blog API

Let's create a more complete example of a blog API with different routers and middleware:

javascript
// File: middleware/logger.js
function logger(req, res, next) {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
}

module.exports = logger;
javascript
// File: middleware/auth.js
function authenticate(req, res, next) {
const token = req.headers.authorization;
if (token === 'valid-token') {
req.user = { id: 1, name: 'John Doe' };
next();
} else {
res.status(401).send('Authentication required');
}
}

module.exports = authenticate;
javascript
// File: routes/posts.js
const express = require('express');
const authenticate = require('../middleware/auth');

const router = express.Router();

// Get all posts - no authentication required
router.get('/', (req, res) => {
res.json([
{ id: 1, title: 'First Post', body: 'Hello world!' },
{ id: 2, title: 'Express Router', body: 'Router middleware is awesome!' }
]);
});

// Get a specific post - no authentication required
router.get('/:id', (req, res) => {
res.json({ id: req.params.id, title: 'Post Title', body: 'Post content...' });
});

// Creating, updating, and deleting posts require authentication
router.post('/', authenticate, (req, res) => {
res.status(201).json({ message: 'Post created', user: req.user.name });
});

router.put('/:id', authenticate, (req, res) => {
res.json({ message: `Post ${req.params.id} updated`, user: req.user.name });
});

router.delete('/:id', authenticate, (req, res) => {
res.json({ message: `Post ${req.params.id} deleted`, user: req.user.name });
});

module.exports = router;
javascript
// File: routes/comments.js
const express = require('express');
const authenticate = require('../middleware/auth');

const router = express.Router({ mergeParams: true }); // Important for accessing parent router params

// Get comments for a post
router.get('/', (req, res) => {
res.json([
{ id: 1, postId: req.params.postId, text: 'Great post!' },
{ id: 2, postId: req.params.postId, text: 'Thanks for sharing!' }
]);
});

// Add comment to a post - requires authentication
router.post('/', authenticate, (req, res) => {
res.status(201).json({
message: `Comment added to post ${req.params.postId}`,
user: req.user.name
});
});

module.exports = router;
javascript
// File: app.js
const express = require('express');
const logger = require('./middleware/logger');
const postsRouter = require('./routes/posts');
const commentsRouter = require('./routes/comments');

const app = express();

// Apply logger middleware to all routes
app.use(logger);

// Parse JSON request bodies
app.use(express.json());

// Mount the posts router
app.use('/api/posts', postsRouter);

// Mount the comments router under each post
app.use('/api/posts/:postId/comments', commentsRouter);

app.get('/', (req, res) => {
res.send('Welcome to the Blog API');
});

app.listen(3000, () => {
console.log('Blog API server is running on port 3000');
});

With this setup, our API now has these endpoints:

  • GET /api/posts - Get all posts
  • GET /api/posts/:id - Get a specific post
  • POST /api/posts - Create a new post (authentication required)
  • PUT /api/posts/:id - Update a post (authentication required)
  • DELETE /api/posts/:id - Delete a post (authentication required)
  • GET /api/posts/:postId/comments - Get comments for a post
  • POST /api/posts/:postId/comments - Add a comment to a post (authentication required)

Router Parameters

Express Router also allows you to create middleware specific to certain parameters using router.param():

javascript
// File: routes/users.js
const express = require('express');
const router = express.Router();

// Middleware to load user data whenever a userId parameter is detected
router.param('userId', (req, res, next, userId) => {
console.log(`Loading user data for user: ${userId}`);

// In a real app, you would fetch from a database
// This is a simulation
if (userId === '123') {
req.user = {
id: 123,
name: 'Alice',
role: 'admin'
};
next();
} else if (userId === '456') {
req.user = {
id: 456,
name: 'Bob',
role: 'user'
};
next();
} else {
res.status(404).send('User not found');
}
});

// Now any route with :userId will have the user data loaded automatically
router.get('/:userId', (req, res) => {
res.json(req.user);
});

router.get('/:userId/posts', (req, res) => {
res.json({
user: req.user.name,
posts: [`${req.user.name}'s first post`, `${req.user.name}'s second post`]
});
});

module.exports = router;

This is especially useful when you have multiple routes that need the same parameter preprocessing.

Organizing Router Files

As your application grows, you might want to organize your router files in a more structured way. Here's a common pattern:

project/
├── app.js
├── middleware/
│ ├── auth.js
│ └── logger.js
└── routes/
├── index.js
├── users/
│ ├── index.js
│ └── profile.js
├── posts/
│ ├── index.js
│ └── comments.js
└── admin/
├── index.js
└── dashboard.js

The index.js files can serve as aggregators that import and export the appropriate routers:

javascript
// File: routes/index.js
const express = require('express');
const usersRouter = require('./users');
const postsRouter = require('./posts');
const adminRouter = require('./admin');

const router = express.Router();

router.use('/users', usersRouter);
router.use('/posts', postsRouter);
router.use('/admin', adminRouter);

module.exports = router;
javascript
// File: app.js
const express = require('express');
const routes = require('./routes');

const app = express();

app.use('/api', routes);

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

This approach keeps your main app.js file clean and makes it easier to manage your routes as your application scales.

Summary

Express Router middleware is a powerful tool that allows you to:

  1. Create modular and reusable route handlers
  2. Apply middleware to specific routes or groups of routes
  3. Organize your code into logical sections
  4. Build complex API structures with clean code
  5. Handle route parameters consistently across routes

Using Express Router middleware effectively leads to more maintainable and scalable Express applications, especially as they grow in complexity.

Additional Resources and Exercises

Resources

Exercises

  1. Basic Router: Create a simple Express application with separate routers for users, products, and orders.

  2. Authentication Router: Implement a router that handles user registration, login, and profile management with appropriate middleware for authentication.

  3. API Versioning: Create an API with two versions (v1 and v2) using nested routers, where v2 has some enhanced functionality.

  4. Middleware Chain: Build a router with a chain of middleware for a file upload endpoint that validates file types, sizes, and user permissions before allowing the upload.

  5. Error Handling: Extend one of your routers to include error-handling middleware that catches and processes errors appropriately for that specific set of routes.

By mastering Express Router middleware, you'll be able to build more organized, maintainable, and scalable web applications with Node.js and Express.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)