Building production‑ready Express applications requires more than just routes.
This tutorial covers advanced topics: error handling, asynchronous patterns,
modular architecture, environment configuration, and request validation.
You'll learn to structure applications that are maintainable and scalable.
Why this matters:
Real‑world applications need to handle errors gracefully, manage complexity,
and be secure. These advanced techniques are essential for professional
backend development.
1. Error Handling
Error handling middleware has four parameters: `(err, req, res, next)`.
// Global error handler (place after all routes)
app.use((err, req, res, next) => {
console.error(err.stack);
const statusCode = err.statusCode || 500;
const message = err.message || 'Internal server error';
res.status(statusCode).json({
error: message,
timestamp: new Date().toISOString(),
path: req.url,
});
});
// Throwing errors in route handlers
app.get('/users/:id', (req, res, next) => {
const user = findUser(req.params.id);
if (!user) {
const err = new Error('User not found');
err.statusCode = 404;
return next(err); // Pass to error handler
}
res.json(user);
});
What is the signature of an Express error-handling middleware?
(req, res, next)
(err, req, res, next)
(err, res, req, next)
(req, res, err, next)
Show answer
B. (err, req, res, next).
Question 2
Which package helps manage environment variables?
dotenv
env
config
environment
Show answer
A. dotenv.
Question 3
What is the purpose of the `express-async-errors` package?
To handle synchronous errors
To automatically catch errors from async route handlers
To improve performance
To add more middleware
Show answer
B. To automatically catch errors from async route handlers.
Exercises
Exercise 1
Add a global error handler to your Product API from the previous tutorial.
It should log the error and return a JSON response with `error`, `timestamp`, and `path`
fields.
Sample answer
app.use((err, req, res, next) => {
console.error(err.stack);
const status = err.statusCode || 500;
res.status(status).json({
error: err.message || 'Internal server error',
timestamp: new Date().toISOString(),
path: req.url,
});
});
Exercise 2
Refactor the Product API to use a modular structure: separate routes,
controllers, and services. Create a `ProductService` that handles the in‑memory data
operations.
Extend the User Management API with validation using Joi. Validate name
(required, min 2 chars), email (required, valid format), and age (optional, number > 0). Add
proper error handling.
Environment variables for configuration (port, etc.)
Use in‑memory storage (an array)
Sample outline
Structure: `src/` with `routes/`, `controllers/`, `services/`,
`middlewares/`, `config/`.
Service: `tasks.service.js` with CRUD operations.
Controller: `tasks.controller.js` that calls the service.
Middleware: `validateTask.js` using Joi.
Error Handler: Global error handler.
Tutorial Summary
You learned advanced Express patterns: global error handling, asynchronous
patterns, modular project structure, environment configuration, and
request validation. These techniques are essential for building
production‑ready, maintainable backend applications.
Key takeaway: Structure your code for scalability and
maintainability. Handle errors gracefully and validate inputs early.