Express.js: Routes and Middlewares in Practice
Understanding the role of routes and middleware in Express.js applications.


Jhonatas Matos
When I started studying Express.js, I quickly realized that mastering routes and middlewares was the key to building robust APIs. Here’s how I structured my learning, with real examples I’ve used in projects.
Why Express.js?
Express.js is a minimalist web framework for Node.js that simplifies the process of building web applications and APIs.
✔ It’s the most widely used Node.js framework ✔ Middleware architecture lets me add features modularly ✔ It doesn’t force rigid structures
Setting Up a Basic Express.js Application
To get started, I set up a simple Express.js application. Here’s how I did it:
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
This code initializes an Express.js application, sets up a basic route, and starts the server on port 3000.
Modularizing Routes
To keep my application organized, I learned to modularize routes. Here’s how I structured my routes:
const express = require('express');
const router = express.Router();
const usersController = require('../controllers/usersController');
// Define routes for user operations
router.get('/', usersController.getAllUsers);
router.post('/', usersController.createUser);
router.get('/:id', usersController.getUserById);
router.put('/:id', usersController.updateUser);
router.delete('/:id', usersController.deleteUser);
module.exports = router;
In this example, I created a separate router for user-related operations. This modular approach helps keep my code clean and maintainable. I then imported this router into my main application file:
const express = require('express');
const app = express();
const usersRouter = require('./routes/users');
app.use('/users', usersRouter); // Mount the users router
This way, all user-related routes are handled by the usersRouter
, making it easier to manage and scale my application.
Defining Routes
I learned to define routes using the app.METHOD(PATH, HANDLER)
syntax. Here’s an example:
app.get('/users', (req, res) => {
res.send('User list');
});
app.post('/users', (req, res) => {
res.send('User created');
});
In this example, I created two routes: one for retrieving a list of users and another for creating a new user.
Route Parameters
Express.js allows me to define dynamic routes using route parameters. Here’s how I used them:
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.send(`User details for user ID: ${userId}`);
});
In this case, :id
is a route parameter that captures the user ID from the URL, allowing me to handle requests for specific users.
HTTP Methods
Express.js supports all standard HTTP methods, which I found essential for building RESTful APIs. Here’s a quick overview of the methods I used:
- GET: Retrieve data from the server (e.g., get user details)
- POST: Send data to the server (e.g., create a new user)
- PUT: Update existing data on the server (e.g., update user information)
- DELETE: Remove data from the server (e.g., delete a user)
Official docs: HTTP Methods
By using these methods appropriately, I was able to create a RESTful API that adheres to standard conventions.
Middlewares
Middlewares are functions that execute during the request-response cycle. They can modify the request, response, or end the request-response cycle.
It`s possible to build a simple logger to understand the flow:
// My custom logger
const myLogger = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
next(); // This 'next' was a game-changer!
};
app.use(myLogger); // Global application
This middleware logs the request method and path, helping me debug and understand the flow of requests in my application.
The Error Handler That Saved Me
One of the most crucial middlewares I implemented was the error handler. It catches errors and sends a standardized response to the client. Error middleware needs to come after all routes
// ... all routes and routers
app.use((err, req, res, next) => {
console.error('Unhandled error:', err);
res.status(500).json({ error: 'Something broke :(' });
});
Recommended Video: Express Middleware Explained
This middleware catches any unhandled errors in the application, logs them, and sends a 500 Internal Server Error response to the client.
Project Structure
To keep my Express.js application organized, I followed a modular structure. Here’s how I structured my project:
├── src/
│ ├── controllers/ # Controllers for handling business logic
│ ├── middlewares/ # Custom middlewares
│ ├── models/ # Data models
│ └── routes/ # API routes
└── app.js # Main application file
This structure allowed me to separate concerns, making it easier to maintain and scale my application.
Key Lessons Learned
- Middlewares are like LEGO – Snap features together
- Keep routes thin – Move complex logic to services
- Handle errors early – Saves debugging hours
Advanced Resources:
Practical Exercise
Link to GitHub project
Conclusion
Mastering routes and middlewares in Express.js has been a game-changer for everyone who works on web development projects. By understanding how to structure routes and implement middlewares effectively, I’ve been able to build robust and maintainable applications. If you’re new to Express.js, I highly recommend diving into these concepts. They will significantly enhance your ability to create powerful web applications.