Error Handling in Node.js with Express

Proper error handling is crucial for building stable and user-friendly APIs. In this section, we’ll learn how to handle:

  • Invalid routes (404 errors)

  • Controller-level application errors (e.g., user not found)

  • Centralized error response handling (using middleware)


1. Handling Unknown Routes (404 Errors)

When a user hits a route that doesn’t exist (e.g., /api/user/signup which hasn’t been defined), we should return a proper 404 Not Found error.

Let’s add this functionality in our Server.ts:

sh
// server.ts (only relevant part shown)
export class Server {
constructor() {
this.setConfigurations();
this.setRoutes();
this.error404Handler(); // Always keep this after all routes
this.handleErrors(); // Global error handler (must come last)
}
error404Handler() {
this.app.use((req, res) => {
res.status(404).json({
message: ‘Not Found’,
status_code: 404
});
});
}
}

This ensures that any undefined routes return a structured 404 response, improving the API’s robustness.


2. Avoiding Repetitive Code in Controllers

Let’s look at this login method in the UserController:

sh
// UserController.ts (inefficient approach)
export class UserController {
static login(req, res) {
res.status(422).json({
message: ‘Username and Password do not match’,
status_code: 422
});
}
}

This works, but it’s not DRY. Repeating the same response structure for every error is inefficient and unmaintainable.


3. Centralized Error Handling with Middleware

Instead of returning errors directly from controllers, we throw or forward them using next() and catch them in a global error handler:

sh
// server.ts
handleErrors() {
this.app.use((error, req, res, next) => {
const errorStatus = req.errorStatus || 500;
res.status(errorStatus).json({
message: error.message || ‘Something went wrong. Please try again later.’,
status_code: errorStatus
});
});
}

Now update the controller to use next():

sh
// UserController.ts
export class UserController {
static login(req, res, next) {
const error = new Error(‘User does not exist’);
(error as any).errorStatus = 422; // Optional custom status
next(error);
}
}

This approach allows consistent formatting of all error responses via one middleware.


4. Demonstrating Control Flow

You can even demonstrate control flow based on conditions using next():

sh
// UserController.ts
export class UserController {
static login(req, res, next) {
const error = new Error(‘User does not exist’);
return next(error); // Will trigger global error handler
}
static test(req, res) {
console.log(‘Test route called’);
res.send(‘OK’);
}
}

If you call next(error), it goes to error middleware.
If you call just next(), it moves to the next route or middleware.


Final Project Structure (So Far)

sh
nodejs-project/
├── node_modules/
├── src/
│ ├── controllers/
│ │ └── UserController.ts
│ ├── environments/
│ │ ├── dev.env.ts
│ │ ├── env.ts
│ │ └── prod.env.ts
│ ├── routes/
│ │ └── UserRouter.ts
│ ├── index.ts
│ ├── server.ts
├── package.json
├── package-lock.json
├── tsconfig.json

Sample Files Recap

✅ UserController.ts

sh
[
export class UserController {
static login(req, res, next) {
const error = new Error(‘User does not exist’);
(error as any).errorStatus = 422;
next(error);
}
}

✅ Server.ts (Error-Related Snippets)

sh
export class Server {
// Inside constructor
this.setConfigurations();
this.setRoutes();
this.error404Handler();
this.handleErrors();
handleErrors() {
this.app.use((error, req, res, next) => {
const errorStatus = (error as any).errorStatus || 500;
res.status(errorStatus).json({
message: error.message || ‘Something went wrong. Please try again later.’,
status_code: errorStatus
});
});
}
error404Handler() {
this.app.use((req, res) => {
res.status(404).json({
message: ‘Not Found’,
status_code: 404
});
});
}
}

✅ Testing the Error Handling

Try hitting:

  • /api/user/login → returns User does not exist (custom 422 error)

  • /api/user/signup → returns Not Found (generic 404 error)


Summary

By using centralized error handling, we:

  • Improve code readability and maintainability

  • Follow DRY (Don’t Repeat Yourself) principles

  • Deliver consistent API error responses

  • Prevent application crashes and undefined behaviors

Scroll to Top