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.
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.
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:
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.
The default choice for most new projects. Powerful, free, and the deepest ecosystem — also the most to learn.
Older, still everywhere, slightly different tradeoffs. A safe, well-documented alternative to Postgres.
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.
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.
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.
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.
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.
Everything you'd now own
None of this is impossible. It's just real, ongoing engineering work — not a weekend project.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.