본 글에서는 RESTful API에 대한 기본적인 이해와 디자인 원칙을 소개하며, Express와 NestJS를 사용한 API 구현 방법을 제공합니다.
Representational State Transfer (REST)는 웹 상에서 지속성있는 네트워크 애플리케이션이 상호작용하는 방식을 정의한 소프트웨어 아키텍처 패턴 중 하나입니다.
REST는 웹의 기본 원칙에 기반하며, 클라이언트와 서버가 다양한 종류의 리소스를 주고받는 방식을 명세합니다.
RESTful API는 이러한 규칙과 제약 조건들을 따르는 웹 API를 의미합니다.
RESTful API 디자인에는 다양한 원칙들이 적용됩니다.
리소스를 지칭하는 URI에서는 명사를 사용하세요. 동작이 아닌 객체를 표현하며, 동작은 HTTP 메서드로 처리합니다.
주요 HTTP 메서드는 GET, POST, PUT, PATCH 및 DELETE입니다. 이들 메서드는 각각 조회, 생성, 갱신, 수정 및 삭제 동작을 수행합니다.
API 전체에서 일관된 명명 법칙과 구조를 사용하세요. 이는 사용자가 API를 이해하고 활용하는 것을 도울 것입니다.
리소스 간 관계를 URI에 반영하여 표현합니다. 예를 들어, /authors/{authorId}/books는 특정 작가의 도서를 나타낼 수 있습니다.
HTTP 상태 코드를 사용하여 클라이언트에게 요청 결과를 알리고, 성공 여부 및 실패 원인을 전달하세요.
클라이언트 요청에 따라, 서버에서 일부 데이터를 페이징 처리하거나 정렬하여 반환할 수 있습니다. 일반적으로 쿼리 파라미터를 사용합니다.
쿼리 파라미터를 사용해 데이터의 부분집합을 검색하거나 필터링하세요.
에러 처리 및 상태 코드와 함께 명확한 에러 메시지를 제공합니다. 이는 디버깅과 문제 해결을 용이하게 합니다.
간단한 온라인 도서관 API를 설계합니다.
리소스들의 계층 구조와 URI, HTTP 메서드를 정의합니다.
예를 들면:
도서 (Books)
저자 (Authors)
출판사 (Publishers)
카테고리 (Categories)
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("서버가 시작되었습니다.");
});
Express 라우터를 이용해 리소스별로 라우팅을 설정합니다.
const bookRouter = require("./routes/books");
const authorRouter = require("./routes/authors");
app.use("/books", bookRouter);
app.use("/authors", authorRouter);
레이어드 아키텍처를 기반으로, 추상화를 통해 리퀘스트 핸들링 로직을 컨트롤러에 구현합니다.
// controllers/bookController.js
exports.getAllBooks = async (req, res) => {
const books = await getAllBooksFromDatabase();
res.json(books);
};
각 요청에 알맞는 처리를 진행하고, 요청 성공 여부와 그에 맞는 응답 데이터와 상태 코드를 반환합니다.
// routes/books.js
const express = require("express");
const router = express.Router();
const bookController = require("../controllers/bookController");
router.get("/", bookController.getAllBooks);
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;
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);
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);
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();
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);
}
}
서비스 레이어에서 각 리소스에 대한 데이터 처리 로직을 작성합니다.
// 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) {
// 로직 작성: 도서 삭제
}
}
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;
}
클래스 밸리데이터를 사용해 데이터 유효성을 검사하며, 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;
}
사용자 정의 에러 핸들러를 구현해 응답 상태 코드와 명확한 에러 메시지를 전달합니다.
// 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 });
}
}
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 {}
RESTful API 작성 프로세스를 통해 웹 개발 생태계를 확장하고, 웹 애플리케이션 개발에 대한 이해를 높이는데 도움이 되기를 바랍니다.