RESTful API 개발 가이드/디자인 원칙

bshunter·2023년 8월 1일
0

들어가며

본 글에서는 RESTful API에 대한 기본적인 이해와 디자인 원칙을 소개하며, Express와 NestJS를 사용한 API 구현 방법을 제공합니다.

1. REST란 무엇인가?

Representational State Transfer (REST)는 웹 상에서 지속성있는 네트워크 애플리케이션이 상호작용하는 방식을 정의한 소프트웨어 아키텍처 패턴 중 하나입니다.
REST는 웹의 기본 원칙에 기반하며, 클라이언트와 서버가 다양한 종류의 리소스를 주고받는 방식을 명세합니다.
RESTful API는 이러한 규칙과 제약 조건들을 따르는 웹 API를 의미합니다.

2. RESTful API 디자인 원칙

RESTful API 디자인에는 다양한 원칙들이 적용됩니다.

중요한 원칙 몇 가지를 살펴봅시다:

A. 자원을 명시적으로 표현하기 (명사 사용)

리소스를 지칭하는 URI에서는 명사를 사용하세요. 동작이 아닌 객체를 표현하며, 동작은 HTTP 메서드로 처리합니다.

B. HTTP 메서드를 사용하여 동작을 결정하기

주요 HTTP 메서드는 GET, POST, PUT, PATCH 및 DELETE입니다. 이들 메서드는 각각 조회, 생성, 갱신, 수정 및 삭제 동작을 수행합니다.

C. URI에 대한 일관성 유지하기

API 전체에서 일관된 명명 법칙과 구조를 사용하세요. 이는 사용자가 API를 이해하고 활용하는 것을 도울 것입니다.

D. 자원에 대한 계층 구조 표현

리소스 간 관계를 URI에 반영하여 표현합니다. 예를 들어, /authors/{authorId}/books는 특정 작가의 도서를 나타낼 수 있습니다.

E. 상태 코드를 사용하여 상태 전달

HTTP 상태 코드를 사용하여 클라이언트에게 요청 결과를 알리고, 성공 여부 및 실패 원인을 전달하세요.

F. 페이징 및 정렬 처리

클라이언트 요청에 따라, 서버에서 일부 데이터를 페이징 처리하거나 정렬하여 반환할 수 있습니다. 일반적으로 쿼리 파라미터를 사용합니다.

G. 필터링 및 탐색을 위한 쿼리 파라미터 사용

쿼리 파라미터를 사용해 데이터의 부분집합을 검색하거나 필터링하세요.

H. 에러 처리와 에러 메시지 표준화

에러 처리 및 상태 코드와 함께 명확한 에러 메시지를 제공합니다. 이는 디버깅과 문제 해결을 용이하게 합니다.

4. 예제: RESTful API 구현

간단한 온라인 도서관 API를 설계합니다.
리소스들의 계층 구조와 URI, HTTP 메서드를 정의합니다.
예를 들면:
도서 (Books)
저자 (Authors)
출판사 (Publishers)
카테고리 (Categories)

5. Express로 RESTful API 구현하기

Express 프레임워크를 사용하여 예제의 RESTful API를 구현합니다.

const express = require("express");
const app = express();

app.get("/books", (req, res) => {
  // 로직 작성: 도서 조회
});

app.post("/books", (req, res) => {
  // 로직 작성: 도서 생성
});

app.put("/books/:id", (req, res) => {
  // 로직 작성: 도서 갱신
});

app.patch("/books/:id", (req, res) => {
  // 로직 작성: 도서 수정
});

app.delete("/books/:id", (req, res) => {
  // 로직 작성: 도서 삭제
});

app.listen(3000, () => {
  console.log("서버가 시작되었습니다.");
});

A. 라우터 설정

Express 라우터를 이용해 리소스별로 라우팅을 설정합니다.

const bookRouter = require("./routes/books");
const authorRouter = require("./routes/authors");
app.use("/books", bookRouter);
app.use("/authors", authorRouter);

B. 컨트롤러 생성

레이어드 아키텍처를 기반으로, 추상화를 통해 리퀘스트 핸들링 로직을 컨트롤러에 구현합니다.

// controllers/bookController.js
exports.getAllBooks = async (req, res) => {
    const books = await getAllBooksFromDatabase();
    res.json(books);
};

C. 요청 처리 및 응답 반환

각 요청에 알맞는 처리를 진행하고, 요청 성공 여부와 그에 맞는 응답 데이터와 상태 코드를 반환합니다.

// routes/books.js
const express = require("express");
const router = express.Router();
const bookController = require("../controllers/bookController");

router.get("/", bookController.getAllBooks);

D. 모델 정의 및 데이터베이스 연동

