iBetter Books
수정

Dart 서버 생태계 — shelf vs dart_frog

PART 06에서는 두 번째 프로젝트를 만듭니다. 할 일을 관리하는 REST API 서버, todo_server입니다. Dart로 서버를 만들기 전에 생태계를 먼저 살펴봅니다. 선택지를 이해하면 왜 이 책에서 dart_frog를 선택했는지 납득할 수 있습니다.

Dart 서버 프레임워크 지형

Dart 서버 생태계의 주요 선택지는 두 가지입니다.

shelf

Dart 팀이 직접 관리하는 공식 HTTP 서버 라이브러리입니다. package:shelf는 저수준 미들웨어 파이프라인을 제공합니다. Express.js와 비슷한 철학입니다. Dart 서버 생태계의 근간이며, dart_frog도 내부적으로 shelf를 사용합니다.

dart_frog

Very Good Ventures가 만든 프레임워크입니다. 파일 기반 라우팅, 핫 리로드, 개발 도구를 제공합니다. Next.js의 철학을 Dart에 가져왔습니다.

shelf 살펴보기

shelf로 간단한 서버를 만들어 보겠습니다. 어떤 코드를 작성해야 하는지 보면 특성이 드러납니다.

dependencies:  shelf: ^1.4.1  shelf_router: ^1.1.4
import 'dart:io';

import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart';

void main() async {
  final router = Router();

  router.get('/todos', (Request request) {
    return Response.ok(
      '[{"id": 1, "title": "할 일 1"}]',
      headers: {'Content-Type': 'application/json'},
    );
  });

  router.post('/todos', (Request request) async {
    final body = await request.readAsString();
    // 파싱 및 저장 로직
    return Response(201, body: body);
  });

  // 미들웨어 파이프라인
  final handler = Pipeline()
      .addMiddleware(logRequests())
      .addHandler(router.call);

  final server = await io.serve(handler, 'localhost', 8080);
  print('서버 실행: http://${server.address.host}:${server.port}');
}

shelf는 명확하고 예측 가능합니다. 모든 것이 코드로 명시됩니다. 라우트를 추가할수록 router.get, router.post 코드가 늘어납니다.

dart_frog 살펴보기

dart_frog는 같은 서버를 다르게 구성합니다.

routes/
├── index.dart          → GET /
└── todos/
    ├── index.dart      → GET /todos, POST /todos
    └── [id].dart       → GET /todos/:id, PUT /todos/:id, DELETE /todos/:id

파일 위치가 곧 URL 경로입니다. routes/todos/index.dart 파일이 /todos 엔드포인트를 처리합니다. 라우트 목록을 별도로 관리할 필요가 없습니다.

// routes/todos/index.dart
import 'package:dart_frog/dart_frog.dart';

Response onRequest(RequestContext context) {
  return switch (context.request.method) {
    HttpMethod.get => _getTodos(context),
    HttpMethod.post => _createTodo(context),
    _ => Response(statusCode: 405),
  };
}

두 프레임워크 비교

항목 shelf dart_frog
라우팅 방식 코드 기반 파일 기반
핫 리로드 직접 구성 기본 제공
개발 CLI 없음 dart_frog dev
학습 곡선 낮음 (저수준) 중간 (규칙 있음)
유연성 매우 높음 높음
코드 생성 없음 자동 (라우트 맵)
공식 지원 Dart 팀 Very Good Ventures
적합한 규모 소형~중형 중형~대형

dart_frog를 선택하는 이유

이 책에서 dart_frog를 선택한 이유는 세 가지입니다.

첫째, 생산성입니다. 파일 기반 라우팅은 라우트가 늘어날수록 유리합니다. 파일을 만들면 라우트가 생깁니다. 파일을 삭제하면 라우트가 사라집니다. 라우트 등록 코드를 관리하지 않아도 됩니다.

둘째, 개발 경험입니다. dart_frog dev 명령어 하나로 핫 리로드 서버가 실행됩니다. 코드를 수정하면 자동으로 반영됩니다. 서버를 재시작할 필요가 없습니다.

셋째, 실무 패턴입니다. 미들웨어, 의존성 주입, 에러 처리가 구조화되어 있습니다. shelf의 저수준 미들웨어를 직접 구성하는 것보다 명확합니다.

shelf를 배울 이유가 없다는 뜻이 아닙니다. dart_frogshelf 위에 구축되어 있으므로 dart_frog를 이해하면 shelf도 이해할 수 있습니다. 그리고 dart_frog가 제공하지 않는 세밀한 제어가 필요할 때 shelf API를 직접 사용할 수 있습니다.

todo_server 아키텍처 개요

이번 프로젝트에서 만들 API 서버의 전체 구조입니다.

GET    /todos              모든 할 일 조회
POST   /todos              할 일 생성
GET    /todos/:id          특정 할 일 조회
PUT    /todos/:id          할 일 수정
DELETE /todos/:id          할 일 삭제

POST   /auth/register      회원 가입
POST   /auth/login         로그인 (JWT 발급)

GET    /todos              (인증 필요) 내 할 일 조회

데이터는 SQLite에 저장합니다. JWT로 인증을 처리합니다. 레이어는 세 가지입니다.

라우트 핸들러 (routes/)
      ↓
서비스 레이어 (lib/src/services/)
      ↓
리포지토리 레이어 (lib/src/repositories/)
      ↓
SQLite (데이터베이스)

정리

이번 챕터에서는 Dart 서버 생태계를 살펴보고 dart_frog를 선택하는 이유를 확인했습니다.

  • shelf는 저수준 공식 라이브러리, dart_frog는 그 위의 고수준 프레임워크입니다.
  • 파일 기반 라우팅, 내장 핫 리로드, 개발 CLI가 dart_frog의 핵심 장점입니다.
  • todo_serverdart_frog + SQLite + JWT 조합으로 만듭니다.

다음 챕터에서는 dart_frog를 설치하고 프로젝트를 생성합니다.