Nest rest todo api

Author : JaNakh Pon , August 06, 2021

Tags

Intro

We are going to build a simple rest api using Nest.Js and TypeOrm with PostgresDB in this tutorial in 3 steps (installation, configuration and coding).


Step 1 (Installation)

Let's get started by installing @nestjs/cli, generate a nest project and installing dependencies:

npm install -g @nestjs/cli
nest new nestdemo && cd nest-typeorm-todo
npm i --save @nestjs/typeorm typeorm pg postgres class-validator

And now we have a nest app with required deoendencies. To provide endpoints for todo, we are going to need three files to define what our endpoints should look like and what kind of data are we going to send through each endpoint. And these files are module, controller and service:

nest g module todo || nest g m todo
nest g controller todo || nest g c todo
nest g service todo || nest g s todo

Step 2 (configuration)

Now, if we look at our nest-typeorm-todo directory, there are a few files we don't need inside /src directory. Let's get rid of these files from src directory:

> rm -rf app.controller.ts app.controller.spec.ts app.service.ts

And, after that, we need to set up connection for PostgresDB and we can set it up by providing credentials whether in app.module.ts or in ormconfig.json. But for this time, I prefer to use ormconfig.json, so let's create a file named ormconfig.json at the root directory:

{
    "type": "postgres",
    "host": "YOUR_HOST_NAME",
    "port": 5432,
    "username": "YOUR_USER_NAME",
    "password": "YOUR_PASSWORD",
    "database": "YOUR_DATABASE_NAME",
    "entities": [
        "dist/**/*.entity{.ts,.js}"
    ],
    "synchronize": true
}

Step 3 (coding)

For coding, we can divide it into three parts:

  1. Defining Entity/Schema
  2. Defining Interfaces
  3. Defining Data Transfer Object (DTO)
  4. Defining API endpoints
    1. Service
    2. Controller and Route
    3. Module

Let's get started:


Entity/Schema

let's create a folder named entities inside /todo directory and add todo.entity.ts in it:

import {
    Entity,
    PrimaryGeneratedColumn,
    Column,
    CreateDateColumn,
    UpdateDateColumn
} from 'typeorm'

@Entity('todos')
export class TodoEntity {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    text: string;

    @Column({ default: false })
    completed: boolean;

    @CreateDateColumn()
    created_at: Date;

    @UpdateDateColumn()
    updated_at: Date;
}

Interfaces

let's create a folder named Interfaces inside /todo directory and add todo.interface.ts in it:

export interface Todo {
    id: number;
    title: string;
    text: string;
    completed: boolean;
}

export interface Status {
    message: string;
}

DTO(Data Transfer Object)

And now, let's create a folder named dto inside /todo directory and add create-todo.dto.ts in it:

import {
  IsNotEmpty,
  IsString,
  IsInt,
  IsNumber,
  IsOptional,
  IsIn,
  IsBoolean,
} from 'class-validator';

export class CreateTodoDTO {

  @IsNotEmpty()
  @IsString()
  readonly title: string;

  @IsNotEmpty()
  @IsString()
  readonly text: string;

  @IsOptional()
  @IsBoolean()
  readonly completed: boolean;

}

CRUD(Create, Read, Update, Delete) Endpoints

And finally, it's time to work on service, controller and module.

todo.service.ts

We need to define methods for CRUD functionality to interact with database in todo.service.ts:

import { Injectable, NotFoundException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { Status, Todo } from './interfaces/todo.interface'
import { TodoEntity } from './entities/todo.entity'
import { CreateTodoDTO } from './dto/create-todo.dto'

@Injectable()
export class TodoService {
    constructor(@InjectRepository(TodoEntity)
    private readonly TodoRepository: Repository<TodoEntity>,) { }

    async create(todo: CreateTodoDTO): Promise<Todo> {
        const resp = await this.TodoRepository.save({
            title: todo.title,
            text: todo.text,
            completed: todo.completed
        })
        return resp
    }

    async findAll(): Promise<Todo[]> {
        return await this.TodoRepository.find()
    }

    async findOne(id: number): Promise<Todo> {
        const resp = await this.TodoRepository.findOne(id)
        if (!resp) {
            throw new NotFoundException('Could not find the task with the provided id!')
        }
        return resp
    }

    async update(id: number, toUpdate: CreateTodoDTO): Promise<Todo> {
        const resp = await this.TodoRepository.update(id, toUpdate)
        if (resp.affected === 0) {
            throw new NotFoundException('Could not find the task with the provided id!')
        }
        return await this.TodoRepository.findOne(id)
    }

    async delete(id: number): Promise<Status> {
        const resp = await this.TodoRepository.delete(id)
        if (resp.affected === 0) {
            throw new NotFoundException('Could not find the task with the provided id!')
        }
        return { message: `successfully removed task with id: ${id}` }
    }
}
todo.controller.ts

Since we have CRUD functionality related methods in todo.service.ts it's time to define routes and what data should be transfered through each route in todo.controller.ts:

import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
import { CreateTodoDTO } from './dto/create-todo.dto';
import { Status, Todo } from './interfaces/todo.interface';
import { TodoService } from './todo.service'

@Controller('todos')
export class TodoController {
    constructor(private todoService: TodoService) { }

    @Post()
    async create(@Body() todo: CreateTodoDTO): Promise<Todo> {
        return await this.todoService.create(todo)
    }

    @Get()
    async find(): Promise<Todo[]> {
        return await this.todoService.findAll()
    }

    @Get(':id')
    async findOne(@Param('id') id: string): Promise<Todo> {
        return await this.todoService.findOne(+id)
    }

    @Put(':id')
    async update(
        @Param('id') id: string,
        @Body() toUpdate: CreateTodoDTO,
    ): Promise<Todo> {
        return await this.todoService.update(+id, toUpdate)
    }

    @Delete(':id')
    async delete(@Param('id') id: string): Promise<Status> {
        return await this.todoService.delete(+id)
    }
}
todo.module.ts

And, To expose endpoints using TypeOrm Entity we need to import TypeOrmModule to our todo.module.ts:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TodoEntity } from './entities/todo.entity';
import { TodoController } from './todo.controller';
import { TodoService } from './todo.service';

@Module({
  imports: [TypeOrmModule.forFeature([TodoEntity])],
  controllers: [TodoController],
  providers: [TodoService]
})
export class TodoModule { }
app.module.ts

And, Finally we need to import TypeOrmModule.foRoot() for PostgresDB Connection and also need to import TodoModule for exposing endpoints:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'
import { TodoModule } from './todo/todo.module';

@Module({
  imports: [
    TypeOrmModule.forRoot(),
    TodoModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule { }

Yay!!! Our Nest.js API with PostgresDB is finished 🎓! and now we can explore it using either postman or thunderclient by running:

  npm run start

Source Code.

Go Back.