dart_frog 프로젝트 시작하기
dart_frog를 설치하고 todo_server 프로젝트를 생성합니다. 처음으로 실제 요청을 처리하는 핸들러를 작성해 봅니다.
dart_frog CLI 설치
dart pub global activate dart_frog_cli
설치 후 PATH에 ~/.pub-cache/bin이 포함되어 있어야 합니다.
dart_frog --version# dart_frog 0.3.x
프로젝트 생성
dart_frog create todo_servercd todo_server
생성된 프로젝트 구조입니다.
todo_server/
├── routes/
│ └── index.dart ← GET / 핸들러
├── lib/
│ └── todo_server.dart
├── test/
│ └── routes/
│ └── index_test.dart
├── pubspec.yaml
└── analysis_options.yaml
pubspec.yaml 확인 및 설정
# 새 파일: pubspec.yamlname: todo_serverdescription: Todo REST API Server with dart_frog.version: 1.0.0publish_to: noneenvironment: sdk: ">=3.0.0 <4.0.0"dependencies: dart_frog: ^1.4.0dev_dependencies: dart_frog_gen: ^1.6.0 lints: ^3.0.0 test: ^1.24.0 mocktail: ^1.0.3
나중에 SQLite, JWT 패키지를 추가할 예정입니다.
dart pub get
개발 서버 실행 — 핫 리로드
dart_frog dev
✓ Running on http://localhost:8080
✓ Hot reload enabled.
✓ Hot restart enabled.
다른 터미널에서 요청을 보냅니다.
curl http://localhost:8080# {"message": "Welcome to dart_frog!"}
routes/index.dart를 수정하면 서버를 재시작하지 않아도 변경이 반영됩니다.
기본 라우트 핸들러 이해
생성된 routes/index.dart를 살펴봅니다.
// 새 파일: routes/index.dart
import 'package:dart_frog/dart_frog.dart';
Response onRequest(RequestContext context) {
return Response.json(body: {'message': 'Welcome to dart_frog!'});
}
onRequest(RequestContext context)가 핸들러 함수입니다. 모든 HTTP 메서드의 요청이 이 함수로 들어옵니다. RequestContext에서 요청 정보를 꺼내고, Response를 반환합니다.
파일 기반 라우팅 규칙
dart_frog의 라우팅은 파일 시스템 구조를 따릅니다.
routes/index.dart → GET /
routes/todos/index.dart → /todos
routes/todos/[id].dart → /todos/:id (동적 세그먼트)
routes/auth/login.dart → /auth/login
[id].dart 파일이 동적 세그먼트를 처리합니다. :id 값은 핸들러에서 context.request.uri.pathSegments로 꺼내거나, 함수 매개변수로 받습니다.
라우트 디렉토리 구성
todo_server에 필요한 라우트 파일을 만듭니다.
mkdir -p routes/todos routes/authtouch routes/todos/index.darttouch routes/todos/[id].darttouch routes/auth/login.darttouch routes/auth/register.dart
첫 번째 실제 핸들러 — /todos
// 새 파일: routes/todos/index.dart
import 'package:dart_frog/dart_frog.dart';
Future<Response> onRequest(RequestContext context) async {
return switch (context.request.method) {
HttpMethod.get => _getTodos(context),
HttpMethod.post => _createTodo(context),
_ => Response(statusCode: 405),
};
}
Future<Response> _getTodos(RequestContext context) async {
// Ch 05에서 데이터베이스 연동 후 교체
final todos = [
{'id': 1, 'title': '첫 번째 할 일', 'completed': false},
{'id': 2, 'title': '두 번째 할 일', 'completed': true},
];
return Response.json(body: todos);
}
Future<Response> _createTodo(RequestContext context) async {
final body = await context.request.json() as Map<String, dynamic>;
final title = body['title'] as String?;
if (title == null || title.isEmpty) {
return Response.json(
statusCode: 400,
body: {'error': 'title은 필수입니다.'},
);
}
// Ch 05에서 실제 저장 구현
final newTodo = {
'id': 3,
'title': title,
'completed': false,
};
return Response.json(statusCode: 201, body: newTodo);
}
// 새 파일: routes/todos/[id].dart
import 'package:dart_frog/dart_frog.dart';
Future<Response> onRequest(RequestContext context, String id) async {
final todoId = int.tryParse(id);
if (todoId == null) {
return Response.json(
statusCode: 400,
body: {'error': '유효하지 않은 ID입니다.'},
);
}
return switch (context.request.method) {
HttpMethod.get => _getTodo(context, todoId),
HttpMethod.put => _updateTodo(context, todoId),
HttpMethod.delete => _deleteTodo(context, todoId),
_ => Response(statusCode: 405),
};
}
Future<Response> _getTodo(RequestContext context, int id) async {
// Ch 05에서 DB 조회로 교체
if (id > 2) {
return Response.json(
statusCode: 404,
body: {'error': '할 일을 찾을 수 없습니다.'},
);
}
return Response.json(body: {'id': id, 'title': '할 일 $id', 'completed': false});
}
Future<Response> _updateTodo(RequestContext context, int id) async {
final body = await context.request.json() as Map<String, dynamic>;
return Response.json(body: {'id': id, ...body});
}
Future<Response> _deleteTodo(RequestContext context, int id) async {
return Response(statusCode: 204);
}
RequestContext 주요 API
// HTTP 메서드 확인
context.request.method; // HttpMethod.get, .post, ...
// URL 정보
context.request.uri; // Uri 객체
context.request.uri.path; // '/todos/1'
context.request.uri.queryParameters; // {'limit': '10'}
// 헤더
context.request.headers; // Map<String, String>
// 요청 본문
final body = await context.request.body(); // 원시 String
final json = await context.request.json(); // dynamic (Map 또는 List)
final bytes = await context.request.bytes(); // Uint8List
// 의존성 읽기 (미들웨어에서 주입한 값)
final db = context.read<Database>();
Response 생성
// 기본 응답
Response(statusCode: 200, body: 'Hello');
// JSON 응답 (Content-Type 자동 설정)
Response.json(body: {'key': 'value'});
Response.json(statusCode: 201, body: newItem);
// 에러 응답
Response(statusCode: 404);
Response.json(statusCode: 422, body: {'error': '유효성 오류'});
// 헤더 포함
Response(
statusCode: 200,
body: 'content',
headers: {'X-Custom-Header': 'value'},
);
프로젝트 구조 설계
전체 todo_server 구조를 미리 설계합니다.
todo_server/
├── routes/
│ ├── index.dart
│ ├── _middleware.dart ← 전역 미들웨어
│ ├── todos/
│ │ ├── _middleware.dart ← /todos 인증 미들웨어
│ │ ├── index.dart ← GET/POST /todos
│ │ └── [id].dart ← GET/PUT/DELETE /todos/:id
│ └── auth/
│ ├── login.dart ← POST /auth/login
│ └── register.dart ← POST /auth/register
├── lib/
│ └── src/
│ ├── models/
│ │ ├── todo.dart
│ │ └── user.dart
│ ├── repositories/
│ │ ├── todo_repository.dart
│ │ └── user_repository.dart
│ ├── services/
│ │ ├── auth_service.dart
│ │ └── todo_service.dart
│ └── database.dart
├── test/
├── pubspec.yaml
└── Dockerfile
실행 확인
# 개발 서버 시작dart_frog dev# 다른 터미널에서 테스트curl -s http://localhost:8080/todos | python3 -m json.toolcurl -s -X POST http://localhost:8080/todos \ -H "Content-Type: application/json" \ -d '{"title": "첫 번째 할 일"}' | python3 -m json.toolcurl -s http://localhost:8080/todos/1 | python3 -m json.toolcurl -s http://localhost:8080/todos/99 | python3 -m json.tool
빌드
배포용 실행 파일을 만들 때는 dart_frog build를 사용합니다.
dart_frog build
.dart_frog/server.dart에 실행 가능한 서버 파일이 생성됩니다. dart compile exe와 동일한 방식으로 네이티브 바이너리로 컴파일할 수 있습니다.
정리
이번 챕터에서는 dart_frog 프로젝트를 생성하고 기본 구조를 잡았습니다.
dart_frog create로 프로젝트를 생성하고dart_frog dev로 핫 리로드 서버를 실행합니다.- 파일 경로가 URL 경로입니다.
[id].dart로 동적 세그먼트를 처리합니다. onRequest(RequestContext context, String id)함수가 핸들러입니다.Response.json()으로 JSON 응답을 쉽게 만듭니다.
다음 챕터에서는 미들웨어로 CORS, 로깅, 인증을 처리합니다.