The backend, explained — step 3

What is a backend, actually?

Every blog post, event, and form submission has to live somewhere. Here's what actually goes into storing it properly, and the fast way to skip all of it.

← Step 2: Git

01

What a backend actually does

The part of a website nobody sees, and the part that actually remembers things.

The frontend is the part you can point at: the homepage, the colors, the layout. A backend is everything behind it that a page can't do on its own — a place to durably store information, and the logic that reads and writes it safely. Two pieces, always: a data store and the code that talks to it.

Anything on a site that has to be remembered needs both. A blog post has to still be there tomorrow. An event needs a real, growing list of who registered. A contact form has to land somewhere a human will actually see it. None of that happens by accident — something has to store it, and something has to fetch it back out correctly, every time.

The backend is also where a site talks to everything outside itself — syncing to Notion, sending through Google, adding someone to a Mailchimp list. Every one of those is backend code: an API key kept out of the browser, a request made securely on a server, a response handled correctly whether it succeeds or fails.

02

What Gravity gives you today: JSON files

Real storage, not a demo — and exactly the right amount of it to start.

Out of the box, Gravity stores everything — blog posts, events, form submissions, portal accounts — as plain JSON files under data/content/. One file per record. No database to install, nothing running in the cloud, nothing to configure. This genuinely works. Plenty of small sites never need anything else.

It also has real limits, and they show up in a predictable order as a site grows:

  • It's not queryable. “Posts published last month, tagged X” means loading and filtering every file in Python — fine at 50 posts, slow at 5,000.
  • It's not concurrency-safe. Two people saving at once can race and silently overwrite each other. There's no transaction to make “read, change, save” atomic.
  • It's not relational. An event and its 200 registrants are two files that happen to reference each other by an ID — nothing enforces that link or cleans it up.
  • It's not always even persistent. Stateless hosts wipe local disk on every deploy — you already need a GCS bucket or Vercel Blob store just to survive redeploys.
03

So you add a database. Now what?

This is where “just use a real database” turns into its own project.

SQL isn't a database — it's the language you use to talk to one. A SQL database (Postgres, MySQL, SQLite) is software that stores rows in tables and enforces rules about them: a registrant has to reference a real event, two writes to the same row can't corrupt each other, a query across ten thousand rows comes back in milliseconds instead of a Python loop. That layer — SQL, sitting between your application and the actual stored bytes — is what JSON files don't have.

Postgres

The default choice for most new projects. Powerful, free, and the deepest ecosystem — also the most to learn.

MySQL

Older, still everywhere, slightly different tradeoffs. A safe, well-documented alternative to Postgres.

SQLite

One file, zero setup — great for a prototype. Risky once real concurrent traffic starts writing to it.

Whichever one you pick, it has to run somewhere: a managed service you now pay for and depend on, or a server you patch, back up, and keep awake yourself. Either way, that's infrastructure you own now, not a file that just sits in your repo.

04

Migrations: the part nobody warns you about

Your schema will change. Every environment has to change with it, in the same order, without losing data.

01

Your schema changes constantly

You'll add a status column to events, a phone field to contacts, a whole new table for something you didn't plan for on day one. In production, you can't just hand-edit the database — your laptop, staging, and production all need the exact same schema, applied in the exact same order.

02

A migration is a versioned, revertible script

A migration tool tracks every schema change as its own small script — add this column, backfill this default, drop that old table — that can be applied or rolled back on demand, and run in the same order everywhere.

03
alembic revision --autogenerate -m "add status to events"

In Flask, that's SQLAlchemy + Alembic

The standard pairing in the Python world: SQLAlchemy defines your tables as Python classes, and Alembic generates and runs the migration scripts that keep the real database's columns in sync with them — on your machine, in CI, and in production, every single time.

05

Everything you'd now own

None of this is impossible. It's just real, ongoing engineering work — not a weekend project.

The tally
choose + hosta database engine, and something to run it on
designa schema for every content type: blog, events, forms, contacts
write + reviewa migration for every schema change, forever
secureparameterized queries, credentials, network access
operatebackups, point-in-time recovery, uptime, connection limits
repeatthis modeling work for every new feature you add
This is also where reputations get lost

