Relational vs NoSQL Databases
Fixed schemas and joins vs flexible documents and horizontal scale — how to actually choose.
3 min read
Almost every backend needs to persist data somewhere durable, and the first major fork in that decision is relational versus NoSQL. Neither is a strict upgrade over the other — they make different structural tradeoffs, and picking the wrong one for your data shape causes pain later that's expensive to undo.
Relational databases
Relational databases (PostgreSQL, MySQL, SQLite) organize data into tables with a fixed schema, and model relationships between tables using foreign keys.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT UNIQUE NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
total NUMERIC NOT NULL
);
SELECT users.email, orders.total
FROM orders
JOIN users ON orders.user_id = users.id
WHERE orders.total > 100;The schema enforces structure up front — you can't insert an order with a user_id that doesn't exist — and JOINs let you query across related tables in one statement. This makes relational databases the strong default for data with clear structure and relationships: users, orders, inventory, anything where consistency between related records matters.
NoSQL databases
"NoSQL" covers several different models, but the most common in backend work is the document store (MongoDB, DynamoDB), which stores flexible, JSON-like documents instead of fixed rows:
{
"_id": "42",
"email": "ada@example.com",
"orders": [
{ "total": 149.99, "status": "shipped" },
{ "total": 29.99, "status": "pending" }
]
}There's no schema enforced by the database itself — different documents in the same collection can have different fields — and related data is often embedded directly rather than joined. This fits data that's naturally nested or document-shaped, evolves quickly, or needs to scale writes horizontally across many servers more easily than a traditional relational setup.
The real tradeoff
| | Relational | NoSQL (document) |
|---|---|---|
| Schema | Fixed, enforced | Flexible, enforced by app code (if at all) |
| Relationships | JOINs across tables | Usually embedded or manually referenced |
| Consistency | Strong (ACID transactions) | Varies — often eventual consistency at scale |
| Horizontal scaling | Harder, though possible | Often designed for it from the start |
| Best fit | Structured, relational data | Unstructured or rapidly evolving data, high write volume |
Choosing in practice
Default to relational unless you have a specific reason not to — most application data (users, orders, permissions) is genuinely relational, and giving up JOINs and schema enforcement for flexibility you don't need just moves validation work into application code. Reach for a document store when your data is naturally document-shaped (a content management system's pages, a product catalog with wildly varying attributes per category) or when you have write-throughput or scale requirements that a single relational instance genuinely can't meet.
It's also common for a single system to use both — a relational database for core transactional data, and a document or key-value store for a specific high-volume or loosely structured subsystem. Whichever you choose, the schema itself needs a disciplined way to evolve over time — which is exactly what migrations solve, next.