Express Architecture
Express.js is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. Understanding its architecture is fundamental to becoming proficient with Express development.
Introduction
Express.js follows a middleware-based architecture where a request flows through a pipeline of functions (middleware) before generating a response. This architecture makes Express both simple to understand and highly extensible. As a beginner, grasping this architecture will help you build well-structured applications that are easier to maintain and scale.
Core Architectural Components
1. Application Object
At the center of any Express application is the application object, typically created by calling the express()
function:
const express = require('express');
const app = express();
The application object is responsible for:
- Configuring application settings
- Registering middleware
- Defining routes
- Starting the HTTP server
2. Request-Response Cycle
Express is built around the HTTP request-response cycle:
- Client sends an HTTP request to the server
- Express receives the request
- The request passes through middleware functions
- A route handler generates a response
- The response is sent back to the client
This cycle forms the foundation of all Express applications.
Middleware Architecture
Middleware functions are the heart of Express. They have access to:
- The request object (
req
) - The response object (
res
) - The next middleware function in the cycle (
next
)
How Middleware Works
Middleware functions execute sequentially in the order they are added to the application. Each middleware can:
- Execute any code
- Modify the request and response objects
- End the request-response cycle
- Call the next middleware in the stack
app.use((req, res, next) => {
console.log('Time:', Date.now());
next(); // Pass control to the next middleware
});
app.use((req, res, next) => {
console.log('Request URL:', req.originalUrl);
next();
});
app.get('/', (req, res) => {
res.send('Hello World!');
});
In this example, when a request is made to the root route '/':
- The first middleware logs the timestamp
- The second middleware logs the request URL
- The route handler responds with "Hello World!"
Types of Middleware
- Application-level middleware - Bound to the app object using
app.use()
orapp.METHOD()
- Router-level middleware - Bound to a router instance
- Error-handling middleware - Takes four arguments:
(err, req, res, next)
- Built-in middleware - Express's included middleware like
express.static
- Third-party middleware - External modules like
morgan
,body-parser
, etc.
Routing Architecture
Express provides a sophisticated routing system that allows you to:
- Define routes for different HTTP methods
- Split routes across multiple files
- Use route parameters to capture values from the URL
Basic Routing
// Simple route
app.get('/hello', (req, res) => {
res.send('Hello, Express!');
});
// Route with parameters
app.get('/users/:userId', (req, res) => {
res.send(`User ID: ${req.params.userId}`);
});
// Route with multiple handlers
app.get('/complex',
(req, res, next) => {
// First handler
console.log('First handler');
next();
},
(req, res) => {
// Second handler
res.send('Complex route handled!');
}
);
Router Object
For more complex applications, Express provides a Router
object to create modular route handlers:
// userRoutes.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.send('All users');
});
router.get('/:id', (req, res) => {
res.send(`User: ${req.params.id}`);
});
module.exports = router;
// app.js
const userRoutes = require('./userRoutes');
app.use('/users', userRoutes);
With this setup, requests to /users
will be handled by the first route handler, and requests to /users/123
will be handled by the second route handler.
Application Structure
While Express doesn't force a specific structure, there are common patterns that many developers follow:
Simple Structure
my-express-app/
├── node_modules/
├── public/ # Static assets
├── routes/ # Route handlers
├── views/ # Templates
├── app.js # Main application file
└── package.json
MVC Structure
For larger applications, an MVC (Model-View-Controller) pattern is common:
my-express-app/
├── node_modules/
├── public/
├── app/
│ ├── controllers/ # Route logic
│ ├── models/ # Data models
│ ├── views/ # Templates
│ └── middleware/ # Custom middleware
├── config/ # Configuration files
├── routes/ # Route definitions
├── app.js # Main application file
└── package.json
Practical Example: Building a RESTful API
Let's apply these concepts to build a simple RESTful API:
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
// Middleware
app.use(bodyParser.json());
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});
// Mock database
const books = [
{ id: 1, title: 'Express.js Fundamentals', author: 'Jane Doe' },
{ id: 2, title: 'Node.js Design Patterns', author: 'John Smith' }
];
// Routes
app.get('/api/books', (req, res) => {
res.json(books);
});
app.get('/api/books/:id', (req, res) => {
const book = books.find(b => b.id === parseInt(req.params.id));
if (!book) return res.status(404).send('Book not found');
res.json(book);
});
app.post('/api/books', (req, res) => {
const book = {
id: books.length + 1,
title: req.body.title,
author: req.body.author
};
books.push(book);
res.status(201).json(book);
});
// Error handling middleware (must be last)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
This example demonstrates:
- Middleware for parsing request bodies and logging
- Route handlers for GET and POST requests
- Error handling middleware
- A structured approach to building a RESTful API
Best Practices
- Modularize your code: Split routes, models, and controllers into separate files
- Use middleware effectively: Apply middleware only where needed
- Handle errors properly: Include error-handling middleware
- Follow RESTful conventions: For clean, predictable APIs
- Use environment variables: For configuration that changes between environments
Summary
Express.js architecture is centered around:
- The application object that sets up and configures the app
- Middleware functions that process requests in a pipeline
- Routing system for directing requests to appropriate handlers
- Request and response objects that provide HTTP functionality
This middleware-based architecture makes Express both powerful and flexible, allowing you to build everything from simple APIs to complex web applications.
Additional Resources
- Official Express Documentation
- Express Middleware Guide
- Express Application Generator
- Express API Reference
Exercises
-
Create a simple Express application with at least three custom middleware functions that log different information about incoming requests.
-
Build a RESTful API for a "todo" list with endpoints for creating, reading, updating, and deleting tasks.
-
Refactor an Express application to use the Router object for modular routing.
-
Create an Express application that serves static files and implements basic authentication using middleware.
If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)