A backend isn't just code — it's other people's data. Lose it, corrupt it, or leave it exposed, and it's not an outage anymore: it's every user's information gone, real legal exposure depending on what you store, and a trust hit a site doesn't easily recover from. This isn't a reason to avoid a real backend — it's the reason backups, security, and careful migrations aren't optional extras. Get them right from day one.

You don't have to build all of this yourself.

Everything above is real, and yes — an AI coding agent genuinely can build a backend for you. But “can” isn't “should.” The error handling, the security review, the backup strategy: those are exactly the parts that are easy to skip when you're moving fast, and expensive to have skipped later. Three honest options:

Build it yourself

Everything in the sections above — your database, your migrations, your security review, your backups. Full control and full ownership, which also means full responsibility. A real option, not a wrong one — just make it a decision, not a default.

Coming soon
Purchase a Gravity backend

A backend built and maintained by EIM that you run — more hands-on than Adhara, without starting from an empty database. Not available yet — ask in Slack to be notified.

Connect Adhara

EIM's fully managed backend — already built, hosted, and maintained. The least control of the three, and by far the least to own. Available today, details below.

What connecting Adhara actually gets you.

Adhara is EIM's managed backend: the database, the schema, the migrations, and the API layer for blog, events, forms, CRM, and commerce — already built, already hosted, already maintained. Gravity already knows how to talk to it.

Minutes, not weeks

No schema to design, no Alembic to learn, no server to patch. Set three environment variables and Gravity switches from local JSON to a real backend — the same way it already works today.

Already secure, already backed up

Parameterized queries, access control, backups, and uptime are Adhara's job, not yours. The engineering work in the section above is exactly what you're paying to not do.

Local first. Always.

You need to be able to run your backend on your own machine, offline, with a database only you can touch — and separately, deploy it to real cloud infrastructure for the world to use. Two environments, clearly separated. Not one database standing in for both.

The cloud-only-dev trap

A lot of today's popular tooling — Supabase and similar platforms are the common example — points your local app straight at a database that only exists in the cloud, even while you're developing. No WiFi, no work. And with no separate local database to break things in, it's alarmingly easy to end up testing — and occasionally corrupting — your real, live user data by accident.

Separate on purpose

Run Postgres in Docker on your laptop for development — disposable, resettable, entirely yours. Deploy a real, managed Postgres instance for production. Same schema, same migrations, completely different data. You can drop and rebuild your local database a hundred times a day; you should never be able to do that to production by mistake.

The prompt we'd actually give an LLM.

Don't start from a blank prompt. Two decisions matter more than the rest: a real Postgres database running locally in Docker from day one, and a FastAPI service in front of it. Copy this into your AI coding agent as a starting point.

prompt
Set up a backend for this project, following these requirements:

1. Local first: add a `postgres` service to docker-compose.yml (postgres:16, a named
   volume so data survives restarts, credentials from .env) so I can develop against a
   real database on my own machine, offline, before anything touches the cloud. This is
   a separate database from production — never the same instance.

2. API layer: a FastAPI service, separate from the frontend, that talks to that
   Postgres database. Pydantic models for request/response validation, a routers/
   structure grouped by resource, and a /health endpoint.

3. Data layer: SQLAlchemy models plus Alembic for migrations — every schema change is
   a reviewable, revertible migration file committed to git, never a hand-edited table.

4. Security basics: no raw SQL string interpolation (SQLAlchemy handles
   parameterization), secrets and the database URL from environment variables only,
   never hardcoded or committed.

5. Production: the same schema and migrations, deployed to a real managed Postgres
   instance and containerized API on real cloud infrastructure — configured entirely
   through environment variables, so the exact same code runs in both places.

6. Make it maintainable: a README section explaining how to run it locally
   (docker compose up, then alembic upgrade head), a seed script for sample data, and
   basic tests covering create/read/update/delete for at least one resource.

Ask me before making any architectural decision I haven't specified above.

This is a starting point, not a finished spec — a good agent will still ask you questions. That's a feature, not friction: the questions it asks are exactly the decisions in the “Everything you'd now own” section above, made explicit instead of guessed at.

The API is next.

Whichever backend you choose, the frontend still needs a way to actually talk to it — that's the API, the connection between this frontend and whatever's on the other end.