Setup express with Typescript - querying database adding services

Arsalan Ahmed Yaldram - Jul 12 '22 - - Dev Community

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 :

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();
Enter fullscreen mode Exit fullscreen mode

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 :

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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 :

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();
Enter fullscreen mode Exit fullscreen mode

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:

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();
Enter fullscreen mode Exit fullscreen mode

Lets register an endpoint in our todos.router.ts:

 addRoutes(): void {
    this.router.get('/', todosController.getAllTodos);
    this.router.post('/', todosController.createTodo);
    this.router.get('/:id', todosController.getTodoById);
 }
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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. 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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player