S
Claude Code logoClaude CodeBackend APIs (NestJS / FastAPI)

Claude Code Backend API Forge

Ben Halvorsen@backendben
92.0Overall score

Postgres MCP plus a schema-designer subagent means the model reasons about the database before writing handlers, and a security-reviewer pass catches injection and auth gaps. The migration dry-run hook prevents destructive schema changes.

92.0Score
1.0kVotes
5Components
2wUpdated

Install this build

Export
terminal
npx setuproll add claude-code-backend-api

Components

Model

  • Claude Opus 4.8

MCP servers

  • postgres
  • github
  • sentry
  • filesystem

Subagents

  • schema-designer
  • implementer
  • security-reviewer

Hooks

  • PreToolUse: secret-scan
  • PostToolUse: run migrations dry-run
  • Stop: contract tests

Rules

  • Validate inputs at the boundary
  • No raw SQL without parameterization
  • Every endpoint needs an integration test
Setup

The agent doesn't touch my Postgres until it has read the schema

How I wired Claude Code to reason about the database first, scan for secrets before every write, and refuse to merge an endpoint without an integration test.

backendben9 min read2026-06-18

I have a rule that predates any of this AI stuff: the first thing you do before changing a backend is read what is already there. The schema, the constraints, the migration history. Most outages I have cleaned up came from somebody writing a handler against a mental model of the table that was three months out of date. So when I started letting Claude Code into our NestJS services, I did not give it a blank cheque. I gave it the same discipline I would expect from a junior who wants to keep their access.

This is the setup I actually run, day to day, on a payments-adjacent API. Model is Claude Opus 4.8. Four MCP servers, three subagents, three hooks. Nothing exotic. The point was never to look clever. The point was to make the agent fast where it is safe and slow where it can hurt me.

The shape of it

PieceWhat I runWhy
ModelClaude Opus 4.8I want the long-horizon reasoning for schema work, not the cheapest token
MCPpostgres, github, sentry, filesystemRead the DB, the PRs, the live errors, the tree
Subagentsschema-designer, implementer, security-reviewerSplit planning, writing, and auditing into separate contexts
Hookssecret-scan, migration dry-run, contract testsBlock the three ways I have actually been burned

The Postgres MCP is the one that earns its keep. Without it the agent guesses at column types and writes handlers that compile and then explode on the first real row. With it, the agent runs \d+ orders and reads the actual constraints before it writes a line. That sounds small. It is the difference between a green CI and a 2am page.

CLAUDE.md: the non-negotiables

My CLAUDE.md is short on purpose. A long memory file is a memory file nobody reads, including the model. I keep it to the three things I am not willing to argue about on a PR.

CLAUDE.md
# API service rules

## Boundaries
- Validate every input at the boundary with a DTO + class-validator. No "trust me" payloads.
- No raw SQL without parameterized queries. If you reach for string concat on a query, stop.
- Every endpoint ships with an integration test that hits the real router, not a mocked service.

## Database
- Read the schema before writing handlers. Use the postgres MCP, run \d on the table.
- Migrations are forward-only. No editing an already-applied migration.
- Never DROP or TRUNCATE in a migration the agent authored. Flag it for a human.

## Style
- NestJS conventions: module, controller, service, no logic in controllers.
- Errors throw typed exceptions, never bare strings.
- If you are unsure about a foreign key, ask. Do not invent one.
Keep it boring
Every line in that file maps to a real incident I sat through. I do not add rules because they sound responsible. I add them after something breaks. If you cannot point to the bug a rule prevents, it is probably noise.

The schema-designer subagent

This is the part people skip and then wonder why the agent writes garbage migrations. The schema-designer runs in its own context, with read access to Postgres and nothing that can write. Its whole job is to come back with a plan: tables, columns, indexes, the migration order. The implementer never gets to design schema. It gets handed one.

.claude/agents/schema-designer.md
---
name: schema-designer
description: Plans database schema changes. Reads the live schema, proposes migrations, never writes application code.
tools: Read, Grep, mcp__postgres__query
model: opus
---

You design Postgres schema changes for a payments-adjacent API.

Before proposing anything:
1. Inspect the current schema for every table you intend to touch.
2. Check existing indexes and foreign keys. Do not duplicate them.
3. Confirm nullability and defaults against real data shape.

Output a plan, not code:
- The DDL for each migration, in order.
- A one-line rollback note per migration.
- Any data backfill that the migration assumes.

