0528 풀스택 실무 기초 (2/N): REST API 설계와 요청/응답 구조
✅ 1. REST API란 무엇인가?
- REST API는 클라이언트와 서버가 HTTP를 기반으로 데이터를 주고받기 위한 API 설계 방식입니다.
- REST는 Representational State Transfer의 약자입니다.
- 쉽게 말하면, 서버의 데이터를 URL로 표현하고 HTTP 메서드로 작업을 구분하는 방식입니다.
➕ 1-1. API란?
- API(Application Programming Interface)는 서로 다른 프로그램이 데이터를 주고받기 위한 약속입니다.
- 프론트엔드 React 앱은 백엔드 NestJS 서버에 API를 호출해서 데이터를 가져오거나 저장합니다.
React 화면
↓ API 요청
NestJS 서버
↓ DB 조회/저장
Database
↓ 결과 반환
NestJS 서버
↓ API 응답
React 화면
- 예를 들어 사용자가 상담 신청을 하면 React는 백엔드에 다음과 같은 API 요청을 보낼 수 있습니다.
POST /api/consults
Content-Type: application/json
{
"name": "홍길동",
"phone": "01012345678",
"model": "Galaxy S25"
}
✅ 2. REST API의 핵심 개념
➕ 2-1. 리소스(Resource)
- REST API에서는 서버가 관리하는 데이터를 리소스라고 부릅니다.
- 리소스는 보통 URL 경로로 표현합니다.
| 리소스 | URL 예시 |
|---|
| 사용자 | /users |
| 상품 | /products |
| 주문 | /orders |
| 상담 신청 | /consults |
| 이벤트 | /events |
| 배너 | /banners |
- URL은 “무엇을 다루는지”를 표현해야 합니다.
- 동작을 URL에 넣기보다는 HTTP 메서드로 구분하는 것이 좋습니다.
좋은 예시:
GET /api/products
POST /api/products
PATCH /api/products/10
DELETE /api/products/10
아쉬운 예시:
GET /api/getProducts
POST /api/createProduct
POST /api/updateProduct
POST /api/deleteProduct
➕ 2-2. HTTP 메서드로 작업 구분하기
- REST API에서는 같은 URL이라도 HTTP 메서드에 따라 의미가 달라집니다.
| 메서드 | 의미 | 예시 |
|---|
GET | 조회 | 상품 목록 조회 |
POST | 생성 | 새 상품 등록 |
PUT | 전체 수정 | 상품 전체 정보 교체 |
PATCH | 부분 수정 | 상품 가격만 수정 |
DELETE | 삭제 | 상품 삭제 |
GET /api/products
POST /api/products
PATCH /api/products/10
- ID가 10번인 상품의 일부 정보를 수정합니다.
DELETE /api/products/10
✅ 3. URL 설계 규칙
- API URL은 프론트엔드와 백엔드가 함께 사용하는 계약입니다.
- 한 번 배포된 API URL은 쉽게 바꾸기 어렵기 때문에 처음부터 일관성 있게 설계해야 합니다.
➕ 3-1. 명사는 복수형으로 작성
- REST API에서는 리소스 이름을 보통 복수형으로 작성합니다.
/users
/products
/orders
/consults
/events
- 하나의 리소스를 조회할 때는 ID를 붙입니다.
/users/1
/products/10
/orders/100
/consults/50
➕ 3-2. URL에는 동사보다 명사를 사용
- URL에는
create, update, delete, get 같은 동사를 넣기보다 HTTP 메서드로 작업을 표현합니다.
좋은 예시:
GET /api/consults
POST /api/consults
PATCH /api/consults/10
DELETE /api/consults/10
아쉬운 예시:
GET /api/getConsults
POST /api/createConsult
POST /api/updateConsult
POST /api/deleteConsult
➕ 3-3. 계층 구조는 필요한 만큼만 사용
- 리소스 간 관계가 있을 때는 URL에 계층 구조를 사용할 수 있습니다.
GET /api/users/1/orders
GET /api/events/3/banners
GET /api/products/10/reviews
- 하지만 계층이 너무 깊어지면 API가 복잡해집니다.
너무 깊은 예시:
GET /api/users/1/orders/5/products/10/options/3
- 실무에서는 너무 깊은 URL보다 쿼리 파라미터를 활용하는 것이 더 관리하기 쉬운 경우도 많습니다.
GET /api/order-items?userId=1&orderId=5
✅ 4. 쿼리 파라미터와 Path Parameter
➕ 4-1. Path Parameter
- Path Parameter는 특정 리소스를 식별할 때 사용합니다.
- URL 경로 안에 ID가 들어가는 형태입니다.
GET /api/products/10
- 위 요청은 ID가 10번인 상품을 조회한다는 뜻입니다.
@Get(':id')
findOne(@Param('id') id: string) {
return this.productsService.findOne(Number(id));
}
➕ 4-2. Query Parameter
- Query Parameter는 조회 조건, 검색어, 정렬, 페이지네이션 등에 사용합니다.
- URL 뒤에
?key=value 형태로 붙습니다.
GET /api/products?page=1&limit=20&keyword=galaxy&sort=latest
| 파라미터 | 의미 |
|---|
page=1 | 1페이지 |
limit=20 | 20개씩 조회 |
keyword=galaxy | galaxy 검색 |
sort=latest | 최신순 정렬 |
@Get()
findAll(
@Query('page') page: string,
@Query('limit') limit: string,
@Query('keyword') keyword: string,
) {
return this.productsService.findAll({
page: Number(page),
limit: Number(limit),
keyword,
});
}
✅ 5. 요청 데이터 설계
- API 요청 데이터는 서버가 처리하기 쉽게 명확한 구조로 보내야 합니다.
- 프론트엔드와 백엔드가 요청 필드 이름, 타입, 필수 여부를 맞춰야 합니다.
➕ 5-1. 상담 신청 API 요청 예시
POST /api/consults
Content-Type: application/json
{
"name": "홍길동",
"phone": "01012345678",
"model": "Galaxy S25",
"telecom": "LG U+",
"agreeMarketing": true
}
➕ 5-2. 요청 데이터 설계 시 확인할 것
- 필수값과 선택값이 구분되어 있는가?
- 문자열, 숫자, boolean 타입이 명확한가?
- 프론트엔드 필드명과 백엔드 DTO 필드명이 일치하는가?
- 개인정보가 포함되는가?
- 서버에서 검증해야 하는 값인가?
- 사용자가 조작하면 안 되는 값이 포함되어 있지 않은가?
- 특히 가격, 권한, 상태값처럼 중요한 값은 프론트엔드에서 보냈다고 그대로 믿으면 안 됩니다.
- 서버에서 반드시 다시 계산하거나 검증해야 합니다.
✅ 6. 응답 데이터 설계
- API 응답 구조가 일정해야 프론트엔드에서 처리하기 쉽습니다.
- 성공 응답과 실패 응답의 형식이 제각각이면 화면마다 예외 처리가 늘어나고 유지보수가 어려워집니다.
➕ 6-1. 성공 응답 예시
{
"success": true,
"message": "상담 신청이 완료되었습니다.",
"data": {
"id": 101,
"createdAt": "2026-05-28T10:30:00.000Z"
}
}
➕ 6-2. 목록 응답 예시
{
"success": true,
"message": "상품 목록 조회에 성공했습니다.",
"data": {
"items": [
{
"id": 1,
"name": "Galaxy S25",
"price": 1200000
},
{
"id": 2,
"name": "iPhone 16",
"price": 1300000
}
],
"pagination": {
"page": 1,
"limit": 20,
"totalCount": 52,
"totalPages": 3
}
}
}
➕ 6-3. 실패 응답 예시
{
"success": false,
"message": "전화번호 형식이 올바르지 않습니다.",
"errorCode": "INVALID_PHONE_NUMBER",
"statusCode": 400
}
-
좋은 실패 응답의 조건
- 사용자가 이해할 수 있는 메시지가 있어야 합니다.
- 개발자가 디버깅할 수 있는 에러 코드가 있어야 합니다.
- HTTP 상태 코드와 응답 내용이 맞아야 합니다.
- 민감한 서버 내부 정보는 노출하지 않아야 합니다.
✅ 7. DTO와 유효성 검증
- DTO(Data Transfer Object)는 클라이언트와 서버 사이에서 주고받는 데이터 구조를 정의한 객체입니다.
- NestJS에서는 DTO와
class-validator를 함께 사용해 요청 데이터를 검증할 수 있습니다.
➕ 7-1. DTO가 필요한 이유
- 요청 데이터 구조를 명확히 정의할 수 있습니다.
- 필수값 누락을 막을 수 있습니다.
- 타입과 형식을 검증할 수 있습니다.
- Controller 코드가 깔끔해집니다.
- API 문서화에도 도움이 됩니다.
➕ 7-2. NestJS DTO 예시
import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator';
export class CreateConsultDto {
@IsString()
@IsNotEmpty()
name: string;
@IsString()
@IsNotEmpty()
phone: string;
@IsString()
@IsNotEmpty()
model: string;
@IsString()
@IsOptional()
telecom?: string;
@IsBoolean()
agreeMarketing: boolean;
}
➕ 7-3. Controller 적용 예시
import { Body, Controller, Post } from '@nestjs/common';
import { CreateConsultDto } from './dto/create-consult.dto';
@Controller('consults')
export class ConsultsController {
@Post()
create(@Body() dto: CreateConsultDto) {
return {
success: true,
message: '상담 신청이 완료되었습니다.',
data: dto,
};
}
}
- DTO를 사용하면 서버가 기대하는 요청 구조를 명확하게 만들 수 있습니다.
- 단, TypeScript 타입만으로는 런타임 검증이 되지 않기 때문에
class-validator 같은 검증 도구가 필요합니다.
✅ 8. 페이지네이션 설계
- 목록 API는 데이터가 많아질 수 있으므로 페이지네이션이 필요합니다.
- 페이지네이션 없이 전체 데이터를 한 번에 내려주면 서버, DB, 브라우저 모두 부담이 커집니다.
➕ 8-1. 기본 페이지네이션 요청
GET /api/consults?page=1&limit=20
| 파라미터 | 설명 |
|---|
page | 현재 페이지 |
limit | 한 페이지당 데이터 개수 |
➕ 8-2. 페이지네이션 응답
{
"success": true,
"data": {
"items": [
{
"id": 1,
"name": "홍길동",
"phone": "01012345678"
}
],
"pagination": {
"page": 1,
"limit": 20,
"totalCount": 135,
"totalPages": 7
}
}
}
➕ 8-3. 실무 주의점
limit 최대값을 제한해야 합니다.
- 사용자가
limit=100000처럼 요청하면 서버와 DB에 큰 부하가 생길 수 있습니다.
- 관리자 페이지에서는 검색 조건, 정렬, 날짜 필터가 함께 들어가는 경우가 많습니다.
GET /api/consults?page=1&limit=20&status=pending&startDate=2026-05-01&endDate=2026-05-28
✅ 9. REST API 설계 실무 예시
➕ 9-1. 상담 신청 API
| 기능 | 메서드 | URL |
|---|
| 상담 신청 등록 | POST | /api/consults |
| 상담 신청 목록 조회 | GET | /api/consults |
| 상담 신청 상세 조회 | GET | /api/consults/:id |
| 상담 상태 변경 | PATCH | /api/consults/:id/status |
| 상담 메모 수정 | PATCH | /api/consults/:id/memo |
| 상담 신청 삭제 | DELETE | /api/consults/:id |
➕ 9-2. 상품 API
| 기능 | 메서드 | URL |
|---|
| 상품 목록 조회 | GET | /api/products |
| 상품 상세 조회 | GET | /api/products/:id |
| 상품 등록 | POST | /api/products |
| 상품 수정 | PATCH | /api/products/:id |
| 상품 삭제 | DELETE | /api/products/:id |
| 상품 노출 여부 변경 | PATCH | /api/products/:id/visibility |
➕ 9-3. 배너 API
| 기능 | 메서드 | URL |
|---|
| 배너 목록 조회 | GET | /api/banners |
| 배너 등록 | POST | /api/banners |
| 배너 수정 | PATCH | /api/banners/:id |
| 배너 삭제 | DELETE | /api/banners/:id |
| 배너 순서 변경 | PATCH | /api/banners/order |
- 완벽하게 REST 원칙을 지키는 것보다 더 중요한 것은 일관성입니다.
- 팀이나 프로젝트 안에서 URL, 메서드, 응답 형식이 일정해야 유지보수가 쉬워집니다.
✅ 10. API 문서화
- API는 프론트엔드와 백엔드가 함께 사용하는 약속이므로 문서화가 중요합니다.
- 문서가 없으면 프론트엔드 개발자는 Network 탭이나 백엔드 코드를 뒤져야 하고, 백엔드는 같은 설명을 반복하게 됩니다.
➕ 10-1. 문서에 포함할 내용
- API 이름
- HTTP 메서드
- URL
- 요청 헤더
- 요청 파라미터
- 요청 바디
- 성공 응답 예시
- 실패 응답 예시
- 인증 필요 여부
- 권한 조건
➕ 10-2. 간단한 API 문서 예시
## 상담 신청 등록 API
### Method
POST
### URL
/api/consults
### Request Body
{
"name": "홍길동",
"phone": "01012345678",
"model": "Galaxy S25",
"agreeMarketing": true
}
### Response 201
{
"success": true,
"message": "상담 신청이 완료되었습니다.",
"data": {
"id": 101
}
}
### Error 400
{
"success": false,
"message": "전화번호 형식이 올바르지 않습니다.",
"errorCode": "INVALID_PHONE_NUMBER"
}
➕ 10-3. 문서화 도구
-
Swagger / OpenAPI
- 백엔드 코드 기반으로 API 문서를 자동 생성할 수 있습니다.
- NestJS와 함께 많이 사용합니다.
-
Postman
- API 요청을 직접 테스트하고 팀과 공유할 수 있습니다.
-
Notion / GitHub Wiki
- 간단한 설명, 정책, 예외 케이스를 정리하기 좋습니다.
✅ 11. REST API 설계 시 주의할 점
➕ 11-1. 프론트엔드 값을 무조건 믿지 않기
- 브라우저에서 보내는 값은 사용자가 조작할 수 있습니다.
- 가격, 할인율, 권한, 상태값, 사용자 ID 같은 중요한 값은 서버에서 검증해야 합니다.
위험한 예시:
프론트엔드에서 finalPrice=1000을 보내고 서버가 그대로 주문 저장
안전한 예시:
프론트엔드는 productId와 optionId만 보내고, 서버가 DB 기준으로 가격 계산
➕ 11-2. 에러 메시지에 내부 정보를 노출하지 않기
- 에러 응답에 DB 구조, SQL 문, 서버 경로, 스택 트레이스가 그대로 노출되면 보안 위험이 생깁니다.
{
"success": false,
"message": "서버 처리 중 오류가 발생했습니다."
}
- 상세 오류는 서버 로그에 남기고, 사용자에게는 필요한 수준의 메시지만 보여주는 것이 좋습니다.
➕ 11-3. API 버전 관리 고려하기
- 운영 중인 API는 프론트엔드, 모바일 앱, 외부 연동 서비스에서 사용될 수 있습니다.
- API 구조를 갑자기 바꾸면 기존 클라이언트가 깨질 수 있습니다.
/api/v1/products
/api/v2/products
- 작은 프로젝트에서는 처음부터 과하게 버전을 나눌 필요는 없지만, 외부 연동이 많거나 모바일 앱이 있는 서비스에서는 버전 관리가 중요합니다.
✅ 12. 실무 체크리스트
➕ 12-1. API 설계 체크리스트
- URL이 리소스 중심으로 설계되었는가?
- HTTP 메서드가 작업 의미와 맞는가?
- Path Parameter와 Query Parameter가 적절히 구분되었는가?
- 요청 DTO가 정의되어 있는가?
- 필수값 검증이 적용되어 있는가?
- 성공 응답과 실패 응답 구조가 일정한가?
- 목록 API에 페이지네이션이 적용되어 있는가?
- 인증과 권한 체크가 필요한 API가 구분되어 있는가?
- 프론트엔드에서 조작 가능한 값을 서버가 다시 검증하는가?
- API 문서가 작성되어 있는가?
➕ 12-2. API 구현 후 테스트 체크리스트
- 정상 요청이 성공하는가?
- 필수값이 없을 때 400 오류가 나는가?
- 존재하지 않는 ID 요청 시 404 오류가 나는가?
- 권한 없는 사용자가 접근할 때 403 오류가 나는가?
- 응답 데이터 구조가 프론트엔드 기대값과 일치하는가?
- 페이지네이션 totalCount가 정확한가?
- 검색 조건과 정렬이 정상 동작하는가?
- 서버 로그에 불필요한 에러가 남지 않는가?
- Swagger나 Postman 문서가 최신 상태인가?
- 모바일/운영 환경에서도 같은 API가 정상 동작하는가?
📌 요약
- REST API는 HTTP 기반으로 클라이언트와 서버가 데이터를 주고받기 위한 API 설계 방식입니다.
- REST API에서는 데이터를 리소스로 보고, URL은 명사 중심으로 설계하며, 작업은 HTTP 메서드로 구분합니다.
Path Parameter는 특정 리소스를 식별할 때 사용하고, Query Parameter는 검색, 정렬, 필터, 페이지네이션 조건에 사용합니다.
- 요청 데이터는 DTO로 구조화하고, 서버에서 반드시 유효성 검증을 해야 합니다.
- 응답 데이터는 성공/실패 구조를 일관되게 설계해야 프론트엔드에서 처리하기 쉽습니다.
- 목록 API에는 페이지네이션을 적용해야 하며,
limit 최대값을 제한해 서버와 DB 부하를 막아야 합니다.
- API 문서화는 프론트엔드와 백엔드 사이의 약속을 명확하게 만드는 작업이며, Swagger, Postman, Notion 등을 활용할 수 있습니다.
- 실무에서는 완벽한 REST보다 일관성, 검증, 문서화, 보안이 더 중요합니다.