Custom error handling in a REST API using TypeScript, Express.js, Joi validation, and object-oriented programming

RMAG news

Hi, Devs!

Building a robust and maintainable REST API requires careful consideration of error handling, validation, and structure. In this article, we’ll explore how to set up a custom error handling mechanism in a REST API using TypeScript, Express.js, Joi for validation, and principles of object-oriented programming (OOP). We’ll structure our project as follows:

Scructure:

—src
—–middlewares
——–ErrorHanlder.ts
—–models
——–User.ts
—–repository
——–UserRepository.ts
—–routes
——–UserRouter.ts
—–controllers
——–UserController.ts
—–services
——–UserService.ts
—–validations
——–UserValidation.ts
—app.ts
—server.ts

Setting Up the Project

mkdir rest-api
cd rest-api
npm init -y
npm install express typescript @types/node @types/express ts-node-dev
npx tsc –init

Middleware for Error Handling

// src/middlewares/ErrorHandler.ts
import { Request, Response, NextFunction } from ‘express’;

class ErrorHandler extends Error {
statusCode: number;
message: string;

constructor(statusCode: number, message: string) {
super();
this.statusCode = statusCode;
this.message = message;
}
}

const handleError = (err: ErrorHandler, req: Request, res: Response, next: NextFunction) => {
const { statusCode, message } = err;
res.status(statusCode).json({
status: ‘error’,
statusCode,
message,
});
};

export { ErrorHandler, handleError };

User Model

// src/models/User.ts
export interface User {
id: number;
name: string;
email: string;
}

User Repository

// src/repository/UserRepository.ts
import { User } from ‘../models/User’;

class UserRepository {
private users: User[] = [];

findAll(): User[] {
return this.users;
}

findById(id: number): User | undefined {
return this.users.find(user => user.id === id);
}

create(user: User): User {
this.users.push(user);
return user;
}
}

export default new UserRepository();

User Service

// src/services/UserService.ts
import UserRepository from ‘../repository/UserRepository’;
import { User } from ‘../models/User’;
import { ErrorHandler } from ‘../middlewares/ErrorHandler’;

class UserService {
getAllUsers(): User[] {
return UserRepository.findAll();
}

getUserById(id: number): User | undefined {
const user = UserRepository.findById(id);
if (!user) {
throw new ErrorHandler(404, ‘User not found’);
}
return user;
}

createUser(user: User): User {
const { error } = userSchema.validate(user);
if (error) {
throw new ErrorHandler(400, error.details[0].message);
}
return UserRepository.create(user);
}
}

export default new UserService();

User Validation

// src/validations/UserValidation.ts
import Joi from ‘joi’;

const userSchema = Joi.object({
id: Joi.number().required(),
name: Joi.string().required(),
email: Joi.string().email().required(),
});

export { userSchema };

User Controller

// src/controllers/UserController.ts
import { Request, Response, NextFunction } from ‘express’;
import UserService from ‘../services/UserService’;
import { ErrorHandler } from ‘../middlewares/ErrorHandler’;

class UserController {
getAllUsers(req: Request, res: Response, next: NextFunction) {
try {
const users = UserService.getAllUsers();
res.json(users);
} catch (error) {
next(error);
}
}

getUserById(req: Request, res: Response, next: NextFunction) {
try {
const user = UserService.getUserById(parseInt(req.params.id));
res.json(user);
} catch (error) {
next(error);
}
}

createUser(req: Request, res: Response, next: NextFunction) {
try {
const user = UserService.createUser(req.body);
res.status(201).json(user);
} catch (error) {
next(error);
}
}
}

export default new UserController();

User Routes

// src/routes/UserRouter.ts
import { Router } from ‘express’;
import UserController from ‘../controllers/UserController’;

const router = Router();

router.get(‘/users’, UserController.getAllUsers);
router.get(‘/users/:id’, UserController.getUserById);
router.post(‘/users’, UserController.createUser);

export default router;

Application Entry Point

// src/app.ts
import express from ‘express’;
import UserRouter from ‘./routes/UserRouter’;
import { handleError } from ‘./middlewares/ErrorHandler’;

const app = express();

app.use(express.json());
app.use(‘/api’, UserRouter);
app.use(handleError);

export default app;

Server Setup

// src/server.ts
import app from ‘./app’;

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

By structuring our project using TypeScript, Express.js, Joi, and OOP principles, we achieve a clean and maintainable codebase. The custom error handling middleware provides a consistent way to manage errors across the application, while the validation logic ensures data integrity. This setup can serve as a solid foundation for more complex applications.

Contacts
Email: luizcalaca@gmail.com
Instagram: https://www.instagram.com/luizcalaca
Linkedin: https://www.linkedin.com/in/luizcalaca/
Twitter: https://twitter.com/luizcalaca

Please follow and like us:
Pin Share