본문 바로가기

프로그램 개발

NestJS 개발 시작하기(로그인 인증 개발); 3. API 개발과 Swagger

NestJS 개발 시작하기(로그인 인증 개발)

1. NestJS와 Spring의 비교

2. NestJS CLI로 개발시작

3. API개발과 Swagger

4. TypeORM으로 DB연결

5. Session, JWT, OAuth 차이

6. jwt 인증 (1)

7. 암호화와 해쉬 함수

 

4. Auth API

앞에서 이번 Auth API의 개발 요구사항을

 

  1. Auth는 signup, signin, signout의 3가지 API를 제공,
  2. 개인 정보는 email을 userid로 동일하게 사용하고, 사용자 명, password

를 제공하는 것으로 잡았으니, 이를 기준으로 하는 간단한 API를 만들어 보자.

a. Swagger

API 개발에는 OpenAPI, Swagger패키지를 사용하면 개발하면서 만들어진 부분을 쉽게 테스트해 볼 수 있다. Swagger를 사용하기 위해서는 먼저 @nestjs/swagger를 설치하고

 

pnpm install @nestjs/swagger

 

main.ts를 아래와 같이 수정한다.

 

async function bootstrap() {

  const app = await NestFactory.create(AppModule);
// added for swagger
  const config = new DocumentBuilder()
    .setTitle('Sensor server')
    .setDescription('Sensor data receiver API description')
    .setVersion('1.0')
    .addTag('sensors')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);
// end of swagger
  await app.listen(3000);
}

b. Controller

Sign up, in, out 세 개의 API에 대해 다음과 같이 정의했다. SignIn도 같은 방법으로 정보전달 Schema를 정의할 수 있겠지만, API를 만드는 다양한 방법에 대해 기록을 남기기 위해 다른 방식을 사용했다.

URL HTTP 방식 설명 전달정보
/sign/up POST 신규생성 Body로 정보 전달. Body는 SignUpDto로 정의
/sign/in GET 로그인 path parameter로 id, query로 passwd를 전달
/sign/out GET 로그아웃 path parameter로 id 전달

먼저 SignUpDto를 하나 만들자.

 

import { ApiProperty } from "@nestjs/swagger";

export class SigninDto {
    @ApiProperty()
    id: string;

    @ApiProperty()
    passwd: string;
}

 

앞에서 생성한 sign.controller에 API의 내용을 아래와 같이 만들 수 있다. Controller는 주로 routing을 위한 목적으로 사용하는 것이 좋은데, HTTP의 어떤 방식으로 받을 것인지와 path를 먼저 annotation으로 @Post('up')과 같이 선언하고, debug를 위한 목적으로 log를 남기고, 뒤에서 만들 Service의 함수를 호출하는 정도록 작성하면 된다.

 

import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
import { SignUpDto } from '../dto/SignUpDto';
import { SignService } from './sign.service';

@Controller('sign')
export class SignController {
    constructor(private readonly signService: SignService) {}

    @Post('up')
    signUp(@Body() signUpDto: SignUpDto) {
        console.debug('Sign up\n' + 'SignUpDto = ' + JSON.stringify(signUpDto));
        const result = this.signService.createAccount(signUpDto);
        return 'Sign up' + (result ? ' success' : ' failed');
    }

    @Get('in/:id')
    signIn(@Param('id') id: string, @Query('password') password: string) {
        console.debug('Sign in\n' + 'id = '+ id + ', password = ' + password);
        const result = this.signService.login(id, password);
        return 'Sign in ' + (result ? 'success' : 'failed');
    }

    @Get('out/:id')
    signOut(@Param('id') id: string) {
        console.debug('Sign in\n' + 'id = '+ id);
        const result = this.signService.logout(id);
        return 'Sign out' + (result ? ' success' : ' failed');
    }
}

 

c. Service

DB와 ORM(Object-Relational Mapping)와 암호화에 대해서는 다음 글에서 정리하기로 하고, 이번에는 메모리에 간단히 저장하는 방식으로 Service를 구현했다. 메모리에 저장할 자료 구조는 요구사항에 있는 내용처럼 email, 패스워드, 사용자명을 저장할 수 있게 아래와 같은 내용으로 Account로 정의했다. 

 

import { ApiProperty } from "@nestjs/swagger";

export class SignUpDto {
    @ApiProperty()
    email: string;

    @ApiProperty()
    password: string;

    @ApiProperty()
    name: string;
}

 

파일은 nest CLI의 generate 명령을 사용해서 자동으로 만들 수도 있다.

 

nest g interface account auth/interface --flat

 

Service는 아래와 같이 계정정보를 저장하는 Account배열과 세션은 없지만 로그인 상태를 저장하는 isLogedin배열을 사용해서 정보를 저장했다. createAccount, login, logout 세 개의 함수로 정의하고 모두 처리 결과를 boolean으로 돌려주는 형식으로 만들었고, 모든 함수에서 공통으로 사용되는 Account검색을 위한 find를 추가로 만들었다.

 

import { Injectable } from '@nestjs/common';
import { Account } from '../interface/account.interface';

@Injectable()
export class SignService {
    accounts: Account[] = [];
    isLogedin: boolean[] = [];

    find(email: string): Account {
        return this.accounts.find(account => account.email === email);
    }

    createAccount(account: Account) : boolean {
        if (this.find(account.email) == undefined) {
            this.accounts.push(account);
            this.isLogedin.push(false);
            return true;
        }
        return false;
    }

    login(email: string, password: string): boolean {
        const account = this.find(email);
        if (account != undefined && account.password === password) {
            this.isLogedin[this.accounts.indexOf(account)] = true;
            return true;
        }
        return false;
    }

    logout(email: string): boolean {
        const account = this.find(email);
        if (account != undefined) {
            this.isLogedin[this.accounts.indexOf(account)] = false;
            return true;
        }
        return false;
    }
}

 

이제 기본적인 로직을 만들었으면, http://localhost:3000/api으로 접속해서 swagger로 API를 테스트해 볼 수 있다. swgger를 사용하더라도 테스트 케이스는 작성해서 CI (Continuous Integration)에서 검증하는 절차를 거치도록 하는 것이 좋다.

 

하나 재미있는 사실은, 만약 이전 글에서 소개한 적이 있는 Github Copilot을 사용하고 있다면, 위의 코드를 작성할 때 적어도 절반 이상은 AI가 자동으로 만들어 준다는 점이다. 개발 시간이 아마 절반이하로 줄었지 않았을까?

 

GitHub copilot; 프로그램을 대신 작성해 주는 AI

0. 개발에 이용되는 AI최근에 딸이 학교 친구들을 대상으로 GPT를 학교 숙제나 공부에 이용해 봤는지 설문조사를 해 봤는데 80% 이상이 이미 사용하고 있다고 응답했다. 또, 70% 이상의 학생들이 GPT

front-it.tistory.com