본문 바로가기
Web/Etc

[Nest] Create a simple backend server

by llHoYall 2020. 10. 2.

The Nest's catchphrase is like below.

A progressive Node.js framework for building efficient, reliable, and scalable server-side applications.

Let's make a simple Rest server with Nest.

Installation

$ yarn add -g @nestjs/cli
  or
$ npm i -g @nest/cli

Globally install Nest CLI, and you can use Nest with CLI.

Create a Project

$ nest new <project name>

Then an interactive shell is executed.

Choose what you want, I chose Yarn.

Nest will config all like Git, ESLint, TypeScript, Jest, etc.

Execute Nest!!

$ yarn start:dev

And open your browser at http://localhost:3000/.

Explore a Project

All of the project sources are in the src folder.

main.ts

This is the entry file of the application which uses the core function NestFactory to create a Nest application instance.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

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

In the code, the application is created by NestFactory with AppModule.

Oh, can you see it? That's why we use the port 3000.

The next thing to look into is AppModule!!

app.module.ts

This is the root module of the application.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

AppModule has controllers and providers.

app.controller.ts

This is the basic controller sample with a single route.

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

If you access the default path, the getHello() function is called.

And, eventually, AppService is called.

app.service.ts

These are the services of the application.

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

There~!! There is a default message!!

 

Now, can you know about the structure of the Nest?

Are you ready to make our own application?

Create an Application

Create a Module with Controller and Service

First, create a module.

$ nest g mo

Input the name of the module. I input the 'contact'.

Then, the contact folder and contact.module.ts file are created, and automatically updated app.module.ts file.

 

Next, create a controller.

$ nest g co

Input the name of the controller. Again, I input the 'contact'.

The contact.controller.ts file is created in the contact folder, and the contact.module.ts file is automatically updated.

 

Lastly, create a service.

$ nest g s

Input the name of the service. I also input the 'contact'.

The contact.service.ts file is created in the contact folder, and the contact.module.ts file is also automatically updated.

This is the current folder structure. All the things are updated automatically.

Create a DTO and Entity

Create a dto folder and create-contact.dto.ts file in the contact folder.

DTO is a Data Transfer Object

Determine the DTO schema.

export class CreateContactDto {
  readonly name: string;

  readonly age: number;

  readonly email: string[];
}

Now, create an entity folder and contact.entity.ts file in the contact folder.

export class Contact {
    id: number;
    name: string;
    age: number;
    email: string[];
  }

C: Create (POST Method)

Let's implement the C of CRUD.

Create service first.

Open contact.service.ts file and add a create method.

import { Injectable } from '@nestjs/common';
import { Contact } from './entity/contact.entity';
import { CreateContactDto } from './dto/create-contact.dto';

@Injectable()
export class ContactService {
  private contacts: Contact[] = [];

  create(contactData: CreateContactDto) {
    this.contacts.push({
      id: this.contacts.length + 1,
      ...contactData,
    });
  }
}

 

Define a variable as a database with the entity, and implement a function with DTO.

Now connect this to the controller.

import { Body, Controller, Post } from '@nestjs/common';
import { CreateContactDto } from './dto/create-contact.dto';
import { ContactService } from './contact.service';

@Controller('contact')
export class ContactController {
  constructor(private readonly contactService: ContactService) {}

  @Post()
  create(@Body() contactData: CreateContactDto) {
    return this.contactService.create(contactData);
  }
}

If the application receives the Post request with '/contact', the contact will be created and added in the DB.

Test your application!

2020/09/30 - [Web/Etc] - [Web] Testing Rest API on VS Code

I used the above method.

This is my test script.

POST http://localhost:3000/contact HTTP/1.1
content-type: application/json

{
    "name": "hoya",
    "age": 18,
    "email": [
      "abc@example.com",
      "def123@exampl.com"
    ]
}

How is it? It works well.

 

But there is a problem. Even if we send a request with the wrong information, there is no problem. That's the problem.

We can resolve this problem with the class validator and transformer.

$ yarn add class-validator class-transformer

Now, update our DTO.

import { IsNumber, IsOptional, IsString } from 'class-validator';

export class CreateContactDto {
  @IsString()
  readonly name: string;

  @IsOptional()
  @IsNumber()
  readonly age: number;

  @IsString({ each: true })
  readonly email: string[];
}

The age is optional, and other fields are mandatory.

Each value of the email array has to be all string value.

And, this is the transformer's turn.

Let's open main.ts file.

import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

Using the pipe like express's middleware.

Our application passes through the validation pipe.

All done. Try to test with the wrong information.

Our server will send us the error with proper messages.

{
  "statusCode": 400,
  "message": [
    "name must be a string",
    "each value in email must be a string"
  ],
  "error": "Bad Request"
}

R: Read (GET Method)

This time implements the R of CRUD.

