Node.js Database: Complete Theory & Practice Guide
Core database theory + practical PostgreSQL, MongoDB, MySQL patterns for production Node.js apps.
Table of Contents
- Theory: Database Fundamentals
- Theory: SQL vs NoSQL
- Theory: Connection Management
- Theory: Query Optimization
- Basic: PostgreSQL with node-postgres
- Basic: MongoDB with Mongoose
- Basic: MySQL with mysql2
- Advanced: Transaction & ACID
- Advanced: Migration & Schema Management
- Best Practices
- Interview Q&A + MCQ
- Contextual Learning Links
1. Theory: Database Fundamentals
A database is an organized collection of structured information stored electronically. In Node.js applications, databases persist data beyond the application's lifecycle.
| Operation | SQL | MongoDB | HTTP Equivalent |
|---|---|---|---|
| Create | INSERT | insertOne() | POST |
| Read | SELECT | findOne() | GET |
| Update | UPDATE | updateOne() | PUT/PATCH |
| Delete | DELETE | deleteOne() | DELETE |
const databaseConcepts = {
Index: { definition: 'Faster lookup', tradeoff: 'Read speed vs write overhead' },
Transaction: { definition: 'All-or-nothing operations', properties: 'ACID' },
ConnectionPool: { definition: 'Reusable connections', benefit: 'Lower latency' },
QueryPlan: { definition: 'Optimizer execution strategy', tool: 'EXPLAIN' }
};
2. Theory: SQL vs NoSQL
| Aspect | SQL | NoSQL |
|---|---|---|
| Data Model | Tables | Documents |
| Schema | Rigid | Flexible |
| Relationships | JOINs | Embedding/refs |
| ACID | Strong | Usually document-level |
const databaseSelectionGuide = {
chooseSQLWhen: ['Complex joins', 'Strict transactions', 'Predictable schema'],
chooseNoSQLWhen: ['Rapid schema change', 'Document-heavy data', 'Horizontal scale'],
hybridApproach: ['PostgreSQL + JSONB', 'Polyglot persistence']
};
3. Theory: Connection Management
const poolSizingTheory = {
formula: 'maxPoolSize = (coreCount × 2) + spindleCount',
warning: '(maxPoolSize × instances) < database max_connections'
};
const connectionStrings = {
PostgreSQL: 'postgresql://user:password@host:5432/db?ssl=true&pool_size=10',
MySQL: 'mysql://user:password@host:3306/db?connectionLimit=10',
MongoDB: 'mongodb://user:password@host:27017/db?maxPoolSize=10'
};
4. Theory: Query Optimization
-- Useful index patterns
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);
CREATE INDEX idx_active_users ON users(id) WHERE active = true;
const optimizationTechniques = {
good: 'SELECT id,name,email FROM users WHERE id = $1',
avoid: 'SELECT *',
use: ['EXPLAIN', 'Parameterized queries', 'Batch queries']
};
5. Basic: PostgreSQL with node-postgres
npm install pg
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME || 'myapp',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
6. Basic: MongoDB with Mongoose
npm install mongoose
const userSchema = new mongoose.Schema({
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
passwordHash: { type: String, required: true, select: false },
role: { type: String, enum: ['user', 'moderator', 'admin'], default: 'user' }
}, { timestamps: true });
7. Basic: MySQL with mysql2
npm install mysql2
const mysql = require('mysql2');
const pool = mysql.createPool({
host: process.env.MYSQL_HOST || 'localhost',
port: process.env.MYSQL_PORT || 3306,
database: process.env.MYSQL_DATABASE || 'myapp',
user: process.env.MYSQL_USER || 'root',
password: process.env.MYSQL_PASSWORD,
waitForConnections: true,
connectionLimit: 10
}).promise();
8. Advanced: Transaction & ACID
const acidProperties = {
Atomicity: 'All operations succeed or all fail',
Consistency: 'Data remains valid',
Isolation: 'Transactions do not interfere',
Durability: 'Committed writes survive crashes'
};
// PostgreSQL transfer transaction (condensed)
await client.query('BEGIN');
await client.query('UPDATE accounts SET balance = balance - $1 WHERE user_id = $2', [amount, fromUserId]);
await client.query('UPDATE accounts SET balance = balance + $1 WHERE user_id = $2', [amount, toUserId]);
await client.query('COMMIT');
9. Advanced: Migration & Schema Management
npm install node-pg-migrate pg
// migrations/001_create_users_table.js
exports.up = async (pgm) => {
pgm.createTable('users', {
id: { type: 'serial', primaryKey: true },
email: { type: 'varchar(255)', notNull: true, unique: true },
created_at: { type: 'timestamp', default: pgm.func('NOW()') }
});
pgm.createIndex('users', 'email');
};
exports.down = async (pgm) => pgm.dropTable('users');
// MongoDB migration shape
async function up(db) {
await db.createCollection('users');
await db.collection('users').createIndex({ email: 1 }, { unique: true });
}
async function down(db) {
await db.collection('users').drop();
}
10. Best Practices
const queryBestPractices = {
good: [
'Use pooling',
'Use parameterized queries',
'Index WHERE/JOIN columns',
'Monitor slow queries with EXPLAIN',
'Set query and connection timeouts'
]
};
// Quick commands
// PostgreSQL: psql -U postgres -d myapp
// MongoDB: mongosh
// MySQL: mysql -u root -p myapp
// Migrations: npm run migrate up
10 Interview Questions + 10 MCQs
1Why should Node.js apps use connection pools?easy
Answer: Pools reduce connection creation overhead and stabilize latency under concurrent load.
2SQL vs NoSQL - when would you pick SQL?easy
Answer: Prefer SQL when relationships, joins, and strict ACID guarantees are essential.
3What is ACID in one line?easy
Answer: A transaction model ensuring Atomicity, Consistency, Isolation, and Durability.
4What causes N+1 query problems?medium
Answer: Fetching parent rows and then querying child data row-by-row in loops.
5How do you verify index usage?medium
Answer: Use `EXPLAIN`/`EXPLAIN ANALYZE` and inspect scan/join strategies and row estimates.
6Why use parameterized queries?easy
Answer: They prevent SQL injection and improve safety for dynamic input.
7What is a good pool sizing rule of thumb?medium
Answer: Start around 8-20 per app instance and ensure total across instances stays below DB limits.
8Why are transactions critical for money movement?hard
Answer: They guarantee both debit and credit succeed together or rollback entirely.
9What are migrations used for?easy
Answer: Version controlling schema changes with repeatable up/down operations.
10What is one fast win for slow queries?medium
Answer: Index selective filter columns and avoid `SELECT *` on large tables.
10 Database MCQs
1
Which is safest for dynamic SQL input?
Explanation: Parameterized queries keep SQL structure separate from data input.
2
ACID property for "all-or-nothing" is:
Explanation: Atomicity ensures the transaction fully commits or fully rolls back.
3
Main benefit of connection pooling:
Explanation: Reusing connections avoids costly open/close cycles per request.
4
What usually indicates N+1 queries?
Explanation: N+1 happens when an initial query is followed by many per-row queries.
5
Best tool to inspect SQL execution strategy:
Explanation: EXPLAIN reveals chosen scans, joins, and estimates.
6
Which index helps multi-column filtering order-aware?
Explanation: Composite indexes optimize predicates that match indexed column order.
7
In transactions, `ROLLBACK` means:
Explanation: ROLLBACK discards in-flight transactional writes.
8
Why use migrations in teams?
Explanation: Migrations provide reproducible schema versioning across environments.
9
Typical SQL engine best for strict relational workloads:
Explanation: PostgreSQL is widely used for transactional relational systems.
10
A strong production DB baseline includes:
Explanation: These controls provide security, reliability, and performance fundamentals.