Setup express with Typescript - querying database adding services
Tags: javascript, typescript, node
Published: 2022-07-12 07:03:47

### 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 four of our series we will : - 
 - Use the `Todo` entity to query our database.
 - Create a `Todo` Service.
 - Use the `Todo` Service in our `Todo` controller.

### Step One: Query our database

In our `todo.controller.ts` lets now remove the hard-coded `todos` and query our database like so : 
```ts
import { Request, Response } from 'express';

import { AppDataSource } from '../../datasource';
import { Todos } from './todos.entity';

class TodoController {
  async getAllTodos(req: Request, res: Response) {
    try {
      const todos = await AppDataSource.getRepository(Todos)
        .createQueryBuilder('todos')
        .getMany();
      return res.status(200).json({
        status: true,
        statusCode: 200,
        todos,
      });
    } catch (error) {
      return res.status(500).json({
        status: false,
        statusCode: 500,
        message: 'Something unusual Happened',
      });
    }
  }

  async getTodoById(req: Request, res: Response) {
    try {
      const { id } = req.params;
      const todo = await AppDataSource.getRepository(Todos)
        .createQueryBuilder('todos')
        .where('todos.id = :id', { id })
        .getOne();
      if (!todo) {
        return res.status(404).json({
          status: false,
          statusCode: 404,
          message: `todo not found for id - ${id}`,
        });
      }

      return res.status(200).json({
        status: true,
        statusCode: 200,
        todo,
      });
    } catch (error) {
      return res.status(500).json({
        status: false,
        statusCode: 500,
        message: 'Something unusual Happened',
      });
    }
  }
}

export const todosController = new TodoController();
```
For querying the database I use the `queryBuilder` pattern, the `createQueryBuilder` function takes in the table name, make sure you pass in the right name. **The try-catch blocks are necessary to handle errors, thrown by our code, or by the orm library, etc. in the up coming tutorials we will create an async handler middleware, that will take care of all this, and then we can get rid of all these try-catch blocks.**

### Step 2: Setup Todo service

Writing all our queries and database related logic in separate service methods as opposed to writing them in the controller is one of the best practices:
-  Writing code that does one job (an atomic unit of code) is easily testable, readable and scalable in the long run. 
- We have re-usable functions that can be used in many controllers, other services throughout the project.
- When testing our code, we can easily mock our services, as opposed to mocking our request handlers. We can also unit test our service functions.

You can notice in each controller code we have to call the `getRepository` function we can just move this piece of code in the entity file itself, or outside our controller class. But given the fact that we are using Typescript lets create a BaseService class. Under `src/utils` create a new file `BaseService.ts` and paste the following : 
```ts
import { EntityTarget, Repository } from 'typeorm';

import { AppDataSource } from '../datasource';

export class BaseService<T> {
  repository: Repository<T>;

  constructor(entity: EntityTarget<T>) {
    this.repository = AppDataSource.getRepository(entity);
  }
}
```
`BaseService` is a generic class, its sole job is to create a repository for us, so that every service class that extends it has the `this.respository` property. As our project grows we can move a lot of repetitive code in this base class. Now lets create our `todos.service.ts` inside `src/api/todos.service.ts` : 
```ts
import { BaseService } from '../../utils/BaseService';

import { Todos, TodoStatus } from './todos.entity';

class TodosService extends BaseService<Todos> {
  constructor() {
    super(Todos);
  }

  getAllTodos() {
    return this.repository.createQueryBuilder('todos').getMany();
  }

  getTodoById(todoId: string) {
    return this.repository
      .createQueryBuilder('todos')
      .where('todos.id = :id', { id: todoId })
      .getOne();
  }

  createTodo(text: string, status: TodoStatus) {
    return this.repository
      .createQueryBuilder()
      .insert()
      .into(Todos)
      .values({
        text,
        status,
      })
      .returning('*')
      .execute();
  }
}

export const todosService = new TodosService();
```
In our constructor we call `super(Todo)` this invokes the `BaseService` class constructor and passes our entity to it. We have also added a new `createTodo` function in our service.

### Step Three: Use service in our controller

Now we will use our service methods in the controller file: 
```ts
import { Request, Response } from 'express';

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

class TodoController {
  async getAllTodos(req: Request, res: Response) {
    try {
      const todos = await todosService.getAllTodos();
      return res.status(200).json({
        status: true,
        statusCode: 200,
        todos,
      });
    } catch (error) {
      return res.status(500).json({
        status: false,
        statusCode: 500,
        message: 'Something unusual Happened',
      });
    }
  }

  async getTodoById(req: Request, res: Response) {
    try {
      const { id: 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,
      });
    } catch (error) {
      return res.status(500).json({
        status: false,
        statusCode: 500,
        message: 'Something unusual Happened',
      });
    }
  }

  async createTodo(req: Request, res: Response) {
    try {
      const { text, status } = req.body;
      const newTodo = await todosService.createTodo(text, status);
      return res.status(201).json({
        status: true,
        statusCode: 201,
        todo: newTodo.raw,
      });
    } catch (error) {
      return res.status(500).json({
        status: false,
        statusCode: 500,
        message: 'Something unusual Happened',
      });
    }
  }
}

export const todosController = new TodoController();
```
Lets  register an endpoint in our `todos.router.ts`: 
```ts
 addRoutes(): void {
    this.router.get('/', todosController.getAllTodos);
    this.router.post('/', todosController.createTodo);
    this.router.get('/:id', todosController.getTodoById);
 }
```

Now you can start your webserver by running `npm run dev` and test your endpoints. I personally use postman, but for such small projects I use REST client for vscode and here is my `api.http` - 
```
GET http://localhost:8080/api/todos

###
GET http://localhost:8080/api/todos/:todoId

###
POST http://localhost:8080/api/todos 
Content-Type: application/json

{
  "text": "Walk for 30 minutes",
  "status": "pending"
}
```

### Summary
Well then we have finished setting up our service and we also used it in our controller. All the code for this tutorial can be found under the `feat/querying-database` branch [here](https://github.com/yaldram/express-typescript-starter/tree/feat/querying-database). In the next tutorial we will get rid of all the try and catch repetitive code by setting a global Error handler and an async middleware until next time PEACE.