Setup express with Typescript - global error handler and async middleware
Tags: javascript, typescript, node
Published: 2022-07-12 07:04:35

### Introduction

In this series we will setup an express server using `Typescript`, we will be using `TypeOrm` as our ORM for querying a PostgresSql Database, we will also use `Jest` and `SuperTest` for testing. The goal of this series is not to create a full-fledged node backend but to setup an express starter project using typescript which can be used as a starting point if you want to develop a node backend using express and typescript.

### Overview

This series is not recommended for beginners some familiarity and experience working with `nodejs`, `express`, `typescript` and `typeorm` is expected. In this post which is part five of our series we will set up : -
 - Set up a global error handler.
 - Implement express async middleware library on our own.
 - Remove try / catch blocks from the controller.

### Step One: Express Global Handler

In this section we will setup 2 handlers, one for handling all our global errors and the next for handling 404 not found routes.
Before doing so lets create some error classes. Under the `src/utils` folder create a new file called `BaseError.ts` - 
```ts
export class BaseError extends Error {
  constructor(message: string) {
    super(message);
    this.name = this.constructor.name;
  }
}
```

Similarly lets create another file under `src/utils` called `NotFoundError.ts`
```ts
import { BaseError } from './BaseError';

export class NotFoundError extends BaseError {
  status: number;

  constructor(message: string, status: number) {
    super(message);
    this.status = status;
  }
}
```
If you are unfamiliar with creating custom error classes I would recommend you read - https://javascript.info/custom-errors.

Now under `server.ts` lets add the following 2 functions: 
```ts
private globalNotFoundHandler() {
    this.app.use((req, res, next) => {
      const error = new NotFoundError('Resource not found', 404);
      next(error);
    });
  }

  private globalErrorHandler() {
    this.app.use(
      (error: Error, req: Request, res: Response, next: NextFunction) => {
        console.log('Error (Global Error Handler)', error.stack);
        if (error instanceof NotFoundError) {
          return res.status(error.status).json({
            status: false,
            statusCode: error.status,
            message: error.message,
          });
        }

        // Handling internal server errors
        return res.status(500).json({
          status: false,
          statusCode: 500,
          message: 'Something unusual Happened',
        });
      }
    );
  }
```
In the above code we added a handler for handling 404 not found routes and a global error handler. In express if your middleware takes in 4 arguments namely `error, req, res, next` it is considered as an error handler middleware. You can see in the `NotFoundError` handler we used `next(error)` meaning we passed the error to our globalErrorHandler. We can also invoke our globalErrorHandler by `throwing an error` from our middlewares, controllers all `throw error` calls will land in globalErrorhandler. 

Now lets call these handlers in our constructor like so - 
```ts
 constructor() {
    // Initialize express application
    this.app = express();
    // add express middlewares
    this.addMiddlewares();
    // add our routes
    this.addRoutes();
    // handle 404 not found routes
    this.globalNotFoundHandler();
    // handle all global errors
    this.globalErrorHandler();
  }
```
Make sure your 404 handler is called after your routes and global error handler is called last.

### Step Two: Setup Async Handler Middleware

We will be using `express-async-handler` library for this. But instead on installing it, lets read their code on github, understand and implement it. Before that lets understand what a middleware is in express. Middlewares are like the layers of an onion in order to reach to the last layer you need to go through all the layers. 

In the following code our last layer is our controller (which is the last piece of middleware). Before reaching the controller our request will go through all these middleware functions - 
```ts
this.router.get(`/`, authMiddleware, validationMiddleware, todoController.createTodo)
```

App wide our last layer (middleware) is our `globalErrorhandler` function. 

Cool now how we write middlewares in express, a middleware is a function that receives `req, res, next` as arguments like - 
```ts
const authMiddleware = (req, res, next) => {}
```
To go from one layer to another layer express uses the next() function. 

Now we all are familiar with currying in javascript. `A curried function is a function that returns another function` - 
```ts
const curriedFunction = () => () => {}
```

Now if I had to write my middleware in this fashion it would be -
```ts
const curriedMiddleware = () => (req, res, next) => {}
```
And if I had to use this `curriedMiddleware` I will use it like so - 
```ts
this.router.get(`/`, authMiddleware(), todoController.createTodo)
```

This is the same pattern that `express-async-middleware` library uses. With this pattern : - 
- We can pass our controller function to the async middleware.
- The async middleware will resolve our controller function catch any errors and if we get any error it will call `next(error)`.
- `next(error)` will end up in our globalErrorHandler and will gracefully handle all the internal server errors. 

Now in our `src` folder create a new folder `middlewares` under it create a new file called `asyncHandler.ts` - 
```ts
import { Request, Response, NextFunction } from 'express';

export function asyncHandler(controllerFunction: any) {
  return function asyncMiddleware(
    req: Request,
    res: Response,
    next: NextFunction
  ) {
    const controllerPromise = controllerFunction(req, res, next);
    return Promise.resolve(controllerPromise).catch(next);
  };
}
```
This is the same code as the express-async-handler library:  
- First we have our curried function `asyncHandler` that accepts our controller function as an argument and returns the middleware function `asyncMiddleware`.
-  In our middleware function we get `req, res, next` arguments. Then we pass the `req, res, next` to our controller function after all we use them in our controllers.
- `const controllerPromise = controllerFunction(req, res, next);` here we are executing our controller function, which will return a promise if our controller is `async` or will just return whatever our controller returns.
- Finally we resolve our `controllerPromise` variable and if we catch any error we just call `next(error)` and this lands in our globalErrorHandler.

### Step 3: Using async handler 

Now lets use our async handler in our `todos.router.ts` we do - 
```ts
 addRoutes(): void {
    this.router.get('/', asyncHandler(todosController.getAllTodos));
    this.router.post('/', asyncHandler(todosController.createTodo));
    this.router.get('/:todoId', asyncHandler(todosController.getTodoById));
  }
```
We will pass our controller to the asyncHandler curried middleware. Lets remove all the try-catch blocks from our controller functions - 
```ts
import { Request, Response } from 'express';

import { todosService } from './todos.service';

class TodoController {
  async getAllTodos(req: Request, res: Response) {
    const todos = await todosService.getAllTodos();

    return res.status(200).json({
      status: true,
      statusCode: 200,
      todos,
    });
  }

  async getTodoById(req: Request, res: Response) {
    const { todoId } = req.params;
    const todo = await todosService.getTodoById(todoId);
    if (!todo) {
      return res.status(404).json({
        status: false,
        statusCode: 404,
        message: `todo not found for id - ${todoId}`,
      });
    }

    return res.status(200).json({
      status: true,
      statusCode: 200,
      todo,
    });
  }

  async createTodo(req: Request, res: Response) {
    const { text, status } = req.body;
    const newTodo = await todosService.createTodo(text, status);

    return res.status(201).json({
      status: true,
      statusCode: 201,
      todo: newTodo.raw,
    });
  }
}

export const todosController = new TodoController();
```
Look how lean and clean our controller functions are now. Lets test our endpoints whether everything is working fine by running `npm run dev`. To test the 404 not found handler visit a route which does not exists on your server. To test the global error Handler pass a json request which does not have the `text field` to create todo endpoint, TodoEntity will throw an error.

### Overview 
In this tutorial instead of pulling in the library we implemented it on our own by reading its github code. I encourage all the developers to read open source code it is very easy, it requires some time and attention but very helpful. All the code for this tutorial can be found under the `feat/async-handler` branch [here](https://github.com/yaldram/express-typescript-starter/tree/feat/async-handler). In the next tutorial we will setup request validation using zod and implement another library on our own until next time PEACE.