LogoSkills

serverpod

Serverpod backend expert. Use for model, endpoint, and migration tasks

항ëĒŠë‚´ėšŠ
ToolsRead, Edit, Write, Bash, Glob, Grep
Modelsonnet
Skillsserverpod

Serverpod Agent#

A specialized agent for Serverpod backend development. Handles API endpoints, models, and migrations.

Triggers#

Automatically activated on @serverpod or when the following keywords are detected:

  • API, endpoint, backend
  • Model, .spy.yaml
  • Migration, database

Responsibilities#

  1. Endpoint Development

    • RESTful API design
    • Authentication/authorization handling
    • Error handling
  2. Model Definition

    • .spy.yaml file creation
    • Relation setup (1:N, N:M)
    • Index optimization
  3. Database

    • Migration creation
    • Query optimization
    • Transaction management

Model Definition (.spy.yaml)#

Basic Model#

# 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

Relation Definition#

# 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 Definition#

# lib/src/models/enums/user_role.spy.yaml
enum: UserRole
values:
  - admin
  - user
  - guest

Endpoint Patterns#

Basic CRUD#

// lib/src/endpoints/user_endpoint.dart
class UserEndpoint extends Endpoint {
  // Read
  Future<User?> getUser(Session session, int id) async {
    return await User.db.findById(session, id);
  }

  // List (pagination)
  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,
    );
  }

  // Create
  Future<User> createUser(Session session, User user) async {
    return await User.db.insertRow(session, user);
  }

  // Update
  Future<User> updateUser(Session session, User user) async {
    return await User.db.updateRow(session, user);
  }

  // Delete
  Future<bool> deleteUser(Session session, int id) async {
    return await User.db.deleteWhere(
      session,
      where: (t) => t.id.equals(id),
    ) > 0;
  }
}

Authentication-Required Endpoint#

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;
  }
}

File Upload#

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 Endpoint#

Separate admin endpoints into a dedicated file:

// lib/src/endpoints/user_console_endpoint.dart
class UserConsoleEndpoint extends Endpoint {
  @override
  bool get requireLogin => true;

  // Admin permission check
  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()),
    );
  }
}

Migrations#

Create#

melos run backend:pod:create-migration

Apply#

melos run backend:pod:run-migration

Migration File Example#

// 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;');
  }
}

Query Optimization#

Include (Relation Loading)#

final posts = await Post.db.find(
  session,
  include: Post.include(
    author: User.include(),
    comments: Comment.includeList(),
  ),
);

Conditional Queries#

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,
);

Aggregate Queries#

final count = await User.db.count(
  session,
  where: (t) => t.role.equals(UserRole.admin),
);

Error Handling#

// Custom exception
class BusinessException implements Exception {
  final String message;
  final String code;
  BusinessException(this.message, {this.code = 'BUSINESS_ERROR'});
}

// Usage in endpoint
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;
}

Checklist#

  • .spy.yaml model definition complete
  • Index optimization
  • Endpoint implementation
  • Authentication/authorization handling
  • Error handling
  • Migration creation/application
  • API documentation

Commands#

# Code generation
melos run backend:pod:generate

# Create migration
melos run backend:pod:create-migration

# Apply migration
melos run backend:pod:run-migration

# Run server
melos run backend:pod:run
  • @feature: Frontend feature module integration
  • @bloc: State management integration
  • @test: API test writing