Backend APIs: HTTP Service Design and Implementation
Design and review HTTP APIs that stay coherent as they grow. Focus on contracts, auth
boundaries, error models, and framework structure for Python and Node.js services.
Target versions (September 2026):
FastAPI 0.141.1
Express 5.2.1 (published 2025-12-01)
NestJS 12.0.1 (major release; check migration notes before upgrading)
Reviewing an existing FastAPI, Express, or NestJS service
Writing or fixing OpenAPI specifications and generated docs
Choosing status codes, error formats, pagination, filtering, or versioning strategy
Designing auth flows for browser, mobile, machine-to-machine, or third-party clients
Refactoring route or controller structure that has become hard to reason about
Planning backward-compatible API evolution
Defining idempotency and retry behavior for write endpoints
When NOT to use
GraphQL schema, resolvers, federation, or persisted query work - out of scope for this skill
gRPC, protobuf schema evolution, or streaming RPCs - out of scope for this skill
Database schema, indexing, replication, or query tuning - use databases
General bug-finding, race-condition hunting, or correctness review outside the API boundary - use code-review
Security auditing for auth bypasses, injection, secrets, or OWASP findings - use security-audit
Writing or debugging automated tests - use testing
Deployment, containers, gateways, ingress, or cluster config - use docker, kubernetes, or networking
CI/CD pipeline design for API delivery - use ci-cd
MCP-specific HTTP servers and tool handlers - use mcp
AI Self-Check
Before returning API code, route design, or OpenAPI output, verify:
Resource names are nouns and URL shape is stable (/users/{userId}/sessions, not /doUserSessionThing)
Method semantics follow RFC 9110 - no "GET that mutates", no PATCH used as a vague catch-all
401 vs 403 is correct: unauthenticated vs authenticated-but-forbidden
Ownership-hiding behavior is deliberate and consistent: decide when out-of-scope resources return 404 vs 403
Error responses use one consistent format, preferably RFC 9457 problem details
Request and response DTOs are explicit and separate from ORM or database models
Input validation happens server-side even if clients or SDKs also validate
Offset pagination is not used blindly on large or high-churn collections where cursor pagination is the safer default
Write endpoints that may be retried (POST /payments, webhook receivers, order creation) define idempotency behavior
OpenAPI docs match real handler behavior, examples, status codes, and auth requirements
First-party browser apps do not get long-lived bearer tokens stored in browser storage by default
Cookie-based browser auth accounts for cookie scope and CSRF behavior instead of assuming cookies are automatically safe
OAuth guidance is current: authorization code + PKCE, no implicit flow, no resource owner password credentials
Sensitive defaults are explicit: cookie flags, token TTLs, scope boundaries, and rate limits are not hand-waved
Framework version checked: FastAPI, Express, NestJS, and OpenAPI examples match current APIs and migration notes
Contract compatibility checked: response codes, pagination, errors, and auth behavior preserve existing clients unless a version bump is explicit
Injection in handler body flagged as contract defect: if injection (SQL, command, header, path traversal) is found inside a route handler, flag it as a missing validation-boundary defect at the contract layer and recommend fixing it there; route to security-audit for a full injection audit if the pattern is widespread
Cross-cutting agent hygiene applied - see references/agent-hygiene.md
Performance
Paginate and filter at the data store; never load full collections just to slice in the handler.
Set timeouts and body limits at the framework, proxy, and client layers.
Measure p95/p99 latency and error rates for changed endpoints before optimizing internals.
Best Practices
Design idempotency for retries on create, payment, provisioning, and webhook endpoints.
Generate or validate OpenAPI from the implementation contract and keep examples executable.
Use explicit auth scopes and tenancy checks in handlers, not only in route grouping or UI state.
Workflow
Build vs. Review: when reviewing an existing service, still walk the same steps. Determine the API boundary, audit the contract, trace auth, then compare the implementation against the published behavior instead of jumping straight into handler code.
Step 1: Determine the boundary
Clarify the API before picking framework patterns:
Who calls it? Browser app, mobile app, third-party integrator, internal service, webhook sender
What kind of API is it? Public API, private app backend, internal service, admin API
Is the task greenfield or review? New design, incremental change, migration, or bug fix
What stability promise exists? Internal-only, versioned public API, or "best effort"
What auth model already exists? Session cookies, JWT bearer, API keys, OAuth/OIDC, or none yet
What house style already exists? Error format, pagination shape, versioning scheme, auth boundary, DTO naming
If the user asks to "just add an endpoint", inspect the surrounding service first. Most bad API
work happens when one route lands with a different error model, auth rule, pagination shape, or
DTO style than the rest of the service.
Step 2: Choose the framework pattern
Pick the framework that matches the codebase and team constraints. Do not switch frameworks for fashion.
Framework
Best fit
Watch for
FastAPI
Python services with typed request/response models and strong OpenAPI output
Leaking ORM models directly, async confusion, piling business logic into route functions
No built-in structure, validation scattered across middleware, inconsistent error handling
NestJS
Larger TypeScript services that benefit from modules, guards, pipes, interceptors, and DI
Over-abstraction, decorator-heavy indirection, hiding simple flows behind too many layers
Framework rules:
FastAPI - keep dependencies explicit, use response models, and centralize exception handling
Express - keep routing, validation, auth, and business logic separated; add one error middleware path
NestJS - keep controllers thin, move auth into guards, validation into pipes, and cross-cutting behavior into interceptors or filters
Step 3: Design the contract first
Define the API contract before writing handlers:
For changes to an existing service, match the current URL, error, pagination, and auth house
style unless the task explicitly includes a migration.
Pick resource shapes and URIs
Choose methods and status codes
Define request and response schemas
Define error shapes
Decide pagination, filtering, sorting, and versioning rules
Only then wire the framework implementation
Prefer:
Noun-based resources: /users/{userId}/sessions
Predictable list endpoints: GET /orders?cursor=...&limit=...&status=paid
Explicit write semantics: POST for create/commands, PUT for full replacement, PATCH for partial update, DELETE for delete
Explicit retry semantics for command endpoints like POST /orders/{orderId}/cancel
One canonical error model across the whole service
Read references/http-api-patterns.md for method semantics, pagination, idempotency, and versioning rules.
Step 4: Design auth around the client, not around JWT hype
Start from the client type:
Client
Default choice
Why
First-party browser app
Session cookie or BFF/token-mediating backend
Keeps tokens off the browser as much as possible
Native/mobile app
OAuth/OIDC authorization code + PKCE
Current standard path for public clients
Third-party integrator
OAuth/OIDC or scoped API keys if OAuth is overkill
Explicit delegation and revocation story
Internal service-to-service
Platform identity or short-lived service credentials
Avoid user-style auth flows between services
Auth rules:
Use sessions by default for first-party web apps unless there is a clear reason not to
Use bearer tokens when the client really is a token-holding client
Treat API keys as machine credentials, not as a universal auth shortcut
Define scope or role boundaries at the API boundary, not ad hoc inside random handlers
If the browser app talks to third-party identity providers, follow authorization code + PKCE and current browser-app guidance rather than resurrecting implicit flow patterns
BFF token mediation (first-party browser app calling an external API):
Offset pagination is fine for small, stable backoffice lists
Cursor pagination is the default for user-facing or high-write collections
Sort order must be stable and documented
Filter names should reflect resource fields or clear domain concepts, not internal SQL column names
Idempotency and retries
Define idempotency for writes that clients or gateways may retry
Use explicit idempotency keys for payment-like or request-replay-prone operations
Distinguish "request accepted" from "side effect completed" when async workflows exist
Idempotency key header pattern (Express/NestJS):
const key = req.headers['idempotency-key'];
if (key) {
const cached = await cache.get(`idem:${key}`);
if (cached) return res.status(cached.status).json(cached.body);
}
// ... execute, then store result keyed by idempotency-key before returning
Idempotency key header pattern (FastAPI):
from fastapi import Header, HTTPException
async def create_order(
body: OrderCreate,
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
):
if idempotency_key:
cached = await cache.get(f"idem:{idempotency_key}")
if cached:
return JSONResponse(status_code=cached["status"], content=cached["body"])
result = await orders.create(body)
if idempotency_key:
await cache.set(f"idem:{idempotency_key}", {"status": 201, "body": result}, ttl=86400)
return result
Decode the cursor server-side (WHERE id > :cursor_id ORDER BY id LIMIT :limit). Never expose raw DB offsets or row numbers in the cursor.
Opaque cursor encode/decode (FastAPI, HMAC-signed to prevent client tampering):
import base64, hmac, hashlib, json, os
SECRET = os.environ["CURSOR_SECRET"].encode()
def encode_cursor(payload: dict) -> str:
body = base64.urlsafe_b64encode(json.dumps(payload, separators=(",", ":")).encode()).rstrip(b"=")
sig = base64.urlsafe_b64encode(hmac.new(SECRET, body, hashlib.sha256).digest()[:8]).rstrip(b"=")
return f"{body.decode()}.{sig.decode()}"
def decode_cursor(token: str) -> dict:
body, sig = token.split(".", 1)
expected = base64.urlsafe_b64encode(hmac.new(SECRET, body.encode(), hashlib.sha256).digest()[:8]).rstrip(b"=").decode()
if not hmac.compare_digest(sig, expected):
raise ValueError("invalid cursor")
return json.loads(base64.urlsafe_b64decode(body + "=="))
Clients treat next_cursor as opaque; the server controls shape and can change it without a breaking contract.
Graceful shutdown and rolling deploys
Rolling deploys send SIGTERM to old instances while new ones come up. Handle it on the API side or expect 502s under load:
Trap SIGTERM, stop accepting new connections, drain in-flight requests, then exit. FastAPI/Uvicorn: configure --timeout-graceful-shutdown (default 30s); Express 5: server.close() then server.closeAllConnections() after the drain window; NestJS: app.enableShutdownHooks() plus onApplicationShutdown handlers
Keep the app-side shutdown window shorter than the orchestrator's termination grace (Kubernetes default 30s). app_shutdown < terminationGracePeriodSeconds or the kernel kills in-flight work
Set HTTP keep-alive timeout shorter than any upstream idle timeout (load balancer, ingress). If the LB holds a connection the server already closed, the next request hits a dead socket. Typical safe pair: server keep-alive 65s behind an LB with 60s idle
Add a readiness probe that flips to failing on SIGTERM before the drain starts. The orchestrator stops routing new traffic while in-flight requests finish
What NOT to Force
Do not force cursor pagination onto every small internal list or backoffice table
Do not cut /v2 just because a new field got added
Do not insist on OAuth for every internal automation path if scoped API keys or platform identity fit better
Do not push first-party browser apps toward bearer-token-in-local-storage patterns because "JWT auth" sounds modern
Do not turn simple Express services into pseudo-enterprise architectures with needless layers and decorators
See references/output-contract.md for the full contract.
Skill name: BACKEND-API
Deliverable bucket:audits
Mode: conditional. When invoked to analyze, review, audit, or improve existing repo content, emit the full contract - monospace inline header, severity-grouped inline summary, linked Markdown deliverable, and concise monospace conclusion - and write the deliverable to docs/local/audits/backend-api/<YYYY-MM-DD>-<slug>.md. When invoked to answer a question, teach a concept, build a new artifact, or generate content, respond freely without the contract.
Severity scale:P0 | P1 | P2 | P3 | info (see shared contract; only used in audit/review mode).
Related Skills
databases - schema design, query tuning, migrations, and persistence concerns behind the API
code-review - correctness bugs, logic errors, and non-API-specific review findings
security-audit - auth bypasses, OWASP findings, secrets, and security review after the API design exists
testing - route, integration, contract, and end-to-end verification
Contract before handlers. Design the resource model, schemas, errors, and auth boundary before writing route code.
Do not let transport leak persistence. Database tables, ORM entities, and internal enums are not public API contracts.
Pick one error format. Prefer RFC 9457 and apply it consistently.
Use current auth guidance. Authorization code + PKCE for public OAuth clients, sessions or BFF patterns for first-party browser apps, no implicit flow, no password grant.
Keep framework structure boring. Thin controllers or routes, explicit validation, explicit auth, centralized error handling.
Backward compatibility is a feature. New fields are cheap; breaking clients is expensive.
Write for real retries. Assume clients, proxies, and job runners will replay requests.
Keep scope on HTTP APIs. When the problem is GraphQL or gRPC-specific, say so and stop pretending the same rules apply.
Match service conventions unless migrating them. A one-off endpoint with a different error, auth, or pagination model is usually a contract bug.
1---2name: backend-api3description: >4 · Design/review HTTP APIs for FastAPI, Express, NestJS: REST, OpenAPI, pagination, OAuth/JWT. Triggers: 'fastapi', 'express', 'nestjs', 'openapi', 'pagination', 'idempotency', 'rest api', 'endpoint'. Not for schemas (use databases).5license: MIT6compatibility: "Optional: Python or Node.js framework context. Optional: OpenAPI-capable framework/docs tooling"7metadata:8 source: iuliandita/skills9 date_added: "2026-04-06"10 effort: high11 argument_hint: "[framework-or-api-task]"12---1314# Backend APIs: HTTP Service Design and Implementation1516Design and review HTTP APIs that stay coherent as they grow. Focus on contracts, auth17boundaries, error models, and framework structure for Python and Node.js services.1819**Target versions** (September 2026):20- FastAPI **0.141.1**21- Express **5.2.1** (published 2025-12-01)22- NestJS **12.0.1** (major release; check migration notes before upgrading)23- OpenAPI Specification **3.2.0** (published 2025-09-19)24- HTTP Semantics: **RFC 9110** (June 2022)25- Problem Details for HTTP APIs: **RFC 9457** (July 2023)26- OAuth 2.0 Security Best Current Practice: **RFC 9700** (January 2025)2728This skill works across five concerns:29- **Contract design** - resources, methods, status codes, schemas, versioning30- **API ergonomics** - pagination, filtering, sorting, idempotency, error format31- **Framework structure** - FastAPI dependencies, Express middleware, NestJS modules and guards32- **Authentication** - sessions, bearer tokens, OAuth/OIDC, BFF and token mediation33- **Review** - unstable contracts, DTO leakage, auth confusion, and HTTP misuse3435## When to use3637- Designing a new REST or HTTP API38- Reviewing an existing FastAPI, Express, or NestJS service39- Writing or fixing OpenAPI specifications and generated docs40- Choosing status codes, error formats, pagination, filtering, or versioning strategy41- Designing auth flows for browser, mobile, machine-to-machine, or third-party clients42- Refactoring route or controller structure that has become hard to reason about43- Planning backward-compatible API evolution44- Defining idempotency and retry behavior for write endpoints4546## When NOT to use4748- GraphQL schema, resolvers, federation, or persisted query work - out of scope for this skill49- gRPC, protobuf schema evolution, or streaming RPCs - out of scope for this skill50- Database schema, indexing, replication, or query tuning - use **databases**51- General bug-finding, race-condition hunting, or correctness review outside the API boundary - use **code-review**52- Security auditing for auth bypasses, injection, secrets, or OWASP findings - use **security-audit**53- Writing or debugging automated tests - use **testing**54- Deployment, containers, gateways, ingress, or cluster config - use **docker**, **kubernetes**, or **networking**55- CI/CD pipeline design for API delivery - use **ci-cd**56- MCP-specific HTTP servers and tool handlers - use **mcp**5758---5960## AI Self-Check6162Before returning API code, route design, or OpenAPI output, verify:6364- [ ] Resource names are nouns and URL shape is stable (`/users/{userId}/sessions`, not `/doUserSessionThing`)65- [ ] Method semantics follow RFC 9110 - no "GET that mutates", no PATCH used as a vague catch-all66- [ ] `401` vs `403` is correct: unauthenticated vs authenticated-but-forbidden67- [ ] Ownership-hiding behavior is deliberate and consistent: decide when out-of-scope resources return `404` vs `403`68- [ ] Error responses use one consistent format, preferably RFC 9457 problem details69- [ ] Request and response DTOs are explicit and separate from ORM or database models70- [ ] Input validation happens server-side even if clients or SDKs also validate71- [ ] Offset pagination is not used blindly on large or high-churn collections where cursor pagination is the safer default72- [ ] Write endpoints that may be retried (`POST /payments`, webhook receivers, order creation) define idempotency behavior73- [ ] OpenAPI docs match real handler behavior, examples, status codes, and auth requirements74- [ ] First-party browser apps do not get long-lived bearer tokens stored in browser storage by default75- [ ] Cookie-based browser auth accounts for cookie scope and CSRF behavior instead of assuming cookies are automatically safe76- [ ] OAuth guidance is current: authorization code + PKCE, no implicit flow, no resource owner password credentials77- [ ] Sensitive defaults are explicit: cookie flags, token TTLs, scope boundaries, and rate limits are not hand-waved78- [ ] **Framework version checked**: FastAPI, Express, NestJS, and OpenAPI examples match current APIs and migration notes79- [ ] **Contract compatibility checked**: response codes, pagination, errors, and auth behavior preserve existing clients unless a version bump is explicit80- [ ] **Injection in handler body flagged as contract defect**: if injection (SQL, command, header, path traversal) is found inside a route handler, flag it as a missing validation-boundary defect at the contract layer and recommend fixing it there; route to **security-audit** for a full injection audit if the pattern is widespread81- [ ] Cross-cutting agent hygiene applied - see `references/agent-hygiene.md`8283---8485## Performance8687- Paginate and filter at the data store; never load full collections just to slice in the handler.88- Set timeouts and body limits at the framework, proxy, and client layers.89- Measure p95/p99 latency and error rates for changed endpoints before optimizing internals.909192---9394## Best Practices9596- Design idempotency for retries on create, payment, provisioning, and webhook endpoints.97- Generate or validate OpenAPI from the implementation contract and keep examples executable.98- Use explicit auth scopes and tenancy checks in handlers, not only in route grouping or UI state.99100101## Workflow102103**Build vs. Review:** when reviewing an existing service, still walk the same steps. Determine the API boundary, audit the contract, trace auth, then compare the implementation against the published behavior instead of jumping straight into handler code.104105### Step 1: Determine the boundary106107Clarify the API before picking framework patterns:108- **Who calls it?** Browser app, mobile app, third-party integrator, internal service, webhook sender109- **What kind of API is it?** Public API, private app backend, internal service, admin API110- **Is the task greenfield or review?** New design, incremental change, migration, or bug fix111- **What stability promise exists?** Internal-only, versioned public API, or "best effort"112- **What auth model already exists?** Session cookies, JWT bearer, API keys, OAuth/OIDC, or none yet113- **What house style already exists?** Error format, pagination shape, versioning scheme, auth boundary, DTO naming114115If the user asks to "just add an endpoint", inspect the surrounding service first. Most bad API116work happens when one route lands with a different error model, auth rule, pagination shape, or117DTO style than the rest of the service.118119### Step 2: Choose the framework pattern120121Pick the framework that matches the codebase and team constraints. Do not switch frameworks for fashion.122123| Framework | Best fit | Watch for |124|-----------|----------|-----------|125| **FastAPI** | Python services with typed request/response models and strong OpenAPI output | Leaking ORM models directly, async confusion, piling business logic into route functions |126| **Express** | Thin Node.js services, custom middleware stacks, existing mature codebases | No built-in structure, validation scattered across middleware, inconsistent error handling |127| **NestJS** | Larger TypeScript services that benefit from modules, guards, pipes, interceptors, and DI | Over-abstraction, decorator-heavy indirection, hiding simple flows behind too many layers |128129Framework rules:130- **FastAPI** - keep dependencies explicit, use response models, and centralize exception handling131- **Express** - keep routing, validation, auth, and business logic separated; add one error middleware path132- **NestJS** - keep controllers thin, move auth into guards, validation into pipes, and cross-cutting behavior into interceptors or filters133134### Step 3: Design the contract first135136Define the API contract before writing handlers:137138For changes to an existing service, match the current URL, error, pagination, and auth house139style unless the task explicitly includes a migration.1401411. Pick resource shapes and URIs1422. Choose methods and status codes1433. Define request and response schemas1444. Define error shapes1455. Decide pagination, filtering, sorting, and versioning rules1466. Only then wire the framework implementation147148Prefer:149- Noun-based resources: `/users/{userId}/sessions`150- Predictable list endpoints: `GET /orders?cursor=...&limit=...&status=paid`151- Explicit write semantics: `POST` for create/commands, `PUT` for full replacement, `PATCH` for partial update, `DELETE` for delete152- Explicit retry semantics for command endpoints like `POST /orders/{orderId}/cancel`153- One canonical error model across the whole service154155Read `references/http-api-patterns.md` for method semantics, pagination, idempotency, and versioning rules.156157### Step 4: Design auth around the client, not around JWT hype158159Start from the client type:160161| Client | Default choice | Why |162|--------|----------------|-----|163| **First-party browser app** | Session cookie or BFF/token-mediating backend | Keeps tokens off the browser as much as possible |164| **Native/mobile app** | OAuth/OIDC authorization code + PKCE | Current standard path for public clients |165| **Third-party integrator** | OAuth/OIDC or scoped API keys if OAuth is overkill | Explicit delegation and revocation story |166| **Internal service-to-service** | Platform identity or short-lived service credentials | Avoid user-style auth flows between services |167168Auth rules:169- Use sessions by default for first-party web apps unless there is a clear reason not to170- Use bearer tokens when the client really is a token-holding client171- Treat API keys as machine credentials, not as a universal auth shortcut172- Define scope or role boundaries at the API boundary, not ad hoc inside random handlers173- If the browser app talks to third-party identity providers, follow authorization code + PKCE and current browser-app guidance rather than resurrecting implicit flow patterns174175BFF token mediation (first-party browser app calling an external API):176```177Browser -> BFF (session cookie) -> BFF attaches Bearer token -> Upstream API178```179The BFF holds the access token server-side; the browser never sees it.180181Read `references/auth-and-session-patterns.md` for sessions, bearer tokens, OAuth, BFF, refresh tokens, and machine auth.182183### Step 5: Implement the framework surface184185Convert the contract into framework code without letting the framework dictate the contract.186187**FastAPI**188- Define Pydantic models for requests and responses189- Use dependency injection for auth, DB/session acquisition, and shared request context190- Register exception handlers for consistent RFC 9457 output191- Keep route handlers thin; move business rules into services or domain modules192193**Express**194- Validate inputs before controller logic195- Attach auth and request context in middleware196- Use one shared error-handling middleware path197- Keep controllers as transport adapters, not the place where every rule in the system lives198199**NestJS**200- Use DTO classes for transport boundaries201- Put validation in pipes and auth in guards202- Use exception filters or interceptors to normalize response and error behavior203- Keep module boundaries meaningful; one module per vague concept is not architecture204205### Step 6: Validate behavior and docs206207Before returning the result:208- Compare OpenAPI docs against the real routes209- Verify auth requirements per endpoint210- Check all non-2xx responses, not just the happy path211- Verify list endpoints under empty, partial, and end-of-cursor states212- Check retries and duplicate submissions for create/payment/webhook paths213- Check repeat-call behavior for command endpoints (`cancel`, `resend`, `approve`) instead of leaving retries implicit214- Compare new endpoints with neighboring endpoints for naming, DTO shape, pagination params, and error consistency215- Route follow-up testing work to **testing** and follow-up security review to **security-audit**216217---218219## Contract Guardrails220221### Versioning222223- Prefer additive change over version churn224- Version only when you need a breaking change225- Keep one versioning scheme for the service: path versioning (`/v1/...`) is the clearest default for public APIs226- Do not mix path versioning, header versioning, and ad hoc `?version=` query params in one API227228### OpenAPI authoring229230- OpenAPI `3.2.0` is the current spec, but much of the framework and Swagger ecosystem still centers on `3.1.x`231- Default to authoring for `3.1` compatibility unless the actual toolchain in the project proves `3.2` support end-to-end232- Do not advertise `3.2` features in generated specs just because the top-level standard moved233234### Error model235236- Prefer RFC 9457 problem details with stable `type`, `title`, `status`, and domain-specific extension fields237- Do not return one error shape from validation, another from auth, and a third from business rules238- Never leak raw stack traces or ORM internals to clients239240Minimal RFC 9457 problem detail response:241```json242{243 "type": "https://api.example.com/errors/insufficient-funds",244 "title": "Insufficient funds",245 "status": 422,246 "detail": "Account balance is $10.00; transfer requires $50.00."247}248```249250Status code decision matrix (pick the narrowest correct code):251252| Scenario | Code | Notes |253|----------|------|-------|254| Malformed JSON, missing required field, wrong type | `400` | Client request is syntactically wrong |255| Well-formed input but fails domain rule (insufficient funds, invalid state) | `422` | Semantic validation |256| No credentials, expired token | `401` | Include `WWW-Authenticate` header |257| Authenticated but not permitted | `403` | Do not challenge for credentials |258| Resource does not exist, or exists but caller must not know | `404` | Pick one policy per resource and keep it |259| Write conflicts with current state (stale ETag, duplicate unique key) | `409` | Problem detail should name the conflict |260| Idempotency-Key reused with a different body | `422` | Not `409` - the key contract is broken |261| Too many requests | `429` | Include `Retry-After` |262| Unhandled server error | `500` | Never leak stack traces in the body |263264FastAPI exception handler wiring for consistent problem+json:265```python266from fastapi import FastAPI, Request267from fastapi.responses import JSONResponse268from fastapi.exceptions import RequestValidationError269270app = FastAPI()271272@app.exception_handler(RequestValidationError)273async def validation_handler(request: Request, exc: RequestValidationError):274 return JSONResponse(275 status_code=400,276 media_type="application/problem+json",277 content={278 "type": "https://api.example.com/errors/validation",279 "title": "Invalid request",280 "status": 400,281 "errors": exc.errors(),282 },283 )284```285286### Pagination and filtering287288- Offset pagination is fine for small, stable backoffice lists289- Cursor pagination is the default for user-facing or high-write collections290- Sort order must be stable and documented291- Filter names should reflect resource fields or clear domain concepts, not internal SQL column names292293### Idempotency and retries294295- Define idempotency for writes that clients or gateways may retry296- Use explicit idempotency keys for payment-like or request-replay-prone operations297- Distinguish "request accepted" from "side effect completed" when async workflows exist298299Idempotency key header pattern (Express/NestJS):300```typescript301const key = req.headers['idempotency-key'];302if (key) {303 const cached = await cache.get(`idem:${key}`);304 if (cached) return res.status(cached.status).json(cached.body);305}306// ... execute, then store result keyed by idempotency-key before returning307```308309Idempotency key header pattern (FastAPI):310```python311from fastapi import Header, HTTPException312313async def create_order(314 body: OrderCreate,315 idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),316):317 if idempotency_key:318 cached = await cache.get(f"idem:{idempotency_key}")319 if cached:320 return JSONResponse(status_code=cached["status"], content=cached["body"])321 result = await orders.create(body)322 if idempotency_key:323 await cache.set(f"idem:{idempotency_key}", {"status": 201, "body": result}, ttl=86400)324 return result325```326327Cursor pagination response envelope:328```json329{330 "data": [...],331 "next_cursor": "eyJpZCI6MTIzfQ",332 "has_more": true333}334```335Decode the cursor server-side (`WHERE id > :cursor_id ORDER BY id LIMIT :limit`). Never expose raw DB offsets or row numbers in the cursor.336337Opaque cursor encode/decode (FastAPI, HMAC-signed to prevent client tampering):338```python339import base64, hmac, hashlib, json, os340341SECRET = os.environ["CURSOR_SECRET"].encode()342343def encode_cursor(payload: dict) -> str:344 body = base64.urlsafe_b64encode(json.dumps(payload, separators=(",", ":")).encode()).rstrip(b"=")345 sig = base64.urlsafe_b64encode(hmac.new(SECRET, body, hashlib.sha256).digest()[:8]).rstrip(b"=")346 return f"{body.decode()}.{sig.decode()}"347348def decode_cursor(token: str) -> dict:349 body, sig = token.split(".", 1)350 expected = base64.urlsafe_b64encode(hmac.new(SECRET, body.encode(), hashlib.sha256).digest()[:8]).rstrip(b"=").decode()351 if not hmac.compare_digest(sig, expected):352 raise ValueError("invalid cursor")353 return json.loads(base64.urlsafe_b64decode(body + "=="))354```355Clients treat `next_cursor` as opaque; the server controls shape and can change it without a breaking contract.356357### Graceful shutdown and rolling deploys358359Rolling deploys send SIGTERM to old instances while new ones come up. Handle it on the API side or expect 502s under load:360361- Trap SIGTERM, stop accepting new connections, drain in-flight requests, then exit. FastAPI/Uvicorn: configure `--timeout-graceful-shutdown` (default 30s); Express 5: `server.close()` then `server.closeAllConnections()` after the drain window; NestJS: `app.enableShutdownHooks()` plus `onApplicationShutdown` handlers362- Keep the app-side shutdown window shorter than the orchestrator's termination grace (Kubernetes default 30s). `app_shutdown < terminationGracePeriodSeconds` or the kernel kills in-flight work363- Set HTTP keep-alive timeout shorter than any upstream idle timeout (load balancer, ingress). If the LB holds a connection the server already closed, the next request hits a dead socket. Typical safe pair: server keep-alive 65s behind an LB with 60s idle364- Add a readiness probe that flips to failing on SIGTERM before the drain starts. The orchestrator stops routing new traffic while in-flight requests finish365366## What NOT to Force367368- Do not force cursor pagination onto every small internal list or backoffice table369- Do not cut `/v2` just because a new field got added370- Do not insist on OAuth for every internal automation path if scoped API keys or platform identity fit better371- Do not push first-party browser apps toward bearer-token-in-local-storage patterns because "JWT auth" sounds modern372- Do not turn simple Express services into pseudo-enterprise architectures with needless layers and decorators373374---375376## Reference Files377378- `references/http-api-patterns.md` - resource design, method semantics, status codes, versioning, pagination, filtering, idempotency379- `references/auth-and-session-patterns.md` - sessions vs bearer tokens, OAuth/OIDC, BFF, refresh tokens, machine clients380381## Output Contract382383See `references/output-contract.md` for the full contract.384385- **Skill name:** BACKEND-API386- **Deliverable bucket:** `audits`387- **Mode:** conditional. When invoked to **analyze, review, audit, or improve** existing repo content, emit the full contract - monospace inline header, severity-grouped inline summary, linked Markdown deliverable, and concise monospace conclusion - and write the deliverable to `docs/local/audits/backend-api/<YYYY-MM-DD>-<slug>.md`. When invoked to **answer a question, teach a concept, build a new artifact, or generate content**, respond freely without the contract.388- **Severity scale:** `P0 | P1 | P2 | P3 | info` (see shared contract; only used in audit/review mode).389390## Related Skills391392- **databases** - schema design, query tuning, migrations, and persistence concerns behind the API393- **code-review** - correctness bugs, logic errors, and non-API-specific review findings394- **security-audit** - auth bypasses, OWASP findings, secrets, and security review after the API design exists395- **testing** - route, integration, contract, and end-to-end verification396- **docker** - containerizing API services397- **kubernetes** - deploying API services on clusters398- **networking** - reverse proxies, TLS termination, CORS-adjacent network boundaries, and gateway behavior399- **ci-cd** - delivery pipelines for API services400- **mcp** - MCP-specific HTTP servers and auth patterns401402## Rules4034041. **Contract before handlers.** Design the resource model, schemas, errors, and auth boundary before writing route code.4052. **Do not let transport leak persistence.** Database tables, ORM entities, and internal enums are not public API contracts.4063. **Pick one error format.** Prefer RFC 9457 and apply it consistently.4074. **Use current auth guidance.** Authorization code + PKCE for public OAuth clients, sessions or BFF patterns for first-party browser apps, no implicit flow, no password grant.4085. **Keep framework structure boring.** Thin controllers or routes, explicit validation, explicit auth, centralized error handling.4096. **Backward compatibility is a feature.** New fields are cheap; breaking clients is expensive.4107. **Write for real retries.** Assume clients, proxies, and job runners will replay requests.4118. **Keep scope on HTTP APIs.** When the problem is GraphQL or gRPC-specific, say so and stop pretending the same rules apply.4129. **Match service conventions unless migrating them.** A one-off endpoint with a different error, auth, or pagination model is usually a contract bug.
Run npx skillmds@latest add iuliandita/backend-api in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
· Design/review HTTP APIs for FastAPI, Express, NestJS: REST, OpenAPI, pagination, OAuth/JWT. Triggers: 'fastapi', 'express', 'nestjs', 'openapi', 'pagination', 'idempotency', 'rest api', 'endpoint'. Not for schemas (use databases). It is listed under Integrations & APIs on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Independent scanners report: SkillSpector: CAUTION, Skill Scanner: PASS. Capability flags: makes network calls, reads secrets. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
iuliandita (@iuliandita) published this skill. Their other Agent Skills are listed on their SkillMD profile.