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.mdsource 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
, never@QueryValue@PathVariable - Singular for single resource —
not/employee/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
for reads,db.replica
for writesdb.primary- 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 —
incompanion object { fun from(entity) }module-client/response/{domain}/ - Pagination — offset-based, manager returns
withEmployeeListResponse
,items
,total
,page
,limithasMore - Errors — return
from managers, never throwClientError.NOT_FOUND.asException().left() - Factory beans —
class with@Factory
method, wire repos + db into manager@Singleton
See code-patterns.md for complete controller, response model, pagination, error handling, and factory bean templates.
Gotchas
- Multi-word
params MUST have explicit snake_case names. The frontend axios interceptor sends@QueryValue
but Micronaut matches the literal param name. Writeproject_id
, not bare@QueryValue("project_id") projectId: UUID
.@QueryValue projectId: UUID - Don't use
,@Put
, or@Delete
. This is RPC-style — all mutations are@Patch
. The only@Post
is for reads.@Get - Controllers that inject repositories are a code smell. If you see
in a controller, move it to the manager.private val fooRepository: FooRepository
not secondandWhere {}
. Calling.where {}
twice replaces the first condition. Use.where {}
to chain..andWhere {}- Don't forget
. Without it, suspend functions may hang or run on the wrong thread pool. Every controller needs it.@ExecuteOn(TaskExecutors.IO)