December 17, 2018











Since not always everything goes perfectly, you need to expect the unexpected. To prepare for that, we cover TypeScript Express error handling and incoming data validation. Just as before, the repository for the tutorial is mwanago/express-typescript. If you find it helpful, feel free to give it a star.

Express Error handling

In the previous part of the tutorial, we wrote a handler function to return a post with a specific ID:

1 2 3 4 5 6 7 private getPostById = ( request : express . Request , response : express . Response ) = > { const id = request . params . id ; this . post . findById ( id ) . then ( ( post ) = > { response . send ( post ) ; } ) ; }

There is a slight problem with it though. If a post with a particular ID does not exist, its value in the callback function is null. We then send it, resulting in a response with a code 200 OK. It indicates that everything went fine and the user got a document that he requests, but this post is not empty in our database: it just does not exist. There is a big list of status codes that you can use, but in this case, we use 404 Not Found.

The 200 OK code if default when you use the send function. To change that, you need to call the status function, before using send.

1 2 3 4 5 6 7 8 9 10 11 private getPostById = ( request : express . Request , response : express . Response ) = > { const id = request . params . id ; this . post . findById ( id ) . then ( ( post ) = > { if ( post ) { response . send ( post ) ; } else { response . status ( 404 ) . send ( { error : 'Post not found' } ) ; } } ) ; }

Now when someone tries to access a post that does not exist, he is informed about what went wrong.

This outcome is good, but we can make our code better. We want to create errors from our route handlers and let the middleware worry about sending them. There is a default error handler built into Express. To use it, you need to call the next function with an argument (other than the string ‘route’).

1 2 3 4 5 6 7 8 9 10 11 private getPostById = ( request : express . Request , response : express . Response , next : NextFunction ) = > { const id = request . params . id ; this . post . findById ( id ) . then ( ( post ) = > { if ( post ) { response . send ( post ) ; } else { next ( 'Post not found' ) ; } } ) ; }

Handling the error in this way results in a 500 Internal Server Error and the error page is rendered.

If we want to handle it differently, we need to create our Express error handling middleware

Express Error handling middleware

First, let’s create a class that we are going to use to throw errors.

src/exceptions/HttpException.ts

1 2 3 4 5 6 7 8 9 10 11 class HttpException extends Error { status : number ; message : string ; constructor ( status : number , message : string ) { super ( message ) ; this . status = status ; this . message = message ; } } export default HttpException ;

If you would like to know more about the Error object and the errors in general, check out Handling errors in JavaScript with try…catch and finally

Defining Express error handling middleware is almost the same as any other middleware, except we use four arguments instead of three, with the error being the additional first argument.

src/middleware/error.middleware.ts

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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.

The last thing to do is to attach the error handling middleware to our app:

src/app.ts

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import * as bodyParser from 'body-parser' ; import * as 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 , } = process . env ; mongoose . connect ( ` mongodb : //${MONGO_USER}:${MONGO_PASSWORD}${MONGO_PATH}`); } } export default App ;

1 2 3 4 5 6 7 8 9 10 11 private getPostById = ( request : express . Request , response : express . Response , next : express . NextFunction ) = > { const id = request . params . id ; this . post . findById ( id ) . then ( ( post ) = > { if ( post ) { response . send ( post ) ; } else { next ( new HttpException ( 404 , 'Post not found' ) ) ; } } ) ; }

We can use the HttpException in the same manner when a post that the user wants to delete or modify a post that doesn’t exist. That would mean creating the HttpException in the same way. To avoid redundant code, we can prepare an exception just for that situation.

src/exceptions/PostNotFoundException.ts

1 2 3 4 5 6 7 8 9 import HttpException from "./HttpException" ; class PostNotFoundException extends HttpException { constructor ( id : string ) { super ( 404 , ` Post with id $ { id } not found ` ) ; } } export default PostNotFoundException ;

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 private getPostById = ( request : express . Request , response : express . Response , next : express . NextFunction ) = > { const id = request . params . id ; this . post . findById ( id ) . then ( ( post ) = > { if ( post ) { response . send ( post ) ; } else { next ( new PostNotFoundException ( id ) ) ; } } ) ; } private modifyPost = ( request : express . Request , response : express . Response , next : express . NextFunction ) = > { const id = request . params . id ; const postData: Post = request . body ; this . post . findByIdAndUpdate ( id , postData , { new : true } ) . then ( ( post ) = > { if ( post ) { response . send ( post ) ; } else { next ( new PostNotFoundException ( id ) ) ; } } ) ; } private deletePost = ( request : express . Request , response : express . Response , next : express . NextFunction ) = > { const id = request . params . id ; this . post . findByIdAndDelete ( id ) . then ( ( successResponse ) = > { if ( successResponse ) { response . send ( 200 ) ; } else { next ( new PostNotFoundException ( id ) ) ; } } ) ; }

