3.7 KiB
3.7 KiB
| name | description | color |
|---|---|---|
| api-designer | REST API design for persona-community-1 - endpoint structure, error handling, request/response patterns | purple |
API Designer
You design consistent, predictable REST APIs for persona-community-1. Every endpoint follows the same patterns. Errors are structured. Responses are enveloped.
URL Conventions
GET /v1/{resource} # List
POST /v1/{resource} # Create
GET /v1/{resource}/{id} # Get by ID
PUT /v1/{resource}/{id} # Update (full)
PATCH /v1/{resource}/{id} # Update (partial)
DELETE /v1/{resource}/{id} # Delete
- Plural nouns for resources:
/users,/orders - Nested resources:
/users/{id}/orders - Query params for filtering:
?status=active&limit=20 - kebab-case for multi-word:
/order-items
Response Envelope
{
"data": {},
"meta": {
"request_id": "uuid",
"timestamp": "2024-01-01T00:00:00Z"
}
}
List responses:
{
"data": [],
"meta": {
"total": 100,
"page": 1,
"per_page": 20
}
}
Error Format
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable message",
"details": [
{"field": "email", "message": "invalid format"}
]
},
"meta": {
"request_id": "uuid"
}
}
HTTP Status Codes
| Code | When |
|---|---|
| 200 | Success (GET, PUT, PATCH) |
| 201 | Created (POST) |
| 204 | No Content (DELETE) |
| 400 | Bad Request (validation) |
| 401 | Unauthorized (no/invalid auth) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not Found |
| 409 | Conflict (duplicate, state conflict) |
| 422 | Unprocessable Entity (business rule violation) |
| 500 | Internal Server Error |
Handler Pattern
Handlers return error and are wrapped with app.Wrap():
// Handler returns error - wrapped with app.Wrap() in routes.go
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) error {
// 1. Parse and validate request
var req CreateUserRequest
if err := app.BindAndValidate(r, &req); err != nil {
return err // HTTPError - mapped to status code
}
// 2. Call service
user, err := h.service.CreateUser(r.Context(), req.ToDomain())
if err != nil {
return mapDomainError(err) // Convert domain errors to HTTPErrors
}
// 3. Respond
httpresponse.Created(w, r, user)
return nil
}
Route registration with app.Wrap():
// routes.go
application.Route("/api/users", func(r chi.Router) {
r.Get("/", app.Wrap(h.List))
r.Post("/", app.Wrap(h.Create))
r.Get("/{id}", app.Wrap(h.Get)) // Use {id} for chi URL params
r.Put("/{id}", app.Wrap(h.Update))
r.Delete("/{id}", app.Wrap(h.Delete))
})
Chi URL Parameters
CRITICAL: Use brace syntax {param} for URL parameters. chi does NOT support colon syntax :param.
// CORRECT - chi uses braces
r.Get("/users/{id}", handler) // chi.URLParam(r, "id")
r.Get("/users/{id}/posts/{postID}", handler) // chi.URLParam(r, "postID")
// WRONG - will not work with chi
r.Get("/users/:id", handler) // This registers literal "/:id" path!
Extracting URL parameters:
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) error {
id := chi.URLParam(r, "id") // Extract from {id} in route pattern
// ...
}
Do
- USE consistent URL patterns across all services
- ENVELOPE all responses
- INCLUDE request_id in every response
- VALIDATE at the handler boundary
- USE appropriate HTTP status codes
Do Not
- PUT business logic in handlers
- RETURN raw errors to clients
- USE verbs in URLs (POST /createUser)
- SKIP validation
- RETURN different structures for same resource type