Express Error handling middleware
Defining HttpException
// src/exceptions/HttpException.ts
class HttpException extends Error {
status: number;
message: string;
constructor(status: number, message: string) {
super(message);
this.status = status;
this.message = message;
}
}
export default HttpException;
Defining Express error handling middleware
// src/middleware/error.middleware.ts
import { NextFunction, Request, Response } from 'express';
import HttpException from '../exceptions/HttpException';
function errorMiddleware(error: HttpException, request: Request, response: Response, next: NextFunction) {
const status = error.status || 500;
const message = error.message || 'Something went wrong';
response
.status(status)
.send({
status,
message,
})
}
export default errorMiddleware;
Since Express runs all the middleware from the first to the last, your error handlers should be at the end of your application stack. If you pass the error to the next function, the framework omits all the other middleware in the chain and skips straight to the error handling middleware which is recognized by the fact that it has four arguments.
use the error middleware
// src/app.ts
import * as bodyParser from 'body-parser';
import express from 'express';
import * as mongoose from 'mongoose';
import Controller from './interfaces/controller.interface';
import errorMiddleware from './middleware/error.middleware';
class App {
public app: express.Application;
constructor(controllers: Controller[]) {
this.app = express();
this.connectToTheDatabase();
this.initializeMiddlewares();
this.initializeControllers(controllers);
this.initializeErrorHandling();
}
public listen() {
this.app.listen(process.env.PORT, () => {
console.log(`App listening on the port ${process.env.PORT}`);
});
}
private initializeMiddlewares() {
this.app.use(bodyParser.json());
}
private initializeErrorHandling() {
this.app.use(errorMiddleware);
}
private initializeControllers(controllers: Controller[]) {
controllers.forEach((controller) => {
this.app.use('/', controller.router);
});
}
private connectToTheDatabase() {
const {
MONGO_USER,
MONGO_PASSWORD,
MONGO_PATH,
MONGO_DB_NAME,
} = process.env;
const url = `mongodb://${MONGO_USER}:${MONGO_PASSWORD}@${MONGO_PATH}/${MONGO_DB_NAME}?authSource=admin`;
console.log(url);
mongoose.connect(url);
}
}
export default App;
// src/service/posts/post.service.ts
...
import HttpException from '../../exceptions/HttpException';
...
export async function getPostById(
request: express.Request,
response: express.Response,
next: express.NextFunction
) {
const id = request.params.id;
const post = await postModel.findById(id).exec();
if (post) {
response.send(post);
} else {
next(new HttpException(404, "Post not found"));
}
}
export async function modifyPost(
request: express.Request,
response: express.Response,
next: express.NextFunction
) {
const id = request.params.id;
const postData: Post = request.body;
const post = await postModel
.findByIdAndUpdate(id, postData, { new: true })
.exec();
if (post) {
response.send(post);
} else {
next(new HttpException(404, "Post not found"));
}
}
export async function deletePost(
request: express.Request,
response: express.Response,
next: express.NextFunction
) {
const id = request.params.id;
const post = await postModel.findByIdAndDelete(id).exec();
if (post) {
response.send(200);
} else {
next(new HttpException(404, "Post not found"));
}
}
...
defining PageNotFonund
// src/exceptions/PostNotFoundException.ts
import HttpException from "./HttpException";
class PostNotFoundException extends HttpException {
constructor(id: string) {
super(404, `Post with id ${id} not found`);
}
}
export default PostNotFoundException;
refact
export async function getPostById(
request: express.Request,
response: express.Response,
next: express.NextFunction
) {
const id = request.params.id;
const post = await postModel.findById(id).exec();
if (post) {
response.send(post);
} else {
next(new PostNotFoundException(id));
}
}
export async function modifyPost(
request: express.Request,
response: express.Response,
next: express.NextFunction
) {
const id = request.params.id;
const postData: Post = request.body;
const post = await postModel
.findByIdAndUpdate(id, postData, { new: true })
.exec();
if (post) {
response.send(post);
} else {
next(new PostNotFoundException(id));
}
}
export async function deletePost(
request: express.Request,
response: express.Response,
next: express.NextFunction
) {
const id = request.params.id;
const post = await postModel.findByIdAndDelete(id).exec();
if (post) {
response.send(200);
} else {
next(new PostNotFoundException(id));
}
}
update controller
// src/controller/posts/post.controller.ts
...
private getPostById = (request: express.Request, response: express.Response, next: express.NextFunction) => {
this.postService.getPostById(request, response, next);
}
private modifyPost = (request: express.Request, response: express.Response, next: express.NextFunction) => {
this.postService.modifyPost(request, response, next)
}
...
private deletePost = (request: express.Request, response: express.Response, next: express.NextFunction) => {
this.postService.deletePost(request, response, next);
}
...
Validating incoming data
for more usage ,read the doc of class-validator
To use decorators with TypeScript, you need to add "experimentalDecorators": true to your
tsconfig.json
// tsconfig.json
{
"compilerOptions": {
...
"experimentalDecorators": true,
...
}
}
pnpm i class-validator class-transformer reflect-metadata es6-shim
update entry
// src/server.ts
import 'dotenv/config';
import App from './app';
import validateEnv from './utils/validateEnv';
import PostsController from './controller/posts/post.controller';
// class-transformer dependency
import 'reflect-metadata';
import 'es6-shim';
// class-transformer
validateEnv();
const app = new App(
[
new PostsController(),
],
);
app.listen();
create dto
// src/dto/posts/post.dto.ts
import { IsString } from 'class-validator';
class CreatePostDto {
@IsString()
public author?: string;
@IsString()
public content?: string;
@IsString()
public title?: string;
}
export default CreatePostDto;
create validation middleware
// src/middleware/validation.middleware.ts
import { plainToClass } from 'class-transformer';
import { validate, ValidationError } from 'class-validator';
import express from 'express';
import HttpException from '../exceptions/HttpException';
function validationMiddleware<T>(type: any): express.RequestHandler {
return (req, res, next) => {
validate(plainToClass(type, req.body))
.then((errors: ValidationError[]) => {
if (errors.length > 0) {
const message = errors.map((error: ValidationError) => error.constraints && Object.values(error.constraints)).join(', ');
next(new HttpException(400, message));
} else {
next();
}
});
};
}
export default validationMiddleware;
attach the middleware
// src/controller/posts/post.controller.ts
import validationMiddleware from '../../middleware/validation.middleware';
import CreatePostDto from '../../dto/posts/post.dto';
class PostsController implements Controller {
...
private initializeRoutes() {
this.router.get(this.path, this.getAllPosts);
this.router.get(`${this.path}/:id`, this.getPostById);
this.router.patch(`${this.path}/:id`, this.modifyPost);
this.router.delete(`${this.path}/:id`, this.deletePost);
this.router.post(this.path, validationMiddleware(CreatePostDto),this.createPost);
}
...
}
here Error handling and validating incoming data procedure is shown,
class-transformer may not be flexiable as you need.
ref: sources