Express.js Middleware
Middleware in Express.js are functions that execute during the lifecycle of a request to the server. They can modify the request and response objects, end the request-response cycle, and call the next middleware function. This guide covers key concepts, types of middleware, examples, and best practices for using middleware in Express.js.
Key Concepts of Express.js Middleware
- Middleware Function: A function that has access to the request and response objects, and the next middleware function in the application’s request-response cycle.
- next Function: A function that, when called, executes the next middleware function in the stack.
- Application-Level Middleware: Middleware bound to an instance of the app object using
app.use()
orapp.METHOD()
. - Router-Level Middleware: Middleware bound to an instance of the express.Router() class.
- Error-Handling Middleware: Middleware that takes four arguments and is used for handling errors.
- Built-In Middleware: Middleware included with Express.js, such as
express.static
,express.json
, andexpress.urlencoded
. - Third-Party Middleware: Middleware provided by third-party libraries, available via npm.
Creating and Using Middleware
Create middleware functions and use them in your application with app.use()
or app.METHOD()
:
Example: Basic Middleware
// basic-middleware.js
const express = require('express');
const app = express();
const port = 3000;
// Middleware function
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // Call the next middleware function
};
// Use middleware
app.use(logger);
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Application-Level Middleware
Bind middleware to an instance of the app object:
Example: Application-Level Middleware
// app-level-middleware.js
const express = require('express');
const app = express();
const port = 3000;
// Middleware functions
const requestTime = (req, res, next) => {
req.requestTime = Date.now();
next();
};
const logger = (req, res, next) => {
console.log(`[${new Date(req.requestTime).toISOString()}] ${req.method} ${req.url}`);
next();
};
// Use middleware
app.use(requestTime);
app.use(logger);
app.get('/', (req, res) => {
res.send(`Hello, World! Request received at ${req.requestTime}`);
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Router-Level Middleware
Bind middleware to an instance of the express.Router()
class:
Example: Router-Level Middleware
// router-middleware.js
const express = require('express');
const app = express();
const router = express.Router();
const port = 3000;
// Middleware function
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
};
// Use middleware in the router
router.use(logger);
router.get('/', (req, res) => {
res.send('Home Page');
});
router.get('/about', (req, res) => {
res.send('About Page');
});
// Use the router
app.use('/', router);
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Error-Handling Middleware
Error-handling middleware takes four arguments: (err, req, res, next)
. Define error-handling middleware last, after other app.use() and routes calls:
Example: Error-Handling Middleware
// error-handling.js
const express = require('express');
const app = express();
const port = 3000;
// Middleware function to simulate an error
const simulateError = (req, res, next) => {
const err = new Error('Something went wrong!');
err.status = 500;
next(err); // Pass the error to the next middleware
};
// Error-handling middleware
const errorHandler = (err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500);
res.send(`Error: ${err.message}`);
};
// Use middleware
app.use(simulateError);
app.use(errorHandler);
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Built-In Middleware
Express.js includes built-in middleware functions, such as express.static
, express.json
, and express.urlencoded
:
Example: Using Built-In Middleware
// built-in-middleware.js
const express = require('express');
const path = require('path');
const app = express();
const port = 3000;
// Use built-in middleware
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.post('/submit', (req, res) => {
res.send(`Form Submitted: ${req.body.name}`);
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Third-Party Middleware
Use third-party middleware available via npm, such as morgan
for logging, body-parser
for parsing request bodies, and cors
for enabling Cross-Origin Resource Sharing:
Example: Using Third-Party Middleware
// third-party-middleware.js
const express = require('express');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
const port = 3000;
// Use third-party middleware
app.use(morgan('combined'));
app.use(bodyParser.json());
app.use(cors());
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Best Practices for Using Middleware
- Use Middleware Functions: Leverage middleware for common tasks such as logging, authentication, error handling, and data parsing.
- Order Matters: The order in which middleware is defined matters, as it affects the request-response cycle.
- Modularize Middleware: Organize middleware functions into separate modules for better maintainability and readability.
- Handle Errors Properly: Implement error-handling middleware to manage runtime exceptions and send appropriate responses.
- Security: Use security-related middleware to protect your application from common vulnerabilities, such as
helmet
for setting HTTP headers.
Testing Middleware
Test your middleware functions using frameworks like Mocha, Chai, and Supertest:
Example: Testing Middleware with Mocha, Chai, and Supertest
// Install Mocha, Chai, and Supertest
// npm install --save-dev mocha chai supertest
// test/middleware.test.js
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const express = require('express');
const app = express();
// Middleware function
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
};
// Use middleware
app.use(logger);
app.get('/', (req, res) => {
res.send('Hello, World!');
});
describe('GET /', () => {
it('should respond with "Hello, World!"', (done) => {
request(app)
.get('/')
.expect('Content-Type', /text\/html/)
.expect(200, 'Hello, World!', done);
});
});
// Define test script in package.json
// "scripts": {
// "test": "mocha"
// }
// Run tests with NPM
// npm run test
Key Points
- Middleware Function: A function that has access to the request and response objects, and the next middleware function in the application’s request-response cycle.
- next Function: A function that, when called, executes the next middleware function in the stack.
- Application-Level Middleware: Middleware bound to an instance of the app object using
app.use()
orapp.METHOD()
. - Follow best practices for using middleware, such as leveraging middleware for common tasks, organizing middleware into separate modules, handling errors properly, and using security-related middleware to protect your application.
Conclusion
Middleware in Express.js are functions that execute during the lifecycle of a request to the server. By understanding and implementing the key concepts, types of middleware, examples, and best practices covered in this guide, you can effectively use middleware in your Express.js applications. Happy coding!