Backend Engineering Skill
Standards for building reliable and testable backends in software development.
Standards for building reliable, testable backends. Read this BEFORE writing any backend code.
Pairs with:
frontend-engineering-skill.md— the frontend consumes the error/response formats defined here.
API Response Format (CANONICAL)
All API endpoints MUST return this envelope:
Success: {data: T, meta?: {total: number, page: number, limit: number, pages: number}}
Error: {error: {code: string, message: string, details?: [{field: string, message: string}]}}
- Single resource:
{data: {id: 1, name: "..."}} - List:
{data: [...], meta: {total: 42, page: 1, limit: 20, pages: 3}} - Error:
{error: {code: "VALIDATION_ERROR", message: "Invalid input", details: [{field: "email", message: "Required"}]}} - NEVER return bare arrays, bare objects, or
{success: false}wrappers
Pagination
Any list endpoint that could grow beyond 20 items MUST support pagination:
GET /api/items?page=1&limit=20
→ {data: [...], meta: {total, page, limit, pages}}
Default: page=1, limit=20. Frontend expects meta to render pagination controls.
Input Validation
Every POST/PUT endpoint MUST:
- Validate ALL required fields — empty string is NOT valid for required fields
- Validate types and constraints (email format, positive numbers, string length)
- Return 400 with field-level errors so the frontend can show inline messages:
{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid input", "details": [ {"field": "email", "message": "Valid email required"}, {"field": "name", "message": "Name is required"} ] } } - Backend MUST re-validate even if frontend validates — never trust client input
HTTP Status Codes
Use consistently across all endpoints:
200— success (GET, PUT, PATCH)201— created (POST that creates a resource)400— validation error (withdetailsarray)404— resource not found409— conflict (duplicate unique field, concurrent edit)500— server error (log full trace, return generic message to client)
NEVER return HTML error pages from API endpoints. NEVER return 200 for errors.
External Service Fallback
AI/LLM features and external APIs MUST work without credentials:
- Check for API key at request time, not at startup — the app must boot regardless
- When API key is missing: return 200 with mock/fallback data, NOT 503
- Mock data must be realistic and contextual (statistical analysis, pattern-based suggestions), not "lorem ipsum"
- Log a warning:
"AI features running in mock mode — set ANTHROPIC_API_KEY for full functionality" - The UI must show the same components for mock and real data — the user shouldn't notice the difference
Real-time Communication
Decision tree
- Need bidirectional messages? → WebSocket
- Server-push only (notifications, status updates)? → SSE (simpler, HTTP-native)
- Low frequency updates (<1/min)? → Polling (simplest, most reliable)
WebSocket implementation checklist
A WebSocket feature is NOT "implemented" until ALL of these work:
- Client connects — verify via console log or connection indicator in UI
- Client sends message → server receives and processes it
- Server broadcasts → ALL connected clients receive the message
- Received messages update the DOM (not just console.log)
- Reconnection on disconnect with exponential backoff
- Connection cleanup on page unload (
beforeunloadevent) - Test: open two browser tabs, action in tab A appears in tab B
Anti-Patterns (NEVER do these)
- Endpoint described in spec but not implemented → returns 404 → automatic FAIL
- Accepting empty or invalid POST/PUT body silently (200 OK with garbage data)
- AI endpoint returning 503 "Service Unavailable" with no fallback
- WebSocket that connects but never sends or processes messages
- Different error response shapes from different endpoints
- Returning 200 for failed operations with
{success: false, error: "..."} - HTML error pages from API endpoints (FastAPI default 422 page)
- Startup crash when optional env vars (API keys) are missing