DEV Community

loading...

Writing Blazing Fast and Less Code CRUD Operation + OpenAPI in NestJS

Edmar Diaz
Fullstack JavaScript Developer working with React, ReactNative, Ionic(3,4,5) Angular <3, NestJS <3, NodeJS, TS <3 and #geekineers
・4 min read

Introduction

Hello guys, today I'm gonna share to you how I write my CRUD Operations in a fastest way.

In this tutorial, we will create a simple ToDo List Backend with CRUD Operations, we will be using mysql as our database and typeorm for our ORM. We will be using nestjsx/crud package for our easy crud operation.

Before we start make sure you have the following installed and configured:

  • MySQL
  • NestJS

Let's Get Started

First let's create a new project using nest-cli:

nest new crud-operation
Enter fullscreen mode Exit fullscreen mode

First lets install typeporm, mysql library and dotenv:

npm install --save dotenv
npm install --save @nestjs/typeorm typeorm mysql
Enter fullscreen mode Exit fullscreen mode

Now lets create our .env file and database configuration service:

touch .env
mkdir src/shared && mkdir src/shared/services
touch src/shared//services/database-connection.service.ts
Enter fullscreen mode Exit fullscreen mode

Let open our .env file and configure our database credential, make sure to put your own database credentials:

DATABASE_HOST = "localhost"
DATABASE_PORT = 3306
DATABASE_USER = "root"
DATABASE_PASSWORD = "root"
DATABASE_DB = "nestjs_crud"
Enter fullscreen mode Exit fullscreen mode

Now let's open our database-connection.service.ts and lets add the follwing code:

import { Injectable } from '@nestjs/common'
import { TypeOrmOptionsFactory, TypeOrmModuleOptions } from '@nestjs/typeorm'
import 'dotenv/config'

@Injectable()
export class DatabaseConnectionService implements TypeOrmOptionsFactory {

  createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
      name: 'default',
      type: 'mysql',
      host: process.env.DATABASE_HOST,
      port: Number(process.env.DATABASE_PORT),
      username: process.env.DATABASE_USER,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE_DB,
      synchronize: true,
      dropSchema: false,
      logging: true,
      entities: ['dist/**/*.entity.js'],
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

After we created our database connection service lets configure our TypeOrmModule and use our database connection service, lets open app.module.ts import our TypeOrmModule:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DatabaseConnectionService } from './shared/services/database-connection.service';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      useClass: DatabaseConnectionService
    })
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Now that we have our database connection configured lets check if we are really connected lets run our application by using this command:

npm run start:dev
Enter fullscreen mode Exit fullscreen mode

Now lets wait for the compilation and build and after it we should see the following:
Alt Text

Now let's make our todo module, service and controller:

nest g module todo
nest g service todo
nest g controller todo
Enter fullscreen mode Exit fullscreen mode

After we created our todo module, service and controller lets install the nestjsx/crud:

npm i @nestjsx/crud class-transformer class-validator
npm i @nestjsx/crud-typeorm
Enter fullscreen mode Exit fullscreen mode

Now lets create our todo entity (Database Entity) :

touch src/todo/todo.entity.ts
Enter fullscreen mode Exit fullscreen mode

Let's open our todo.entity.ts and create our todo entity:

import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";

@Entity('todos')
export class TodoEntity {

  @PrimaryGeneratedColumn() 
  id: number;

  @Column() 
  title: string;

  @Column()
  description: string

  @Column({
    type: 'boolean',
    default: false
  })
  is_done: boolean

  @CreateDateColumn()
  create_at: Date

  @UpdateDateColumn()
  updated_at: Date
}
Enter fullscreen mode Exit fullscreen mode

We successfully created our todos table in our database.

Now Let's start our CRUD Operation, lets open our todo.service.ts and write the following code:

import { Injectable } from '@nestjs/common';
import { TypeOrmCrudService } from "@nestjsx/crud-typeorm";
import { TodoEntity } from './todo.entity';
import { InjectRepository } from '@nestjs/typeorm';

@Injectable()
export class TodoService extends TypeOrmCrudService<TodoEntity>{
  constructor(@InjectRepository(TodoEntity) repo) {
    super(repo)
  }
}
Enter fullscreen mode Exit fullscreen mode

Now lets open our todo.controller.ts and write the following:

import { Controller } from '@nestjs/common';
import { Crud, CrudController } from '@nestjsx/crud';
import { TodoEntity } from './todo.entity';
import { TodoService } from './todo.service';

@Crud({
  model: {
    type: TodoEntity
  }
})

@Controller('todo')
export class TodoController implements CrudController<TodoEntity> {
  constructor(public service: TodoService) {}
}
Enter fullscreen mode Exit fullscreen mode

At this point we are almost done with our service and controller and ofcourse CRUD Operation, we just need to import our TypeOrmModule and use our TodoEntity on our todo.module.ts so lets open up our todo.module.ts:

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

@Module({
  imports: [
    TypeOrmModule.forFeature([TodoEntity])
  ],
  providers: [TodoService],
  controllers: [TodoController]
})
export class TodoModule {}
Enter fullscreen mode Exit fullscreen mode

At this point our CRUD Operation is done, now maybe you are wondering WTF where is @post , @Get, @Put, @Patch, @Delete methods?? Nestjs/Crud package handles it for us automatically but we can override different method if we want our own custom logic just go to their wiki for more info about it, Now lets setup our OpenAPI, for this we will be using nestjs swagger:

npm install --save @nestjs/swagger swagger-ui-express
Enter fullscreen mode Exit fullscreen mode

After installing swagger lets open our main.ts:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const options = new DocumentBuilder()
    .setTitle('Todo Crud')
    .setDescription('The todo API description')
    .setVersion('1.0')
    .addTag('todo')
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('api/docs', app, document);

  await app.listen(3000);
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode

Now our openAPI is configured we can now access it on http://localhost:3000/api/docs
Alt Text

Now we can see our different todo endpoints. But our openAPI is not properly documented at this moment we need to add api tags for us to show what are the fields that we need on creating a todo for example this screenshot.
Alt Text

It's empty right?, We dont know what are to fields on our post request

In order to fix this we need to add @ApiProperty() on our column in our entity file, lets open our todo.entity.ts and add the decorator:

import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";
import { ApiProperty } from "@nestjs/swagger";

@Entity('todos')
export class TodoEntity {

  @PrimaryGeneratedColumn() 
  id: number;

  @ApiProperty()
  @Column() 
  title: string;

  @ApiProperty()
  @Column()
  description: string

  @ApiProperty()
  @Column({
    type: 'boolean',
    default: false
  })
  is_done: boolean

  @CreateDateColumn()
  create_at: Date

  @UpdateDateColumn()
  updated_at: Date
}
Enter fullscreen mode Exit fullscreen mode

Lets refresh the page and, now we can see the fields

Alt Text

We can now test our crud operation.
Alt Text

Aside from that nestjs/crud comes in with different options like pagination, filters, etc
Alt Text

You dont have to worry about pagination, writing your own filters, etc, its blazing fast right and less code.

If you want to explore more and go deep in you can check nestjsx/crud wiki and repo

Discussion (0)