Handlers & Resources
ShipQ’s API layer is intentionally simple: handlers register themselves with metadata, then the handler compiler generates everything around them — server wiring, OpenAPI specs, test clients, and TypeScript clients.
Generating Resources
Section titled “Generating Resources”The fastest way to get a working API is shipq resource, which generates querydefs, handlers, and tests in one shot.
Generate all CRUD operations
Section titled “Generate all CRUD operations”shipq resource pets allThis creates:
| File | Method | Route | Description |
|---|---|---|---|
api/pets/create.go | POST | /pets | Create a pet |
api/pets/get_one.go | GET | /pets/:id | Get a single pet by ID |
api/pets/list.go | GET | /pets | List pets (with pagination) |
api/pets/update.go | PATCH | /pets/:id | Update a pet |
api/pets/soft_delete.go | DELETE | /pets/:id | Soft-delete a pet |
api/pets/register.go | — | — | Handler registration function |
Plus query definitions in querydefs/ and test files in api/pets/spec/.
Generate individual operations
Section titled “Generate individual operations”You don’t have to generate all five at once:
shipq resource pets createshipq resource pets get_oneshipq resource pets listshipq resource pets updateshipq resource pets deletePublic vs. auth-protected routes
Section titled “Public vs. auth-protected routes”If you’ve run shipq auth, routes are auth-protected by default (controlled by protect_by_default = true in shipq.ini). To make routes public:
shipq resource pets all --publicHandler Registration
Section titled “Handler Registration”Every handler in ShipQ registers itself with metadata that the handler compiler uses for code generation. The register.go file in each API package is responsible for this.
How handler metadata works
Section titled “How handler metadata works”When you write (or generate) a handler, it describes itself via a handler.HandlerInfo struct:
- HTTP routing: method (
GET,POST,PATCH,DELETE) and path (e.g.,/pets/:id) - Path parameters: automatically extracted from the path pattern (e.g.,
idfrom/pets/:id) - Authentication: whether the handler requires auth, allows optional auth, or is fully public
- Request type: the struct that represents the request body (nil for GETs with no body)
- Response type: the struct that represents the response body
This metadata is what powers OpenAPI generation, TypeScript client codegen, test client generation, and admin UI generation.
Request and response types
Section titled “Request and response types”Handlers use plain Go structs for request and response types. ShipQ inspects struct tags to determine JSON field names, required fields, and omitempty behavior:
type CreatePetRequest struct { Name string `json:"name"` Species string `json:"species"` Age int `json:"age"`}
type CreatePetResponse struct { ID string `json:"id"` Name string `json:"name"` Species string `json:"species"` Age int `json:"age"` CreatedAt time.Time `json:"created_at"`}Fields without omitempty and that aren’t pointers are treated as required in the OpenAPI spec.
Handler Compilation
Section titled “Handler Compilation”After you’ve added or modified handlers, compile the handler registry:
shipq handler compileThis runs the handler compiler, which:
- Discovers all handler registration functions across your
api/packages - Extracts full metadata: HTTP method, path, path params, request/response struct definitions, auth requirements
- Generates all the downstream artifacts
What gets generated
Section titled “What gets generated”| Artifact | Location | Description |
|---|---|---|
| Server main | cmd/server/main.go | Runnable HTTP server with all handlers wired up |
| OpenAPI spec | GET /openapi (dev/test) | OpenAPI 3.1 JSON spec |
| Docs UI | GET /docs (dev/test) | Interactive API documentation |
| Admin UI | Generated from OpenAPI | Useful for manual testing |
| HTTP test client | api/*/spec/ | Typed test client for each resource |
| Test harness | api/*/spec/ | Test setup/teardown helpers |
| Integration tests | api/*/spec/ | RBAC, tenancy, and 401 tests |
| TypeScript client | Configurable output dir | Typed HTTP client for your frontend |
| React/Svelte hooks | Configurable output dir | Optional framework-specific helpers |
The handler generate alternative
Section titled “The handler generate alternative”For more control over what gets generated, you can use handler generate instead of resource:
shipq handler generate postsThis generates the same CRUD handler files but gives you more flexibility to customize before compiling.
Customizing Generated Handlers
Section titled “Customizing Generated Handlers”Generated handler files are yours to modify — they’re not overwritten on subsequent compiles unless they have the // Code generated by shipq. DO NOT EDIT. header or a zz_generated_ filename prefix.
Files you CAN safely edit
Section titled “Files you CAN safely edit”api/<table>/create.goapi/<table>/get_one.goapi/<table>/list.goapi/<table>/update.goapi/<table>/soft_delete.go
Files you should NOT hand-edit
Section titled “Files you should NOT hand-edit”These are overwritten on every shipq handler compile:
- Any file starting with
zz_generated_ - Any file with the
// Code generated by shipq. DO NOT EDIT.header cmd/server/main.go
Adding Custom Handlers
Section titled “Adding Custom Handlers”You can write handlers from scratch alongside generated ones. The pattern is:
- Create an API package (e.g.,
api/reports/) - Write your handler function with request/response structs
- Write a
register.gothat registers the handler with the registry - Add query definitions in
querydefs/if you need custom queries - Run
shipq handler compileto regenerate server wiring
As long as your handler registers itself correctly, ShipQ’s handler compiler will pick it up and include it in the OpenAPI spec, test client, TypeScript client, and server wiring.
Nested Resources
Section titled “Nested Resources”ShipQ handles foreign key relationships naturally. If you create a books table that references authors:
shipq migrate new authors name:string bio:textshipq migrate new books title:string author_id:references:authorsshipq resource authors allshipq resource books allThe generated books handlers will include the author_id field in request/response types, and the OpenAPI spec will reflect the relationship. Foreign key columns are resolved to their correct types (e.g., the author_id field in the request body will reference the appropriate type, not a raw int64).
Iteration Workflow
Section titled “Iteration Workflow”The typical handler development loop:
# 1. Generate or edit handlersshipq resource <table> all# ... or hand-edit api/<table>/*.go
# 2. Add or edit query definitions# ... edit querydefs/<table>/queries.go
# 3. Recompile queries (if querydefs changed)shipq db compile
# 4. Recompile handler registry (always needed after handler changes)shipq handler compile
# 5. Run testsgo test ./... -v
# 6. Start the servergo run ./cmd/server