Spartan-ai-toolkit backend-api-design

Design RPC-style APIs with layered architecture (Controller → Manager → Repository). Use when creating new API endpoints, designing API contracts, or reviewing API patterns.

install
source · Clone the upstream repo
git clone https://github.com/c0x12c/ai-toolkit
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/c0x12c/ai-toolkit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/toolkit/skills/backend-api-design" ~/.claude/skills/spartan-stratos-spartan-ai-toolkit-backend-api-design-126303 && rm -rf "$T"
manifest: toolkit/skills/backend-api-design/SKILL.md
source content

Backend API Design — Quick Reference

URL Patterns

GET  /api/v1/employees              # List (plural)
GET  /api/v1/employee               # Get one (?id=xxx)
POST /api/v1/employee               # Create
POST /api/v1/employee/update        # Update (?id=xxx)
POST /api/v1/employee/delete        # Soft delete (?id=xxx)
POST /api/v1/employee/restore       # Restore (?id=xxx)
POST /api/v1/sync/employees         # Action

Hard Rules

  • NO path params — always
    @QueryValue
    , never
    @PathVariable
  • Singular for single resource
    /employee
    not
    /employees/{id}
  • Plural for collections
    /employees
  • Verb sub-paths for actions
    /delete
    ,
    /restore
    ,
    /sync

Layered Architecture

Controller  →  thin, just delegates
    ↓
Manager     →  business logic, transactions, Either returns
    ↓
Repository  →  data access only, no business logic

Controller: Thin Wrapper

  • Parse query params with defaults
  • Delegate to manager
  • Unwrap Either with
    .throwOrValue()
  • NO business logic, NO repository access

Manager: Business Logic

  • Returns
    Either<ClientException, T>
  • Wraps DB ops in
    transaction(db.primary) { }
  • Orchestrates multiple repositories
  • Validates business rules

Repository: Data Access

  • Returns entities or null
  • db.replica
    for reads,
    db.primary
    for writes
  • Always checks
    deletedAt.isNull()

Quick Code Reference

The core controller delegation pattern:

@Get("/employee")
suspend fun getEmployee(@QueryValue id: UUID): EmployeeResponse {
  return employeeManager.findById(id).throwOrValue()
}
  • Response models
    companion object { fun from(entity) }
    in
    module-client/response/{domain}/
  • Pagination — offset-based, manager returns
    EmployeeListResponse
    with
    items
    ,
    total
    ,
    page
    ,
    limit
    ,
    hasMore
  • Errors — return
    ClientError.NOT_FOUND.asException().left()
    from managers, never throw
  • Factory beans
    @Factory
    class with
    @Singleton
    method, wire repos + db into manager

See code-patterns.md for complete controller, response model, pagination, error handling, and factory bean templates.

Gotchas

  • Multi-word
    @QueryValue
    params MUST have explicit snake_case names.
    The frontend axios interceptor sends
    project_id
    but Micronaut matches the literal param name. Write
    @QueryValue("project_id") projectId: UUID
    , not bare
    @QueryValue projectId: UUID
    .
  • Don't use
    @Put
    ,
    @Delete
    , or
    @Patch
    .
    This is RPC-style — all mutations are
    @Post
    . The only
    @Get
    is for reads.
  • Controllers that inject repositories are a code smell. If you see
    private val fooRepository: FooRepository
    in a controller, move it to the manager.
  • andWhere {}
    not second
    .where {}
    .
    Calling
    .where {}
    twice replaces the first condition. Use
    .andWhere {}
    to chain.
  • Don't forget
    @ExecuteOn(TaskExecutors.IO)
    .
    Without it, suspend functions may hang or run on the wrong thread pool. Every controller needs it.