Express.js: Routes and Middlewares in Practice

Understanding the role of routes and middleware in Express.js applications.

Express.js: Routes and Middlewares in Practice
Jhonatas Matos

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

  1. Middlewares are like LEGO – Snap features together
  2. Keep routes thin – Move complex logic to services
  3. Handle errors early – Saves debugging hours

Advanced Resources:

Practical Exercise

Link to GitHub projectGitHub Octocat

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.