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.

  1. First Normal Form (1NF): Every column must hold atomic (indivisible) values. No comma-separated lists in a single column.
  2. Second Normal Form (2NF): Must be in 1NF, and all non-key attributes must depend on the ENTIRE primary key.
  3. 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.

Query without an indexsql
-- The database must scan EVERY row in the users table to find 'kuldeep' (O(n) time)
SELECT * FROM users WHERE username = 'kuldeep';
Adding an indexsql
-- 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.

The Problem (Pseudo-code)javascript
// 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.
The Solution (Eager Loading)javascript
// 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.