NoSQL(MongoDB) 또는 관계형 데이터베이스(MySQL, PostgreSQL)를 사용해 모델의 구조를 정의하고, 데이터를 저장 및 검색하는데 사용합니다.

// models/bookModel.js
const mongoose = require("mongoose");
const { Schema } = mongoose;

const bookSchema = new Schema({
  title: String,
  author: String,
  publisher: String,
  category: String,
});

const Book = mongoose.model("Book", bookSchema);

module.exports = Book;

E. 에러 처리 및 상태 코드 전달

try-catch 구문과 함께 사용자 정의 에러 핸들러를 생성하여 에러를 효율적으로 처리하고 올바른 상태 코드를 전달합니다.

// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send("Internal Server Error");
}

app.use(errorHandler);

F. 사용자 인증 구현 (옵션)

JWT, OAuth, Passport.js 등의 방법을 사용해 인증 및 권한을 구현합니다.

const jwt = require("jsonwebtoken");
const SECRET = process.env.SECRET_KEY;

function authenticate(req, res, next) {
  const token = req.header("Authorization").split(" ")[1];
  try {
    const decoded = jwt.verify(token, SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).send("Unauthorized");
  }
}

app.use(authenticate);

6. NestJS로 RESTful API 구현하기

NestJS 프레임워크로 예제의 RESTful API를 구현합니다.

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

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

A. 모듈, 컨트롤러 생성

NestJS의 모듈 및 컨트롤러를 사용하여 애플리케이션 로직을 구축합니다.

// books/books.controller.ts
import { Controller, Get, Post, Put, Patch, Delete, Param, Body } from '@nestjs/common';
import { BooksService } from './books.service';
import { CreateBookDto } from './dto/create-book.dto';

@Controller('books')
export class BooksController {
  constructor(private readonly booksService: BooksService) {}

  @Get()
  findAll() {
    return this.booksService.findAll();
  }

  @Post()
  create(@Body() createBookDto: CreateBookDto) {
    return this.booksService.create(createBookDto);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateBookDto: UpdateBookDto) {
    return this.booksService.update(id, updateBookDto);
  }

  @Patch(':id')
  modify(@Param('id') id: string, @Body() modifyBookDto: ModifyBookDto) {
    return this.booksService.modify(id, modifyBookDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.booksService.remove(id);
  }
}

B. 서비스 작성

서비스 레이어에서 각 리소스에 대한 데이터 처리 로직을 작성합니다.

// books/books.service.ts
import { Injectable } from '@nestjs/common';
import { Book } from './interfaces/book.interface';

@Injectable()
export class BooksService {
  private readonly books: Book[] = [];

  findAll(): Book[] {
    return this.books;
  }

  create(book: Book) {
    this.books.push(book);
  }

  update(id: string, book: Book) {
    // 로직 작성: 도서 갱신
  }

  modify(id: string, book: Partial<Book>) {
    // 로직 작성: 도서 수정
  }

  remove(id: string) {
    // 로직 작성: 도서 삭제
  }
}

C. TypeORM을 사용한 엔티티 및 리파지터리 작성

TypeORM을 사용하여 데이터베이스 테이블과 매핑되는 엔티티 클래스와 리파지터리 패턴을 작성합니다.

// books/entities/book.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class Book {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 60 })
  title: string;

  @Column()
  author: string;

  @Column()
  publisher: string;

  @Column()
  category: string;
}

D. 데이터 유효성 검사를 위한 클래스 밸리데이터 사용

클래스 밸리데이터를 사용해 데이터 유효성을 검사하며, API 가이드에 따른 에러 메시지를 전달합니다.

// books/dto/create-book.dto.ts
import { IsString, Length } from 'class-validator';

export class CreateBookDto {
  @IsString()
  @Length(1, 60)
  readonly title: string;

  @IsString()
  readonly author: string;

  @IsString()
  readonly publisher: string;

  @IsString()
  readonly category: string;
}

E. 에러 처리 기능

사용자 정의 에러 핸들러를 구현해 응답 상태 코드와 명확한 에러 메시지를 전달합니다.

// app/exceptions/http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const status = exception.getStatus();

    response.status(status).json({ message: exception.message });
  }
}

F. 인증 및 인가 구현 (옵션)

JWT, OAuth, Passport.js 등의 방법을 사용해 인증 및 권한을 구현합니다.

import { JwtModule, JwtService } from '@nestjs/jwt';

@Module({
  imports: [
    JwtModule.register({
      secret: process.env.SECRET_KEY,
      signOptions: { expiresIn: '1h' },
    }),
  ],
  providers: [AuthService],
  exports: [AuthService],
})
export class AuthModule {}

7. 마치며

RESTful API 작성 프로세스를 통해 웹 개발 생태계를 확장하고, 웹 애플리케이션 개발에 대한 이해를 높이는데 도움이 되기를 바랍니다.

0개의 댓글