Hard rules:
- Forward-only migrations.
- No DROP / TRUNCATE without an explicit human-approval flag.
- Money columns are NUMERIC, never float. If you see a float column for
  currency, call it out as a bug.

Splitting it out like this matters more than the prompt wording. When the implementer is also trying to design the database, both jobs get worse. The plan gets sloppy because it is racing to the code, and the code gets sloppy because the plan was an afterthought. Two contexts, two jobs.

Claude Code Tutorial #8 - Subagents12:36
Claude Code Tutorial #8 - Subagents· Net Ninja

Hooks: the parts I don't trust the prompt to enforce

Rules in CLAUDE.md are suggestions. The model usually follows them. Usually is not good enough when the thing on the line is a credentials leak or a destructive migration. So the three things I genuinely care about are not rules, they are hooks. Code runs, deterministically, whether the model feels like it or not.

.claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "scripts/secret-scan.sh \"$CLAUDE_FILE_PATH\""
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "scripts/migrations-dry-run.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "pnpm test:contract"
          }
        ]
      }
    ]
  }
}

The secret-scan hook is a thirty-line shell script wrapping gitleaks against the file about to be written. If it finds an AWS key or a DB URL with a password baked in, it exits non-zero and the write never lands. The migration dry-run applies pending migrations against a throwaway database and rolls back. If the DDL would fail, I find out before it touches anything real. The Stop hook runs contract tests so the session cannot end on a green vibe and a red suite.

The dry-run hook has saved me twice
Both times the agent wrote a migration that added a NOT NULL column to a populated table with no default. Compiles fine. Looks fine. Would have locked the table and failed on deploy. The dry-run caught it against a copy of prod before I ever saw the diff.

What a real session looks like

zsh - orders-api
$claude "add a refunds endpoint, full refund only for now"
schema-designer spins up, read-only on postgres
[schema-designer] inspecting orders, payments, refunds ...
[schema-designer] refunds table missing. proposing migration 0042_create_refunds.
[schema-designer] amount as NUMERIC(12,2), fk payments(id), idempotency_key UNIQUE.
implementer takes the plan, writes the migration + handler
[hook PreToolUse] secret-scan: clean
[hook PostToolUse] migrations dry-run: 0042 applied + rolled back OK
[implementer] wrote refunds.controller.ts, refunds.service.ts, refunds.dto.ts
security-reviewer pass before stop
[security-reviewer] auth guard present, idempotency enforced, amount validated. no raw SQL.
[hook Stop] test:contract ... 11 passed
$git diff --stat
4 files changed, 187 insertions(+)
$
security-reviewer - refunds endpoint
You
Review the refunds endpoint for auth and injection gaps before we stop.
Agent
Auth: @UseGuards(JwtGuard) present on the controller. Good. Idempotency: idempotency_key is read from the header and enforced at the unique constraint, so a double-submit returns the original refund instead of charging twice. Good. Injection: query goes through the repository, parameterized, no string interpolation. Validation: amount is checked against the original payment, so an over-refund is rejected. One nit, the 404 for an unknown payment leaks whether the id exists. Acceptable here, but noting it.
The reviewer pass runs in its own context so the implementer's optimism doesn't leak in.

What I would not do

  • I do not give the implementer write access to Postgres. It writes migrations, a human or the dry-run hook applies them.
  • I do not skip the contract tests on the Stop hook to go faster. The whole point is that the session cannot lie to me.
  • I do not let the agent edit an already-applied migration. Forward-only, every time. Editing history is how you desync staging and prod.
  • I do not pile rules into CLAUDE.md hoping more text means more safety. The hooks are the safety. The text is etiquette.

The honest tradeoff: this setup is slower per task than just letting the model rip. The schema-designer adds a round trip. The reviewer adds another. The hooks add seconds. But the pass rate is around 90 percent on my work, and the failures it does have are loud and early, not silent and in prod. I will take a slower green over a fast red every single day.

Worth reading

Create custom subagents - Claude Code DocsThe official spec for subagent frontmatter, tool allowlists, and isolated context. Read this before you split your agents.code.claude.comdisler/claude-code-hooks-masteryCovers every hook lifecycle event with working examples. Where I cribbed the structure of my secret-scan and dry-run hooks.GitHub4.1k

If you run a backend with a database you care about, this is the safest way I have found to hand an agent real access without losing sleep. Pull the whole thing, hooks and subagents included, with npx setuproll add claude-code-backend-api, then trim my paranoia down to your own list of incidents. Keep the dry-run. Trust me on the dry-run.

0 Reviews

Your rating
Sign in to post

Loading discussion...