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: · 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).4license: MIT5---67# Backend APIs: HTTP Service Design and Implementation89Design and review HTTP APIs that stay coherent as they grow. Focus on contracts, auth10boundaries, error models, and framework structure for Python and Node.js services.1112**Target versions** (September 2026):13- FastAPI **0.141.1**14- Express **5.2.1** (published 2025-12-01)15- NestJS **12.0.1** (major release; check migration notes before upgrading)16- OpenAPI Specification **3.2.0** (published 2025-09-19)17- HTTP Semantics: **RFC 9110** (June 2022)18- Problem Details for HTTP APIs: **RFC 9457** (July 2023)19- OAuth 2.0 Security Best Current Practice: **RFC 9700** (January 2025)2021This skill works across five concerns:22- **Contract design** - resources, methods, status codes, schemas, versioning23- **API ergonomics** - pagination, filtering, sorting, idempotency, error format24- **Framework structure** - FastAPI dependencies, Express middleware, NestJS modules and guards25- **Authentication** - sessions, bearer tokens, OAuth/OIDC, BFF and token mediation26- **Review** - unstable contracts, DTO leakage, auth confusion, and HTTP misuse2728## When to use2930- Designing a new REST or HTTP API31- Reviewing an existing FastAPI, Express, or NestJS service32- Writing or fixing OpenAPI specifications and generated docs33- Choosing status codes, error formats, pagination, filtering, or versioning strategy34- Designing auth flows for browser, mobile, machine-to-machine, or third-party clients35- Refactoring route or controller structure that has become hard to reason about36- Planning backward-compatible API evolution37- Defining idempotency and retry behavior for write endpoints3839## When NOT to use4041- GraphQL schema, resolvers, federation, or persisted query work - out of scope for this skill42- gRPC, protobuf schema evolution, or streaming RPCs - out of scope for this skill43- Database schema, indexing, replication, or query tuning - use **databases**44- General bug-finding, race-condition hunting, or correctness review outside the API boundary - use **code-review**45- Security auditing for auth bypasses, injection, secrets, or OWASP findings - use **security-audit**46- Writing or debugging automated tests - use **testing**47- Deployment, containers, gateways, ingress, or cluster config - use **docker**, **kubernetes**, or **networking**48- CI/CD pipeline design for API delivery - use **ci-cd**49- MCP-specific HTTP servers and tool handlers - use **mcp**5051---5253## AI Self-Check5455Before returning API code, route design, or OpenAPI output, verify:5657- [ ] Resource names are nouns and URL shape is stable (`/users/{userId}/sessions`, not `/doUserSessionThing`)58- [ ] Method semantics follow RFC 9110 - no "GET that mutates", no PATCH used as a vague catch-all59- [ ] `401` vs `403` is correct: unauthenticated vs authenticated-but-forbidden60- [ ] Ownership-hiding behavior is deliberate and consistent: decide when out-of-scope resources return `404` vs `403`61- [ ] Error responses use one consistent format, preferably RFC 9457 problem details62- [ ] Request and response DTOs are explicit and separate from ORM or database models63- [ ] Input validation happens server-side even if clients or SDKs also validate64- [ ] Offset pagination is not used blindly on large or high-churn collections where cursor pagination is the safer default65- [ ] Write endpoints that may be retried (`POST /payments`, webhook receivers, order creation) define idempotency behavior66- [ ] OpenAPI docs match real handler behavior, examples, status codes, and auth requirements67- [ ] First-party browser apps do not get long-lived bearer tokens stored in browser storage by default68- [ ] Cookie-based browser auth accounts for cookie scope and CSRF behavior instead of assuming cookies are automatically safe69- [ ] OAuth guidance is current: authorization code + PKCE, no implicit flow, no resource owner password credentials70- [ ] Sensitive defaults are explicit: cookie flags, token TTLs, scope boundaries, and rate limits are not hand-waved71- [ ] **Framework version checked**: FastAPI, Express, NestJS, and OpenAPI examples match current APIs and migration notes72- [ ] **Contract compatibility checked**: response codes, pagination, errors, and auth behavior preserve existing clients unless a version bump is explicit73- [ ] **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 widespread74- [ ] Cross-cutting agent hygiene applied - see `references/agent-hygiene.md`7576---7778## Performance7980- Paginate and filter at the data store; never load full collections just to slice in the handler.81- Set timeouts and body limits at the framework, proxy, and client layers.82- Measure p95/p99 latency and error rates for changed endpoints before optimizing internals.838485---8687## Best Practices8889- Design idempotency for retries on create, payment, provisioning, and webhook endpoints.90- Generate or validate OpenAPI from the implementation contract and keep examples executable.91- Use explicit auth scopes and tenancy checks in handlers, not only in route grouping or UI state.929394## Workflow9596**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.9798### Step 1: Determine the boundary99100Clarify the API before picking framework patterns:101- **Who calls it?** Browser app, mobile app, third-party integrator, internal service, webhook sender102- **What kind of API is it?** Public API, private app backend, internal service, admin API103- **Is the task greenfield or review?** New design, incremental change, migration, or bug fix104- **What stability promise exists?** Internal-only, versioned public API, or "best effort"105- **What auth model already exists?** Session cookies, JWT bearer, API keys, OAuth/OIDC, or none yet106- **What house style already exists?** Error format, pagination shape, versioning scheme, auth boundary, DTO naming107108If the user asks to "just add an endpoint", inspect the surrounding service first. Most bad API109work happens when one route lands with a different error model, auth rule, pagination shape, or110DTO style than the rest of the service.111112### Step 2: Choose the framework pattern113114Pick the framework that matches the codebase and team constraints. Do not switch frameworks for fashion.115116| Framework | Best fit | Watch for |117|-----------|----------|-----------|118| **FastAPI** | Python services with typed request/response models and strong OpenAPI output | Leaking ORM models directly, async confusion, piling business logic into route functions |119| **Express** | Thin Node.js services, custom middleware stacks, existing mature codebases | No built-in structure, validation scattered across middleware, inconsistent error handling |120| **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 |121122Framework rules:123- **FastAPI** - keep dependencies explicit, use response models, and centralize exception handling124- **Express** - keep routing, validation, auth, and business logic separated; add one error middleware path125- **NestJS** - keep controllers thin, move auth into guards, validation into pipes, and cross-cutting behavior into interceptors or filters126127### Step 3: Design the contract first128129Define the API contract before writing handlers:130131For changes to an existing service, match the current URL, error, pagination, and auth house132style unless the task explicitly includes a migration.1331341. Pick resource shapes and URIs1352. Choose methods and status codes1363. Define request and response schemas1374. Define error shapes1385. Decide pagination, filtering, sorting, and versioning rules1396. Only then wire the framework implementation140141Prefer:142- Noun-based resources: `/users/{userId}/sessions`143- Predictable list endpoints: `GET /orders?cursor=...&limit=...&status=paid`144- Explicit write semantics: `POST` for create/commands, `PUT` for full replacement, `PATCH` for partial update, `DELETE` for delete145- Explicit retry semantics for command endpoints like `POST /orders/{orderId}/cancel`146- One canonical error model across the whole service147148Read `references/http-api-patterns.md` for method semantics, pagination, idempotency, and versioning rules.149150### Step 4: Design auth around the client, not around JWT hype151152Start from the client type:153154| Client | Default choice | Why |155|--------|----------------|-----|156| **First-party browser app** | Session cookie or BFF/token-mediating backend | Keeps tokens off the browser as much as possible |157| **Native/mobile app** | OAuth/OIDC authorization code + PKCE | Current standard path for public clients |158| **Third-party integrator** | OAuth/OIDC or scoped API keys if OAuth is overkill | Explicit delegation and revocation story |159| **Internal service-to-service** | Platform identity or short-lived service credentials | Avoid user-style auth flows between services |160161Auth rules:162- Use sessions by default for first-party web apps unless there is a clear reason not to163- Use bearer tokens when the client really is a token-holding client164- Treat API keys as machine credentials, not as a universal auth shortcut165- Define scope or role boundaries at the API boundary, not ad hoc inside random handlers166- If the browser app talks to third-party identity providers, follow authorization code + PKCE and current browser-app guidance rather than resurrecting implicit flow patterns167168BFF token mediation (first-party browser app calling an external API):169```170Browser -> BFF (session cookie) -> BFF attaches Bearer token -> Upstream API171```172The BFF holds the access token server-side; the browser never sees it.173174Read `references/auth-and-session-patterns.md` for sessions, bearer tokens, OAuth, BFF, refresh tokens, and machine auth.175176### Step 5: Implement the framework surface177178Convert the contract into framework code without letting the framework dictate the contract.179180**FastAPI**181- Define Pydantic models for requests and responses182- Use dependency injection for auth, DB/session acquisition, and shared request context183- Register exception handlers for consistent RFC 9457 output184- Keep route handlers thin; move business rules into services or domain modules185186**Express**187- Validate inputs before controller logic188- Attach auth and request context in middleware189- Use one shared error-handling middleware path190- Keep controllers as transport adapters, not the place where every rule in the system lives191192**NestJS**193- Use DTO classes for transport boundaries194- Put validation in pipes and auth in guards195- Use exception filters or interceptors to normalize response and error behavior196- Keep module boundaries meaningful; one module per vague concept is not architecture197198### Step 6: Validate behavior and docs199200Before returning the result:201- Compare OpenAPI docs against the real routes202- Verify auth requirements per endpoint203- Check all non-2xx responses, not just the happy path204- Verify list endpoints under empty, partial, and end-of-cursor states205- Check retries and duplicate submissions for create/payment/webhook paths206- Check repeat-call behavior for command endpoints (`cancel`, `resend`, `approve`) instead of leaving retries implicit207- Compare new endpoints with neighboring endpoints for naming, DTO shape, pagination params, and error consistency208- Route follow-up testing work to **testing** and follow-up security review to **security-audit**209210---211212## Contract Guardrails213214### Versioning215216- Prefer additive change over version churn217- Version only when you need a breaking change218- Keep one versioning scheme for the service: path versioning (`/v1/...`) is the clearest default for public APIs219- Do not mix path versioning, header versioning, and ad hoc `?version=` query params in one API220221### OpenAPI authoring222223- OpenAPI `3.2.0` is the current spec, but much of the framework and Swagger ecosystem still centers on `3.1.x`224- Default to authoring for `3.1` compatibility unless the actual toolchain in the project proves `3.2` support end-to-end225- Do not advertise `3.2` features in generated specs just because the top-level standard moved226227### Error model228229- Prefer RFC 9457 problem details with stable `type`, `title`, `status`, and domain-specific extension fields230- Do not return one error shape from validation, another from auth, and a third from business rules231- Never leak raw stack traces or ORM internals to clients232233Minimal RFC 9457 problem detail response:234```json235{236 "type": "https://api.example.com/errors/insufficient-funds",237 "title": "Insufficient funds",238 "status": 422,239 "detail": "Account balance is $10.00; transfer requires $50.00."240}241```242243Status code decision matrix (pick the narrowest correct code):244245| Scenario | Code | Notes |246|----------|------|-------|247| Malformed JSON, missing required field, wrong type | `400` | Client request is syntactically wrong |248| Well-formed input but fails domain rule (insufficient funds, invalid state) | `422` | Semantic validation |249| No credentials, expired token | `401` | Include `WWW-Authenticate` header |250| Authenticated but not permitted | `403` | Do not challenge for credentials |251| Resource does not exist, or exists but caller must not know | `404` | Pick one policy per resource and keep it |252| Write conflicts with current state (stale ETag, duplicate unique key) | `409` | Problem detail should name the conflict |253| Idempotency-Key reused with a different body | `422` | Not `409` - the key contract is broken |254| Too many requests | `429` | Include `Retry-After` |255| Unhandled server error | `500` | Never leak stack traces in the body |256257FastAPI exception handler wiring for consistent problem+json:258```python259from fastapi import FastAPI, Request260from fastapi.responses import JSONResponse261from fastapi.exceptions import RequestValidationError262263app = FastAPI()264265@app.exception_handler(RequestValidationError)266async def validation_handler(request: Request, exc: RequestValidationError):267 return JSONResponse(268 status_code=400,269 media_type="application/problem+json",270 content={271 "type": "https://api.example.com/errors/validation",272 "title": "Invalid request",273 "status": 400,274 "errors": exc.errors(),275 },276 )277```278279### Pagination and filtering280281- Offset pagination is fine for small, stable backoffice lists282- Cursor pagination is the default for user-facing or high-write collections283- Sort order must be stable and documented284- Filter names should reflect resource fields or clear domain concepts, not internal SQL column names285286### Idempotency and retries287288- Define idempotency for writes that clients or gateways may retry289- Use explicit idempotency keys for payment-like or request-replay-prone operations290- Distinguish "request accepted" from "side effect completed" when async workflows exist291292Idempotency key header pattern (Express/NestJS):293```typescript294const key = req.headers['idempotency-key'];295if (key) {296 const cached = await cache.get(`idem:${key}`);297 if (cached) return res.status(cached.status).json(cached.body);298}299// ... execute, then store result keyed by idempotency-key before returning300```301302Idempotency key header pattern (FastAPI):303```python304from fastapi import Header, HTTPException305306async def create_order(307 body: OrderCreate,308 idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),309):310 if idempotency_key:311 cached = await cache.get(f"idem:{idempotency_key}")312 if cached:313 return JSONResponse(status_code=cached["status"], content=cached["body"])314 result = await orders.create(body)315 if idempotency_key:316 await cache.set(f"idem:{idempotency_key}", {"status": 201, "body": result}, ttl=86400)317 return result318```319320Cursor pagination response envelope:321```json322{323 "data": [...],324 "next_cursor": "eyJpZCI6MTIzfQ",325 "has_more": true326}327```328Decode the cursor server-side (`WHERE id > :cursor_id ORDER BY id LIMIT :limit`). Never expose raw DB offsets or row numbers in the cursor.329330Opaque cursor encode/decode (FastAPI, HMAC-signed to prevent client tampering):331```python332import base64, hmac, hashlib, json, os333334SECRET = os.environ["CURSOR_SECRET"].encode()335336def encode_cursor(payload: dict) -> str:337 body = base64.urlsafe_b64encode(json.dumps(payload, separators=(",", ":")).encode()).rstrip(b"=")338 sig = base64.urlsafe_b64encode(hmac.new(SECRET, body, hashlib.sha256).digest()[:8]).rstrip(b"=")339 return f"{body.decode()}.{sig.decode()}"340341def decode_cursor(token: str) -> dict:342 body, sig = token.split(".", 1)343 expected = base64.urlsafe_b64encode(hmac.new(SECRET, body.encode(), hashlib.sha256).digest()[:8]).rstrip(b"=").decode()344 if not hmac.compare_digest(sig, expected):345 raise ValueError("invalid cursor")346 return json.loads(base64.urlsafe_b64decode(body + "=="))347```348Clients treat `next_cursor` as opaque; the server controls shape and can change it without a breaking contract.349350### Graceful shutdown and rolling deploys351352Rolling deploys send SIGTERM to old instances while new ones come up. Handle it on the API side or expect 502s under load:353354- 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` handlers355- 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 work356- 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 idle357- Add a readiness probe that flips to failing on SIGTERM before the drain starts. The orchestrator stops routing new traffic while in-flight requests finish358359## What NOT to Force360361- Do not force cursor pagination onto every small internal list or backoffice table362- Do not cut `/v2` just because a new field got added363- Do not insist on OAuth for every internal automation path if scoped API keys or platform identity fit better364- Do not push first-party browser apps toward bearer-token-in-local-storage patterns because "JWT auth" sounds modern365- Do not turn simple Express services into pseudo-enterprise architectures with needless layers and decorators366367---368369## Reference Files370371- `references/http-api-patterns.md` - resource design, method semantics, status codes, versioning, pagination, filtering, idempotency372- `references/auth-and-session-patterns.md` - sessions vs bearer tokens, OAuth/OIDC, BFF, refresh tokens, machine clients373374## Output Contract375376See `references/output-contract.md` for the full contract.377378- **Skill name:** BACKEND-API379- **Deliverable bucket:** `audits`380- **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.381- **Severity scale:** `P0 | P1 | P2 | P3 | info` (see shared contract; only used in audit/review mode).382383## Related Skills384385- **databases** - schema design, query tuning, migrations, and persistence concerns behind the API386- **code-review** - correctness bugs, logic errors, and non-API-specific review findings387- **security-audit** - auth bypasses, OWASP findings, secrets, and security review after the API design exists388- **testing** - route, integration, contract, and end-to-end verification389- **docker** - containerizing API services390- **kubernetes** - deploying API services on clusters391- **networking** - reverse proxies, TLS termination, CORS-adjacent network boundaries, and gateway behavior392- **ci-cd** - delivery pipelines for API services393- **mcp** - MCP-specific HTTP servers and auth patterns394395## Rules3963971. **Contract before handlers.** Design the resource model, schemas, errors, and auth boundary before writing route code.3982. **Do not let transport leak persistence.** Database tables, ORM entities, and internal enums are not public API contracts.3993. **Pick one error format.** Prefer RFC 9457 and apply it consistently.4004. **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.4015. **Keep framework structure boring.** Thin controllers or routes, explicit validation, explicit auth, centralized error handling.4026. **Backward compatibility is a feature.** New fields are cheap; breaking clients is expensive.4037. **Write for real retries.** Assume clients, proxies, and job runners will replay requests.4048. **Keep scope on HTTP APIs.** When the problem is GraphQL or gRPC-specific, say so and stop pretending the same rules apply.4059. **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. 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.