# Building Backends

> The foundational guide for how Claude should build backend systems — the rules, defaults, and judgment that apply to every service before reaching for a specific playbook. Read this FIRST for any backend work (new service, new endpoint, data model, auth, background job). Covers architecture, API design, the data layer, error handling, security, concurrency, and testing, and points to the specific skills (api-service-scaffold, db-migration-guardian, backend-observability) for the deep dives.

- Skill: `omonuj/building-backends` (Agent Skill)
- Install (CLI): `npx skillmds@latest add omonuj/building-backends`
- Raw SKILL.md: https://api.skillmd.com/api/skills/omonuj/building-backends/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: omonuj (https://skillmd.com/u/omonuj)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/omonuj/building-backends

---


# building-backends

This is the backbone skill for backend work. Read it before writing any server code — it sets the defaults and the non-negotiables so every service comes out consistent, then hands off to the specific skills for the detailed procedures. If a rule here conflicts with a local repo convention, the repo wins; if it conflicts with nothing, follow it.

## First, understand before you build
- **Read the existing code before adding to it.** Match the repo's framework, error model, layering, naming, and test style. A "correct" pattern that fights the codebase is the wrong pattern. Introducing a second way to do something the repo already does is a defect, not an improvement.
- **Clarify the contract, not the implementation.** Know the inputs, outputs, error cases, who calls it, and the consistency/latency expectations before choosing a design. Most backend rework comes from building the wrong contract fast.
- **Prefer boring.** Proven, readable, obvious solutions over clever ones. The backend runs unattended at 3am; optimize for the on-call engineer who has to understand it half-asleep, not for elegance.

## Architecture — the rules
- **Layer with dependencies pointing inward.** `transport (http) → services (use-cases) → domain (entities/rules)`, and `services → repositories (I/O) → domain`. The domain imports nothing outward and does no I/O. If an import arrow points the wrong way, the design is broken.
- **Handlers orchestrate, services decide, repositories do I/O.** A handler parses/validates input, calls a service, maps the result to a response — no business logic. A repository is the *only* place that touches a DB, queue, or external HTTP.
- **Keep the domain testable without infrastructure.** If you can't unit-test the core logic without a database or network, the logic is in the wrong layer.
- **One composition root.** Build config, wire dependencies, and start the server in a single place. Everything else receives its dependencies; nothing reaches out to construct global singletons.
- **Design stateless.** Session/scratch state lives in a shared store (DB/cache), not in process memory, so the service scales horizontally and survives a restart. If you're caching in a module-level variable, stop and think.

→ For standing up the skeleton with these layers already wired, use **api-service-scaffold**.

## API design — the rules
- **Explicit, versioned contracts.** Version the API (URL or header) from day one; never make a breaking change to a shipped contract — add the new shape alongside and deprecate the old.
- **Validate every input at the boundary** into a typed value. The core never sees a raw request body. Reject invalid input with 422 and field-level detail; never trust the client.
- **One error model, mapped once.** Domain code raises typed errors (`NotFoundError`, `ConflictError`, `ValidationError`, `UnauthorizedError`…); a single boundary mapper turns them into status + `{ code, message, details? }`. Never leak stack traces or internal messages. `404` for "not found" *and* "not yours" — distinguishing them leaks existence.
- **Consistent shapes.** Same envelope for success, same envelope for errors, consistent pagination (cursor over offset for large sets), consistent field casing. A caller should learn your conventions once.
- **Idempotency for unsafe operations.** `PUT`/`DELETE` are idempotent by definition; make `POST`s that can be retried safe with an idempotency key. Networks retry — a double-charged customer is a design bug.
- **Right status codes.** 2xx success, 4xx caller's fault, 5xx your fault. A validation failure is 422, not 200-with-an-error-body; an auth failure is 401/403, not 400.

