Node.js: 8 Best practices for Express middlewares

Mayank Choubey
Tech Tonic
4 min readMay 7, 2024

--

In this next article of the beginner best practices, we’ll discuss the best practices of writing and using middlewares in Express. Without further delay, let’s get started.

Introduction

Middleware functions are a fundamental concept in web development, particularly in frameworks like Express.js. They are small, reusable functions that sit between the request and response cycle, allowing developers to manipulate and transform the data in various ways. Middleware functions can perform a wide range of tasks, such as authentication, rate limiting, logging, and response modification. They are typically used to decouple application logic from the main request handler, making the code more modular, flexible, and maintainable.

Middleware functions are executed in a sequence, allowing developers to chain multiple functions together to create a customized processing pipeline. Each middleware function has access to the request and response objects, as well as the next function in the chain, enabling it to modify the data or pass control to the next function. This architecture enables developers to build robust, scalable, and efficient web applications that can handle complex tasks with ease.

Now that we have a basic introduction of middlewares, let’s get started with the 8 best practices.

Best Practice 1: Keep middleware functions small and focused

Keeping middleware functions small and focused is essential for maintainable and efficient code. A single responsibility principle should be applied to each middleware function, meaning it should perform only one specific task. This approach makes it easier to test, debug, and reuse middleware functions across different routes and applications. When middleware functions are small and focused, they are less prone to errors and are more efficient in terms of performance.

An example is as follows:

// Good: Single responsibility
app.use((req, res, next) => {
console.log('Request received:', req.url);
next();
});

// Bad: Multiple responsibilities
app.use((req, res, next) => {
console.log('Request received:', req.url);
if (req.url === '/protected' && req.query.password !== 'secret') {
return res.status(401).send('Unauthorized');
}
next();
});

Best Practice 2: Use next() to pass control

Using next() to pass control to the next middleware or route handler is crucial for proper middleware chaining. Failing to call next() can result in the request being stuck in the current middleware, causing performance issues and memory leaks. Always calling next() ensures that the middleware chain is executed correctly, and the control is passed to the next middleware or route handler. This approach also allows for flexible and dynamic middleware chaining, enabling you to easily add or remove middleware functions as needed.

An example is shown below:

app.use((req, res, next) => {
console.log('Request received:', req.url);
next(); // Pass control to the next middleware
});

Best Practice 3: Handle errors gracefully

Handling errors gracefully is critical in middleware functions to prevent application crashes and ensure a seamless user experience. Using try-catch blocks and passing errors to the next error-handling middleware using next(err) enables you to catch and handle errors efficiently. This approach allows you to centralize error handling and provide a uniform error response across the application. Additionally, it enables you to log and analyze errors, making it easier to identify and fix issues.

app.use((req, res, next) => {
try {
// Code that might throw an error
} catch (err) {
next(err); // Pass error to the next error-handling middleware
}
});

Best Practice 4: Use return to short-circuit

Using return to short-circuit the middleware chain is essential for optimizing performance and reducing unnecessary processing. When a middleware function completes its task, it should return immediately, preventing further unnecessary processing. This approach reduces latency and improves overall application performance. Additionally, it enables you to implement authentication, rate limiting, and other security measures more effectively.

An example is shown below:

app.use((req, res, next) => {
if (req.url === '/protected' && req.query.password !== 'secret') {
return res.status(401).send('Unauthorized'); // Short-circuit the chain
}
next();
});

Best Practice 5: Use middleware order wisely

The order of middleware functions is crucial, as it determines the sequence of execution. Placing middleware functions in the correct order ensures that they execute correctly and efficiently. Typically, authentication and rate limiting middleware should be placed early in the chain, followed by logging and other middleware functions. This approach ensures that critical security and performance measures are executed first, followed by secondary functions.

An example is as follows:

app.use(authenticationMiddleware);
app.use(rateLimitingMiddleware);
app.use(loggerMiddleware);
app.use(responseModifierMiddleware);

Best Practice 6: Use named functions

Using named functions for middleware makes them easier to identify and debug. Named functions provide a clear indication of their purpose, making it easier to understand the code. Additionally, named functions enable you to reuse middleware functions across different routes and applications, promoting modularity and separation of concerns.

A simple example is shown below:

function loggerMiddleware(req, res, next) {
console.log('Request received:', req.url);
next();
}

app.use(loggerMiddleware);

Best Practice 7: Avoid using app.use() inside route handlers

Using app.use() inside route handlers can lead to confusion and complexity. Middleware functions should be separate from route handlers to maintain a clear separation of concerns. Mixing middleware and route handlers can make the code difficult to understand and maintain.

An example:

// Good: Separate middleware and route handlers
app.use(loggerMiddleware);
app.get('/', (req, res) => {
res.send('Hello from Express!');
});

// Bad: Mixing middleware and route handlers
app.get('/', (req, res) => {
app.use(loggerMiddleware); // Avoid using app.use() inside route handlers
res.send('Hello from Express!');
});

Best Practice 8: Use app.use() for middleware, not route handlers

Using app.use() only for middleware functions ensures that middleware and route handlers are separate and distinct. This approach enhances modularity and separation of concerns, making it easier to understand and maintain the code. Additionally, it enables you to reuse middleware functions across different routes and applications.

// Good: Using app.use() for middleware
app.use(loggerMiddleware);

// Bad: Using app.use() for route handlers
app.use((req, res) => {
res.send('Hello from Express!'); // Should be a separate route handler
});

That’s all about it! I hope this has been a good use of your time.

--

--