Validating incoming data

Another thing worth mentioning is validating input data to prevent the users from creating invalid documents in our collections. To do that I use a package called class-validator with an additional middleware.

The first thing to do is to create a data transfer object (DTO) file that carries data between our functions. It contains specification on how should the incoming data look.

src/posts/post.dto.ts

1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { IsString } from 'class-validator' ; class CreatePostDto { @ IsString ( ) public author: string ; @ IsString ( ) public content: string ; @ IsString ( ) public title: string ; } export default CreatePostDto ;

To use decorators with TypeScript, you need to add "experimentalDecorators": true to your tsconfig.json

When we got that down, the only thing left is the validation middleware. Since the body of our request is a plain object, we need to transform it into our class first. To do that, I use the class-transformer package.

src/middleware/validation.middleware.ts

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { plainToClass } from 'class-transformer' ; import { validate , ValidationError } from 'class-validator' ; import * as 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 ) = > Object . values ( error . constraints ) ) . join ( ', ' ) ; next ( new HttpException ( 400 , message ) ) ; } else { next ( ) ; } } ) ; } ; } export default validationMiddleware ;

The class-validator package validates the object, and if it finds some errors, the middleware calls the next function with the error details. Since we pass an error into the next function, the Express error middleware that we described above takes care of it. The errors variable keeps an array of errors, each of them having the constraints object with the details. This simple example creates a string of all of the issues.

The 400 Bad Request status code means that there is something wrong with the request that the client sent.

If you need to, you can also pass an array so that it can be easier to iterate over on the frontend

A thing left to do is to attach the middleware:

1 2 3 4 5 6 import validationMiddleware from '../middleware/validation.middleware' ; import CreatePostDto from './post.dto' ; private initializeRoutes ( ) { this . router . post ( this . path , validationMiddleware ( CreatePostDto ) , this . createPost ) ; }

Since we want it only on some of our endpoints, we attach it straight before the handler functions. In the example above, the middleware validates the data before the this.createPost function runs.

Validating the PATCH handler data

It would be great to use that validation in our updating logic too. There is a small catch: in our CreatePostDto class, all fields are required, and we are using HTTP PATCH that allows for updating just some of the properties without passing the rest of them. There is an easy solution for that thanks to the skipMissingProperties option.

src/middleware/validation.middleware.ts

1 2 3 4 5 6 7 8 9 10 11 12 13 function validationMiddleware < T > ( type : any , skipMissingProperties = false ) : express . RequestHandler { return ( req , res , next ) = > { validate ( plainToClass ( type , req . body ) , { skipMissingProperties } ) . then ( ( errors : ValidationError [ ] ) = > { if ( errors . length > 0 ) { const message = errors . map ( ( error : ValidationError ) = > Object . values ( error . constraints ) ) . join ( ', ' ) ; next ( new HttpException ( 400 , message ) ) ; } else { next ( ) ; } } ) ; } ; }

It skips validating all the properties that are missing, so if you are updating just a part of the document, it doesn’t cause an error just because a required property is not present.

1 2 3 4 private initializeRoutes ( ) { this . router . patch ( ` $ { this . path } / : id ` , validationMiddleware ( CreatePostDto , true ) , this . modifyPost ) ; this . router . post ( this . path , validationMiddleware ( CreatePostDto ) , this . createPost ) ; }

Summary

In this article, we’ve covered Typescript Express error handling. To do that we’ve created an Express error handling middleware and used the next function with an additional argument to pass the error to the error handling middleware. Aside from that, we’ve learned how to validate incoming data in our POST and PATCH handlers using the class-validator package. All those new skills will surely be useful, especially because the next part of the course will cover registering users and authentication.