## The data layer — the rules
- **The database is the source of truth; enforce invariants there.** Foreign keys, unique constraints, `NOT NULL`, and checks belong in the schema, not only in application code — app code has bugs and races; the DB doesn't lie.
- **Transactions wrap invariants, and are as short as possible.** Everything that must be all-or-nothing goes in one transaction; do no slow work (external calls, heavy compute) while holding one. Long transactions hold locks and starve everyone else.
- **Expect concurrency.** Two requests will hit the same row at once. Use optimistic locking (a version column) or appropriate isolation; never read-modify-write across a round trip without protection.
- **Migrations are backwards-compatible and reversible.** Never change a column's meaning in one deploy; expand→migrate→contract. Never take a table-blocking lock on a hot table. Every migration ships with a tested rollback.
- **Watch the N+1.** Fetch related data in a set, not in a loop of queries. Profile the query count on real endpoints, not just the latency.
- **Index the queries you actually run** — and only those. An unused index is write-amplification for nothing.

→ For the migration mechanics (locks, expand-contract, backfills, rollbacks), use **db-migration-guardian**.

## Security — the baseline (non-negotiable)
- **Authenticate then authorize, on every request, server-side.** Never trust a client claim about who it is or what it may do. Check ownership on every resource access — "I have a valid token" ≠ "this record is mine".
- **Parameterize every query.** No string-built SQL, ever. Same for any injectable interface (shell, LDAP, template).
- **Secrets come from the environment/secret manager, never the code.** Nothing sensitive in source, logs, or error messages. (See the `secrets-management` skill.)
- **Least privilege everywhere** — DB users, service credentials, cloud roles scoped to exactly what the service needs.
- **Rate-limit and set timeouts** on inbound endpoints and outbound calls. An unbounded call or an un-limited endpoint is a denial-of-service waiting to happen.
- **Validate and encode at the boundaries** to prevent injection and SSRF; treat every external input, including from other internal services, as untrusted.

## Concurrency, jobs, and failure
- **Assume every external call fails.** Set timeouts, add bounded retries with backoff + jitter, and a circuit breaker for a flaky dependency. A retry without a timeout is how one slow dependency takes down the whole service.
- **Make operations idempotent** so a retry (yours or the caller's) is safe.
- **Background work goes on a queue,** not in the request path. The request enqueues; a worker processes; failures land in a dead-letter queue for inspection, not a silent `catch {}`.
- **Fail loud, degrade gracefully.** Surface errors to telemetry; where a non-critical dependency is down, degrade (serve stale, skip the enrichment) rather than 500 the whole request.

## Observability is part of "done", not an afterthought
A service you can't debug in production isn't finished. Structured logs, RED/USE metrics, and tracing tied to one correlation id go in as you build, not after the first incident.

→ Use **backend-observability** for the instrumentation details and the cardinality/PII guardrails.

## Testing stance
- **Test behavior through the public surface** (the endpoint / the use-case), not private internals — so tests survive refactors.
- **A pyramid, not an ice-cream cone:** many fast unit tests on domain/service logic, a focused set of integration tests over the real DB and wiring, a few end-to-end tests on critical paths.
- **Every bug fix gets a regression test** that fails before the fix and passes after.
- **Deterministic tests.** No reliance on wall-clock, network, or ordering; seed data explicitly; isolate tests from each other.

## Procedure for any backend task
1. Read the surrounding code; nail the contract (inputs/outputs/errors/callers/consistency).
2. Place the work in the right layer; keep the domain I/O-free.
3. Validate input at the edge; use the one error model; return the right status.
4. Put invariants in the DB + a short transaction; handle concurrency; plan migrations as expand-contract.
5. Apply the security baseline (authz + ownership, parameterized queries, secrets, timeouts/limits).
6. Make external calls and jobs resilient and idempotent.
7. Instrument as you go; add tests at the right layer, including a regression test for any bug.
8. Confirm against the relevant specific skill's `Definition of done`.

## Definition of done
- Layers respected (domain does no I/O; dependencies point inward); one composition root.
- Every input validated at the boundary; one error model mapped once; correct status codes; versioned contract.
- Invariants enforced in the DB; transactions short; concurrency handled; migrations backwards-compatible + reversible.
- Security baseline met: authz + ownership checks, parameterized queries, secrets external, timeouts/rate limits present.
- External calls/jobs idempotent and resilient; background work off the request path.
- Observability wired (logs/metrics/traces on a correlation id); tests at the right layers, green, with regression coverage.

