The database is the beating heart of any backend application. Whether you are building a simple to-do app or a global e-commerce platform, choosing the right database and structuring your data correctly is the most critical architectural decision you will make.
Step 1 — Relational (SQL) vs NoSQL
The first major decision is choosing between a Relational (SQL) database or a NoSQL database.
Relational Databases (SQL)
- Examples: PostgreSQL, MySQL, SQLite.
- Structure: Data is stored in tables with strictly defined columns (schemas).
- Relationships: Tables are linked together using Primary Keys and Foreign Keys.
- Best For: Financial systems, ERPs, and apps where data integrity and strict relationships are crucial.
NoSQL Databases
- Examples: MongoDB, DynamoDB, Redis.
- Structure: Data is stored as JSON-like documents, key-value pairs, or wide columns. No strict schema required.
- Relationships: Typically, data is denormalized (nested) rather than joined.
- Best For: Rapid prototyping, catalog data, real-time analytics, and systems requiring massive horizontal scaling.
Step 2 — ACID Properties
ACID is a set of properties that guarantee database transactions are processed reliably. SQL databases strongly enforce ACID.
Step 3 — Normalization
Normalization is the process of structuring a relational database to reduce data redundancy and improve data integrity.
- First Normal Form (1NF): Every column must hold atomic (indivisible) values. No comma-separated lists in a single column.
- Second Normal Form (2NF): Must be in 1NF, and all non-key attributes must depend on the ENTIRE primary key.
- Third Normal Form (3NF): Must be in 2NF, and there must be no transitive dependencies (columns depending on other non-key columns).
Step 4 — Database Indexing
An index is a data structure (usually a B-Tree) that improves the speed of data retrieval operations, at the cost of slower writes and increased storage space.
-- The database must scan EVERY row in the users table to find 'kuldeep' (O(n) time)
SELECT * FROM users WHERE username = 'kuldeep';-- We create an index on the username column
CREATE INDEX idx_username ON users(username);
-- Now, the database uses a binary search on the B-Tree index to find the user in O(log n) time!
SELECT * FROM users WHERE username = 'kuldeep';Step 5 — The N+1 Query Problem
The N+1 query problem is the most common performance killer in backend frameworks utilizing ORMs.
// 1 Query to fetch 50 posts
const posts = await db.query('SELECT * FROM posts LIMIT 50');
for (const post of posts) {
// 50 Queries executed inside the loop to fetch the author for each post!
const author = await db.query('SELECT * FROM users WHERE id = ?', post.author_id);
console.log(post.title, author.name);
}
// Total Queries: 1 + 50 = 51 queries.// 1 Query to fetch 50 posts
const posts = await db.query('SELECT * FROM posts LIMIT 50');
const authorIds = posts.map(p => p.author_id);
// 1 Query to fetch all 50 authors at once using SQL 'IN' clause
const authors = await db.query('SELECT * FROM users WHERE id IN (?)', authorIds);
// Total Queries: 2 queries instead of 51!Step 6 — Connection Pooling
Opening a new connection to a database is extremely slow (requires TCP handshake, authentication, etc.). If your API opens a new connection for every request, it will crash under load.