---
name: local-http-backend
description: "Spin up a self-hosted HTTP/REST backend on the user's machine with a local DB (SQLite by default) — for trackers, dashboards, prototypes, integrations, anything that needs CRUD over the network without cloud deployment. FastAPI + SQLAlchemy + Uvicorn stack, port 8000 default, verified with live curl."
version: 1.0.0
author: hermes (community)
license: MIT
platforms: [linux, macos, windows]
metadata:
  hermes:
    tags: [HTTP, REST, FastAPI, SQLAlchemy, SQLite, Uvicorn, Backend, CRUD, Local]
prerequisites:
  commands: [uv]
---

# Local HTTP Backend

When the user asks for "a backend I can hit over HTTP", "a tiny API on my machine",
"a local REST server with a DB", "something I can read/write via curl" — build it
this way. One pass, no over-engineering, end-to-end verified.

## When to load

- User wants an HTTP service running on their host (localhost or LAN)
- Persistence in a local file (not Postgres, not cloud)
- CRUD over REST, swagger/docs nice-to-have
- Used as building block for a CLI/agent/frontend that needs an API
- Domain is irrelevant: tracker, dashboard, prototype, integration glue, logging hub

Do NOT load this skill for: cloud-deployable production APIs, async workers,
WebSockets-as-primary, high-throughput services, anything that needs Postgres
out of the gate. For those, design first, don't template.

## Default stack (proven)

- **FastAPI** — auto docs at `/docs`, Pydantic validation out of the box
- **SQLAlchemy 2.x** + **SQLite** — zero-config file DB, scales fine for personal use
- **Uvicorn** — ASGI server, hot-reload optional
- **Dependency manager: uv** — see pitfall #1 below

## Scaffolding (proven layout)

```
~/puls-backend/                  # or any other name the user gave
├── pyproject.toml               # project + deps, see templates/
├── README.md                    # endpoints, run command, fields
├── app/
│   ├── __init__.py
│   ├── db.py                    # engine + SessionLocal + get_db()
│   ├── models.py                # SQLAlchemy ORM
│   ├── schemas.py               # Pydantic v2: Base / Create / Update / Out
│   └── main.py                  # FastAPI app + routes
└── data/
    └── <name>.db                # SQLite file, auto-created
```

A worked example from a real session lives in `references/puls-backend-example.md`.

A reusable pattern on top of this stack — auto-populating the backend from
IMAP mail via a cron-driven poller (e.g. job-application tracker) — lives
in `references/mail-driven-tracker.md`. Load it when the user says "every
time an email arrives about X, also update my tracker."

## Workflow (do in order, no skipping)

1. **Confirm domain + one field set.** Don't ask 5 questions; pick a sensible model
   (e.g. for "track X with status": id, name, status, timestamps, notes) and ship.
   User can extend later.
2. **Lay out the project.** `mkdir -p ~/puls-backend/app ~/puls-backend/data` (or
   the user's chosen name).
3. **Write pyproject.toml first** — uv sync needs it. See `templates/pyproject.toml`.
4. **Write app files.** Keep schemas split (Base / Create / Update / Out) so PUT
   is partial — `model_dump(exclude_unset=True)` is the magic line.
5. **Run `uv sync`** in project root. Install completes in seconds.
6. **Start server background.** Use `terminal(background=true, notify_on_complete=false)`
   — long-lived daemon, no exit to notify on. See pitfall #2.
7. **Smoke-test every route with curl.** `/health` and a real CRUD round-trip
   (POST → GET → PUT → DELETE). Capture the output. Don't claim success on
   "the server started".
8. **Seed real data when relevant.** If the user already has the data in another
   system (e.g. emails for job applications), POST each row through the real API
   — this verifies write path AND populates the DB in one step.

## Endpoints (typical pattern)

| Verb   | Path                | Notes                                              |
|--------|---------------------|----------------------------------------------------|
| GET    | `/`                 | tiny banner with doc pointer                       |
| GET    | `/health`           | `{"status":"ok"}` for uptime checks                |
| GET    | `/items`            | list, support `?status=` and `?q=` filters         |
| GET    | `/items/{id}`       | 404 on miss                                        |
| POST   | `/items`            | 201 on create, 422 if Pydantic rejects             |
| PUT    | `/items/{id}`       | partial updates via Pydantic Update model          |
| DELETE | `/items/{id}`       | 204 on success                                     |

Filter on read: `db.query(Model).filter(...).order_by(LastUpdate.desc()).all()`.
Default sort = most-recently-updated first — usually what humans want.

## Model field recipe

For any "tracker" the user asks for, this set is the spine:

```
id           int PK
<entity>_name str, indexed
status       str, enum-literal via Pydantic
<created_at> date
last_update  datetime, onupdate=utcnow
notes        text, optional
source       str, optional (where the row came from)
external_id  str, optional (cross-ref to other system)
contact_*    name/email, optional
```

Use Pydantic `Literal["a","b","c"]` for status — gives free 422 on bad values.

## Pitfalls (read before writing)

1. **`python -m venv` does NOT work on this Windows host.** The `python` shim
   points at the hermes-agent venv, not a fresh environment. Don't waste time
   debugging "venv directory exists but pip install doesn't take". Use `uv sync`
   against a `pyproject.toml` — it creates `.venv/` and installs deps in one shot.
   See memory: "Windows MSYS bash: never pass `$_` or `\\` to powershell.exe..."

2. **Server as background process: use `notify_on_complete=false`.** The server
   never exits, so notifying on completion is noise. `notify_on_complete=true`
   is for bounded jobs (tests, builds, batch jobs).

3. **`uvicorn --reload` is fine in dev but breaks Pydantic v2's startup speedup**
   in some configs. For headless verification, run without --reload.

4. **Bind to `127.0.0.1`, not `0.0.0.0`, unless LAN access is needed.** Binding
   0.0.0.0 exposes the API to the entire local network — fine when intentional,
   risky when not. Confirm with user first if LAN access matters.

5. **SQLite + FastAPI threads:** `create_engine(url, connect_args={"check_same_thread": False})`
   is mandatory, otherwise the second concurrent request crashes.

6. **Pydantic v2 config:** `class Config: from_attributes = True` becomes
   `model_config = {"from_attributes": True}` — v1 syntax still floats around
   the internet and silently does nothing in v2.

7. **Don't `Base.metadata.create_all()` at import time if you plan to use Alembic.**
   Fine for prototypes, fatal once you need migrations.

8. **Use `urllib.request` from `execute_code` for verification**, not subprocess
   curl — keeps everything in one Python process, errors raise as exceptions
   you can catch and assert against (e.g. assert HTTPError.code == 422).

## Templates / references

- `templates/pyproject.toml` — minimal uv-managed Python project with FastAPI/SQLAlchemy
- `references/puls-backend-example.md` — full worked session: job-application tracker
  for Chris Bringemeier, including the actual API outputs and seeding log