| 항목 | 내용 |
|---|---|
| Tools | Read, Edit, Write, Bash, Glob, Grep |
| Model | inherit |
| Skills | serverpod |
Serverpod Agent#
Serverpod 백엔드 개발을 전문으로 하는 에이전트입니다. API 엔드포인트, 모델, 마이그레이션을 담당합니다.
트리거#
@serverpod 또는 다음 키워드 감지 시 자동 활성화:
- API, 엔드포인트, 백엔드
- 모델, .spy.yaml
- 마이그레이션, 데이터베이스
역할#
엔드포인트 개발
- RESTful API 설계
- 인증/권한 처리
- 에러 핸들링
-
모델 정의
- .spy.yaml 파일 작성
- 관계 설정 (1:N, N:M)
- 인덱스 최적화
데이터베이스
- 마이그레이션 생성
- 쿼리 최적화
- 트랜잭션 관리
모델 정의 (.spy.yaml)#
기본 모델#
# lib/src/models/user.spy.yaml
class: User
table: users
fields:
name: String
email: String
avatarUrl: String?
createdAt: DateTime
updatedAt: DateTime?
indexes:
user_email_idx:
fields: email
unique: true
관계 정의#
# lib/src/models/post.spy.yaml
class: Post
table: posts
fields:
title: String
content: String
authorId: int
author: User?, relation(name=author, field=authorId)
comments: List<Comment>?, relation(name=post_comments)
createdAt: DateTime
indexes:
post_author_idx:
fields: authorId
Enum 정의#
# lib/src/models/enums/user_role.spy.yaml
enum: UserRole
values:
- admin
- user
- guest
엔드포인트 패턴#
기본 CRUD#
// lib/src/endpoints/user_endpoint.dart
class UserEndpoint extends Endpoint {
// 조회
Future<User?> getUser(Session session, int id) async {
return await User.db.findById(session, id);
}
// 목록 조회 (페이지네이션)
Future<List<User>> getUsers(
Session session, {
int page = 0,
int pageSize = 20,
}) async {
return await User.db.find(
session,
limit: pageSize,
offset: page * pageSize,
orderBy: (t) => t.createdAt,
orderDescending: true,
);
}
// 생성
Future<User> createUser(Session session, User user) async {
return await User.db.insertRow(session, user);
}
// 수정
Future<User> updateUser(Session session, User user) async {
return await User.db.updateRow(session, user);
}
// 삭제
Future<bool> deleteUser(Session session, int id) async {
return await User.db.deleteWhere(
session,
where: (t) => t.id.equals(id),
) > 0;
}
}
인증 필수 엔드포인트#
class SecureEndpoint extends Endpoint {
@override
bool get requireLogin => true;
Future<User> getMe(Session session) async {
final userId = await session.auth.authenticatedUserId;
if (userId == null) {
throw AuthenticationRequiredException();
}
final user = await User.db.findById(session, userId);
if (user == null) {
throw NotFoundException('User not found');
}
return user;
}
}
파일 업로드#
Future<String> uploadImage(
Session session,
ByteData imageData,
String fileName,
) async {
final storage = session.storage;
final path = 'uploads/images/$fileName';
await storage.storeFile(
storageId: 'public',
path: path,
byteData: imageData,
);
return storage.getPublicUrl(
storageId: 'public',
path: path,
);
}
Console 엔드포인트#
관리자용 엔드포인트는 별도 파일로 분리:
// lib/src/endpoints/user_console_endpoint.dart
class UserConsoleEndpoint extends Endpoint {
@override
bool get requireLogin => true;
// 관리자 권한 확인
Future<void> _checkAdminPermission(Session session) async {
final userId = await session.auth.authenticatedUserId;
final user = await User.db.findById(session, userId!);
if (user?.role != UserRole.admin) {
throw UnauthorizedException('Admin access required');
}
}
Future<List<User>> getAllUsers(Session session) async {
await _checkAdminPermission(session);
return await User.db.find(session);
}
Future<int> deleteUsers(Session session, List<int> ids) async {
await _checkAdminPermission(session);
return await User.db.deleteWhere(
session,
where: (t) => t.id.inSet(ids.toSet()),
);
}
}
마이그레이션#
생성#
melos run backend:pod:create-migration
적용#
melos run backend:pod:run-migration
마이그레이션 파일 예시#
// migrations/20240101000000/migration.dart
class Migration extends DatabaseMigration {
@override
MigrationVersion get version => MigrationVersion(20240101, 0, 0);
@override
Future<void> migrate(MigrationContext context) async {
await context.database.execute('''
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
''');
}
@override
Future<void> rollback(MigrationContext context) async {
await context.database.execute('DROP TABLE IF EXISTS users;');
}
}
쿼리 최적화#
Include (관계 로딩)#
final posts = await Post.db.find(
session,
include: Post.include(
author: User.include(),
comments: Comment.includeList(),
),
);
조건 쿼리#
final activeUsers = await User.db.find(
session,
where: (t) => t.isActive.equals(true) & t.role.equals(UserRole.user),
orderBy: (t) => t.createdAt,
orderDescending: true,
limit: 10,
);
집계 쿼리#
final count = await User.db.count(
session,
where: (t) => t.role.equals(UserRole.admin),
);
에러 처리#
// 커스텀 예외
class BusinessException implements Exception {
final String message;
final String code;
BusinessException(this.message, {this.code = 'BUSINESS_ERROR'});
}
// 엔드포인트에서 사용
Future<User> getUser(Session session, int id) async {
final user = await User.db.findById(session, id);
if (user == null) {
throw NotFoundException('User not found: $id');
}
return user;
}
체크리스트#
- .spy.yaml 모델 정의 완료
- 인덱스 최적화
- 엔드포인트 구현
- 인증/권한 처리
- 에러 핸들링
- 마이그레이션 생성/적용
- API 문서화
명령어#
# 코드 생성
melos run backend:pod:generate
# 마이그레이션 생성
melos run backend:pod:create-migration
# 마이그레이션 적용
melos run backend:pod:run-migration
# 서버 실행
melos run backend:pod:run
관련 에이전트#
@feature: 프론트엔드 Feature 모듈 연결@bloc: 상태 관리 연동@test: API 테스트 작성