Flutter 개발

REST API 설계: Flutter 앱과 맞물리는 백엔드 구조

프론트가 편한 API를 만드는 백엔드의 자세

John Doe
2025년 8월 10일
8분 읽기
44

좋은 API 설계란

API를 만들다 보면 백엔드 편의에 맞춰 설계하기 쉽습니다. 하지만 API의 고객은 프론트엔드입니다. Flutter 앱에서 사용하기 편한 API가 좋은 API입니다.

직접 Flutter로 앱을 만들고, 백엔드도 만들어본 입장에서 양쪽 모두에 효율적인 패턴을 정리해봤습니다.

URL 네이밍 컨벤션

text
# 좋은 예: 복수형 명사, 소문자, 하이픈 구분
GET    /api/v1/users
GET    /api/v1/users/:id
POST   /api/v1/users
PATCH  /api/v1/users/:id
DELETE /api/v1/users/:id

# 중첩 리소스
GET    /api/v1/users/:id/orders
POST   /api/v1/users/:id/orders

# 나쁜 예
GET    /api/getUser          # 동사 사용
GET    /api/user_list         # 언더스코어
POST   /api/createNewOrder    # camelCase + 동사
URL에 동사를 넣지 마세요. HTTP 메서드(GET, POST, PATCH, DELETE)가 이미 동사 역할을 해요. URL은 명사만으로 구성하는 게 REST의 핵심입니다.

응답 구조 통일하기

json
// 성공 응답 — 단건
{
  "data": {
    "id": "abc123",
    "name": "홍길동",
    "email": "hong@example.com"
  }
}

// 성공 응답 — 목록 (페이지네이션 포함)
{
  "data": [...],
  "meta": {
    "total": 142,
    "page": 1,
    "limit": 20,
    "hasNext": true
  }
}

// 에러 응답
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "이메일 형식이 올바르지 않습니다",
    "details": [
      { "field": "email", "message": "유효한 이메일을 입력해주세요" }
    ]
  }
}
모든 응답을 data/error 래퍼로 감싸면 Flutter에서 파싱이 일관돼요. freezed 모델 하나로 모든 API 응답을 처리할 수 있겠죠.

Flutter에서 응답 모델 매핑

Dart
// api_response.dart — 공통 응답 래퍼
@freezed
class ApiResponse<T> with _$ApiResponse<T> {
  const factory ApiResponse.success({
    required T data,
    PageMeta? meta,
  }) = _Success;

  const factory ApiResponse.error({
    required ApiError error,
  }) = _Error;
}

@freezed
class PageMeta with _$PageMeta {
  const factory PageMeta({
    required int total,
    required int page,
    required int limit,
    required bool hasNext,
  }) = _PageMeta;
}

커서 기반 페이지네이션

text
// offset 방식 — 간단하지만 데이터 변동 시 중복/누락 발생
GET /api/v1/posts?page=3&limit=20

// cursor 방식 — 무한 스크롤에 적합
GET /api/v1/posts?cursor=abc123&limit=20

// 응답
{
  "data": [...],
  "meta": {
    "nextCursor": "def456",
    "hasNext": true
  }
}
무한 스크롤 UI라면 커서 기반 페이지네이션이 안전해요. offset 방식은 목록이 변하면 같은 아이템이 중복 표시되거나 빠지는 문제가 있습니다. Flutter의 infinite_scroll_pagination 패키지와도 커서 방식이 잘 맞습니다.

HTTP 상태 코드 가이드

자주 쓰는 상태 코드만 정리하죠.
- 200: 조회/수정 성공
- 201: 생성 성공
- 204: 삭제 성공 (응답 본문 없음)
- 400: 요청 데이터 오류 (검증 실패)
- 401: 인증 필요 (토큰 없음/만료)
- 403: 권한 부족 (인증됐지만 접근 불가)
- 404: 리소스 없음
- 409: 충돌 (이미 존재하는 리소스)
- 500: 서버 에러

이 정도만 정확하게 쓰면 Flutter에서 에러 분기가 깔끔해집니다. 모든 에러를 200으로 보내면서 body에 status를 넣는 건 안티패턴입니다.

API 버전 관리

URL에 버전을 넣으세요. /api/v1/users 이렇게. 나중에 Breaking Change가 필요하면 v2를 만들고, 기존 v1은 유지하면 돼요.
모바일 앱은 웹과 달리 사용자가 업데이트를 안 됩니다. 1년 전 버전의 앱도 동작해야 하니 API 하위 호환성이 특히 중요해요. 필드를 추가하는 건 괜찮지만, 필드를 삭제하거나 타입을 바꾸는 건 새 버전으로 분리하세요.
#REST API
#API 설계
#백엔드
#Flutter
#NestJS