I'll make some services.

getAll(): Contact[] {
  return this.contacts;
}

getOneWithID(id: number): Contact {
  const contact = this.contacts.find(contact => contact.id === id);
  if (!contact) {
    throw new NotFoundException(`Not found contact with ID ${id}`);
  }
  return contact;
}

getOneWithName(name: string): Contact {
  const contact = this.contacts.find(contact => contact.name === name);
  if (!contact) {
    throw new NotFoundException(`Not found contact with name ${name}`);
  }
  return contact;
}

Just by looking at the name of the function, you will know what it is doing.

And, use this on the controller.

@Get()
getAll(): Contact[] {
  return this.contactService.getAll();
}

@Get('search')
search(@Query('name') searchingName: string) {
  return this.contactService.getOneWithName(searchingName);
}

@Get(':id')
getOneWithID(@Param('id') contactId: number): Contact {
  return this.contactService.getOneWithID(contactId);
}

If we access the '/contact' path, we can get the whole list of contact.

If we access with a query string like '/contact/search?name=kim', we can get the one contact as given name.

If we access with the ID like '/contact/1', we can get the one contact as given ID.

 

The getAll() function works well, but getOneWithID() function doesn't work well.

Because our expected id is a number value, but the HTTP request is a string value.

We have two options.

The first option is for us to convert directly, and the second option is for Nest to automatically convert directly.

I use the second option.

Let's open the main.ts file and add this argument.

app.useGlobalPipes(new ValidationPipe({ transform: true }));

Now the problem has gone.

 

If you change the order of @Get() decorators, it will not work properly.

Our server will try to handle all the requests with ':id', not 'search'.

Please be sure to be careful.

 

Here is my test script.

###
Get http://localhost:3000/contact HTTP/1.1

###
Get http://localhost:3000/contact/2 HTTP/1.1

###
Get http://localhost:3000/contact/search?name=hoya HTTP/1.1

U: Update (PATCH, PUT Method)

Now, it is U turn of CRUD.

 

First, add more DTO for updating.

We need a mapped-types of Nest.

$ yarn add @nestjs/mapped-types

Create the update-contact.dto.ts file and fill it.

import { PartialType } from '@nestjs/mapped-types';
import { CreateContactDto } from './create-contact.dto';

export class UpdateContactDto extends PartialType(CreateContactDto) {}

You can now apply only a fraction of it using an already defined CreateContactDto.

 

Let's create a service as before.

updateAll(updateData: CreateContactDto[]): Contact[] {
  this.contacts = [];
  updateData.map(data => {
    this.contacts.push({
      id: this.contacts.length + 1,
      ...data,
    });
  });
  return this.contacts;
}

updateOneWithID(id: number, updateData: UpdateContactDto) {
  const contact = this.getOneWithID(id);
  if (!contact) {
    throw new NotFoundException(`Not found contact with ID ${id}`);
  }
  this.deleteOneWithID(id);
  this.contacts.push({ ...contact, ...updateData });
}

deleteOneWithID(id: number): boolean {
  this.contacts = this.contacts.filter(contact => contact.id !== id);
  return true;
}

It is hard to update partial data, so we delete the old data and add new data.

So, I implement the deleteOneWithID() service as well.

Let's implement the controller side.

@Put()
updateAll(@Body() updateData: CreateContactDto[]) {
  return this.contactService.updateAll(updateData);
}

@Patch(':id')
patch(@Param('id') contactId: number, @Body() updateData: UpdateContactDto) {
  return this.contactService.updateOneWithID(contactId, updateData);
}

Isn't it simple?

 

Now, this is my test script.

###
PUT http://localhost:3000/contact HTTP/1.1
content-type: application/json

[
  {
    "name": "kim",
    "email": [
      "ghi@example.com"
    ]
  },
  {
    "name": "lee",
    "email": [
      "jkl@example.com"
    ]
  }
]

###
PATCH http://localhost:3000/contact/2 HTTP/1.1
content-type: application/json

{
  "age": 30
}

D: Delete (DELETE Method)

Now, finally, it is D of CRUD.

We already make a service for this.

So we just implement the controller part.

@Delete(':id')
delete(@Param('id') contactId: number) {
  return this.contactService.deleteOneWithID(contactId);
}

And, here is the test script.

###
DELETE http://localhost:3000/contact/2 HTTP/1.1

 

I want you to feel how easy to make a restful backend server with Nest.

'Web > Etc' 카테고리의 다른 글

[Firebase] Firebase Authentication with React  (0) 2020.10.04
[Nest] Unit Testing and E2E Testing  (0) 2020.10.03
[Web] Testing Rest API on VS Code  (0) 2020.09.30
[Gulp] Getting Started  (0) 2020.09.12
[Babel] Getting Started  (0) 2020.09.10

댓글