Database Integration - Complete Guide
যেকোনো ব্যাকএন্ড অ্যাপ্লিকেশনের প্রাণ হলো ডেটাবেস। Data persistence, retrieval, এবং management সবকিছুই database এর উপর নির্ভর করে।
Database Types Overview
1. SQL (Relational) Databases
Structure: Tables with rows and columns
Schema: Fixed, predefined structure
Query Language: SQL (Structured Query Language)
Popular SQL Databases:
- PostgreSQL - Most advanced open-source (ACID compliant)
- MySQL - Most popular (used by Facebook, Twitter)
- SQLite - Lightweight, file-based
- Microsoft SQL Server - Enterprise-grade
- MariaDB - MySQL fork
Best For:
- Complex relationships
- ACID transactions
- Financial applications
- Data integrity critical
- Structured data
2. NoSQL (Non-Relational) Databases
Structure: Flexible (documents, key-value, graph, etc.)
Schema: Dynamic, schema-less
Query Language: Database-specific APIs
Popular NoSQL Databases:
- MongoDB - Document-based (most popular)
- Redis - In-memory key-value store (caching)
- Cassandra - Wide-column store (scalability)
- Neo4j - Graph database (relationships)
- Couchbase - Document + key-value
Best For:
- Rapid development
- Flexible schema
- Horizontal scaling
- Real-time applications
- Unstructured data
SQL vs NoSQL - When to Use What?
| Feature | SQL | NoSQL |
|---|---|---|
| Schema | Fixed, predefined | Flexible, dynamic |
| Scalability | Vertical (scale up) | Horizontal (scale out) |
| Transactions | ACID guaranteed | Eventually consistent |
| Relationships | Built-in (JOINs) | Manual (references) |
| Data Structure | Structured | Unstructured/Semi-structured |
| Use Cases | Banking, ERP, CRM | Social media, IoT, Gaming |
| Learning Curve | Moderate | Easy (for simple use) |
| Performance | Good for complex queries | Fast for simple queries |
Decision Tree
Need strict data integrity? (Banking, Finance)
├── Yes → SQL (PostgreSQL)
└── No → Continue
Need complex relationships & JOINs?
├── Yes → SQL (PostgreSQL, MySQL)
└── No → Continue
Need horizontal scalability?
├── Yes → NoSQL (MongoDB, Cassandra)
└── No → SQL
Rapid development with changing requirements?
├── Yes → NoSQL (MongoDB)
└── No → SQL
Storing unstructured data? (JSON, logs)
├── Yes → NoSQL (MongoDB)
└── No → SQL১. MongoDB & Mongoose - Complete Guide
MongoDB = Document-oriented NoSQL database (stores data in JSON-like BSON format)
Mongoose = ODM (Object Data Modeling) library for MongoDB (makes working with MongoDB easier)
Why Mongoose?
Without Mongoose (Native MongoDB Driver):
- Manual validation
- No schema structure
- Complex queries
- Lots of boilerplate code
With Mongoose:
✅ Schema definition
✅ Built-in validation
✅ Middleware (hooks)
✅ Virtual properties
✅ Population (relationships)
✅ Query building
✅ Type castingInstallation & Setup
Install Mongoose
npm install mongoose
# Optional: Install MongoDB locally
# Or use MongoDB Atlas (cloud)Basic Connection
const mongoose = require('mongoose');
// Local MongoDB
const uri = 'mongodb://localhost:27017/my_database';
// MongoDB Atlas (cloud)
// const uri = 'mongodb+srv://username:password@cluster.mongodb.net/my_database';
mongoose.connect(uri)
.then(() => console.log('MongoDB Connected ✅'))
.catch(err => console.error('Connection Failed ❌', err));Advanced Connection with Options
const mongoose = require('mongoose');
const options = {
// Connection pool size
maxPoolSize: 10,
minPoolSize: 5,
// Timeout settings
serverSelectionTimeoutMS: 5000, // Timeout after 5s
socketTimeoutMS: 45000,
// Automatically reconnect
autoIndex: true, // Build indexes automatically
// Use new URL parser
useNewUrlParser: true,
useUnifiedTopology: true
};
mongoose.connect(uri, options)
.then(() => console.log('MongoDB Connected ✅'))
.catch(err => console.error('Connection Failed ❌', err));
// Connection events
mongoose.connection.on('connected', () => {
console.log('Mongoose connected to DB');
});
mongoose.connection.on('error', (err) => {
console.error('Mongoose connection error:', err);
});
mongoose.connection.on('disconnected', () => {
console.log('Mongoose disconnected');
});
// Graceful shutdown
process.on('SIGINT', async () => {
await mongoose.connection.close();
console.log('Mongoose connection closed due to app termination');
process.exit(0);
});Environment Variables (Best Practice)
// .env file
MONGODB_URI=mongodb://localhost:27017/my_database
NODE_ENV=development
// app.js
require('dotenv').config();
const uri = process.env.MONGODB_URI;
mongoose.connect(uri)
.then(() => console.log('MongoDB Connected'))
.catch(err => console.error(err));Schema Design - Complete Guide
Schema হলো document এর structure এবং validation rules এর blueprint।
Basic Schema
const mongoose = require('mongoose');
const { Schema } = mongoose;
const userSchema = new Schema({
name: String,
email: String,
age: Number,
isActive: Boolean,
createdAt: Date
});
const User = mongoose.model('User', userSchema);Schema with Validation
const userSchema = new Schema({
name: {
type: String,
required: [true, 'Name is required'],
trim: true, // Remove whitespace
minlength: [3, 'Name too short'],
maxlength: [50, 'Name too long']
},
email: {
type: String,
required: true,
unique: true, // Create unique index
lowercase: true, // Convert to lowercase
trim: true,
match: [/^\S+@\S+\.\S+$/, 'Invalid email format']
},
age: {
type: Number,
min: [18, 'Must be 18+'],
max: [120, 'Invalid age']
},
role: {
type: String,
enum: ['user', 'admin', 'moderator'], // Only these values
default: 'user'
},
password: {
type: String,
required: true,
minlength: 8,
select: false // Don't return in queries by default
},
avatar: {
type: String,
default: 'default-avatar.png'
},
isActive: {
type: Boolean,
default: true
},
tags: {
type: [String], // Array of strings
default: []
},
address: {
street: String,
city: String,
country: {
type: String,
default: 'Bangladesh'
}
}
}, {
timestamps: true, // Auto-create createdAt & updatedAt
versionKey: false // Disable __v field
});
const User = mongoose.model('User', userSchema);All Schema Types
const schema = new Schema({
// Basic types
string: String,
number: Number,
boolean: Boolean,
date: Date,
buffer: Buffer,
objectId: mongoose.Schema.Types.ObjectId,
mixed: mongoose.Schema.Types.Mixed, // Any type
decimal: mongoose.Schema.Types.Decimal128,
// Arrays
arrayOfStrings: [String],
arrayOfNumbers: [Number],
arrayOfObjects: [{
name: String,
value: Number
}],
// Nested objects
nested: {
field1: String,
field2: Number
},
// Map (key-value pairs)
mapField: {
type: Map,
of: String
}
});Custom Validation
const userSchema = new Schema({
email: {
type: String,
validate: {
validator: function(v) {
return /^\S+@\S+\.\S+$/.test(v);
},
message: props => `${props.value} is not a valid email!`
}
},
password: {
type: String,
validate: {
validator: function(v) {
// At least 8 chars, 1 uppercase, 1 lowercase, 1 number
return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(v);
},
message: 'Password must be at least 8 chars with uppercase, lowercase & number'
}
},
age: {
type: Number,
validate: {
validator: Number.isInteger,
message: '{VALUE} is not an integer'
}
}
});Async Validation
const userSchema = new Schema({
email: {
type: String,
validate: {
validator: async function(email) {
const user = await mongoose.models.User.findOne({ email });
return !user; // Return false if user exists
},
message: 'Email already exists'
}
}
});Schema Methods & Middleware
Instance Methods
userSchema.methods.getFullName = function() {
return `${this.firstName} ${this.lastName}`;
};
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
// Usage
const user = await User.findById(id);
console.log(user.getFullName());
const isMatch = await user.comparePassword('password123');Static Methods
userSchema.statics.findByEmail = function(email) {
return this.findOne({ email: email.toLowerCase() });
};
userSchema.statics.findActive = function() {
return this.find({ isActive: true });
};
// Usage
const user = await User.findByEmail('john@example.com');
const activeUsers = await User.findActive();Query Helpers
userSchema.query.byAge = function(age) {
return this.where({ age: age });
};
userSchema.query.active = function() {
return this.where({ isActive: true });
};
// Usage
const users = await User.find().byAge(25).active();Virtual Properties (Computed Fields)
userSchema.virtual('fullName').get(function() {
return `${this.firstName} ${this.lastName}`;
});
userSchema.virtual('fullName').set(function(name) {
const [firstName, lastName] = name.split(' ');
this.firstName = firstName;
this.lastName = lastName;
});
// Usage
const user = new User({ firstName: 'John', lastName: 'Doe' });
console.log(user.fullName); // "John Doe"
user.fullName = 'Jane Smith';
console.log(user.firstName); // "Jane"Middleware (Hooks)
Pre Hooks:
// Before save
userSchema.pre('save', async function(next) {
// Hash password if modified
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
// Before validate
userSchema.pre('validate', function(next) {
this.email = this.email.toLowerCase();
next();
});
// Before remove
userSchema.pre('remove', async function(next) {
// Delete user's posts when user is deleted
await this.model('Post').deleteMany({ author: this._id });
next();
});Post Hooks:
// After save
userSchema.post('save', function(doc, next) {
console.log(`User ${doc.name} has been saved`);
next();
});
// After find
userSchema.post('find', function(docs) {
console.log(`Found ${docs.length} users`);
});
// Error handling
userSchema.post('save', function(error, doc, next) {
if (error.name === 'MongoError' && error.code === 11000) {
next(new Error('Email already exists'));
} else {
next(error);
}
});Schema & Model Design
ডেটা দেখতে কেমন হবে তার ব্লুপ্রিন্ট হলো স্কিমা।
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
age: Number,
role: { type: String, default: 'user' }
});
// Model তৈরি (User কালেকশন তৈরি হবে)
const User = mongoose.model('User', userSchema);CRUD Operations - Complete Guide
Create (C)
Method 1: Using Constructor + save()
const user = new User({
name: 'John Doe',
email: 'john@example.com',
age: 25
});
await user.save();
console.log('User created:', user._id);Method 2: Using create()
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
age: 25
});Method 3: Insert Many (Bulk)
const users = await User.insertMany([
{ name: 'User 1', email: 'user1@example.com' },
{ name: 'User 2', email: 'user2@example.com' },
{ name: 'User 3', email: 'user3@example.com' }
]);
console.log(`${users.length} users created`);With Error Handling:
try {
const user = await User.create({
name: 'John',
email: 'john@example.com'
});
console.log('User created:', user);
} catch (error) {
if (error.name === 'ValidationError') {
console.error('Validation failed:', error.message);
} else if (error.code === 11000) {
console.error('Duplicate email');
} else {
console.error('Error:', error);
}
}Read (R)
Find All:
// All users
const users = await User.find();
// With conditions
const activeUsers = await User.find({ isActive: true });
// Multiple conditions
const users = await User.find({
age: { $gte: 18 },
isActive: true,
role: 'user'
});Find One:
// First match
const user = await User.findOne({ email: 'john@example.com' });
// By ID
const user = await User.findById('507f1f77bcf86cd799439011');
// With conditions
const user = await User.findOne({
age: { $gte: 18 },
isActive: true
});Select Specific Fields:
// Include only name and email
const users = await User.find().select('name email');
// Exclude password
const users = await User.find().select('-password');
// Multiple ways
const users = await User.find().select({ name: 1, email: 1, _id: 0 });Sorting:
// Ascending order
const users = await User.find().sort('age');
const users = await User.find().sort({ age: 1 });
// Descending order
const users = await User.find().sort('-age');
const users = await User.find().sort({ age: -1 });
// Multiple fields
const users = await User.find().sort({ age: 1, name: -1 });Limiting & Skipping:
// Limit results
const users = await User.find().limit(10);
// Skip results (pagination)
const users = await User.find().skip(20).limit(10);
// Page 3, 10 items per page
const page = 3;
const limit = 10;
const users = await User.find()
.skip((page - 1) * limit)
.limit(limit);Counting:
// Count all
const count = await User.countDocuments();
// Count with conditions
const count = await User.countDocuments({ isActive: true });
// Estimated count (faster but less accurate)
const count = await User.estimatedDocumentCount();Exists Check:
const exists = await User.exists({ email: 'john@example.com' });
console.log(exists); // { _id: '...' } or nullUpdate (U)
Update One:
// Find by ID and update
const user = await User.findByIdAndUpdate(
userId,
{ age: 26, name: 'John Updated' },
{
new: true, // Return updated document
runValidators: true // Run schema validators
}
);
// Find one and update
const user = await User.findOneAndUpdate(
{ email: 'john@example.com' },
{ $set: { age: 26 } },
{ new: true }
);Update Many:
// Update all matching documents
const result = await User.updateMany(
{ isActive: false },
{ $set: { status: 'inactive' } }
);
console.log(`${result.modifiedCount} users updated`);Update with Operators:
// Increment age by 1
await User.findByIdAndUpdate(userId, { $inc: { age: 1 } });
// Add to array
await User.findByIdAndUpdate(userId, { $push: { tags: 'new-tag' } });
// Remove from array
await User.findByIdAndUpdate(userId, { $pull: { tags: 'old-tag' } });
// Add to array (if not exists)
await User.findByIdAndUpdate(userId, { $addToSet: { tags: 'unique-tag' } });
// Set minimum value
await User.findByIdAndUpdate(userId, { $min: { age: 18 } });
// Set maximum value
await User.findByIdAndUpdate(userId, { $max: { age: 100 } });
// Multiply
await User.findByIdAndUpdate(userId, { $mul: { price: 1.1 } });
// Rename field
await User.updateMany({}, { $rename: { 'oldName': 'newName' } });
// Unset (remove field)
await User.findByIdAndUpdate(userId, { $unset: { tempField: 1 } });Update or Insert (Upsert):
const user = await User.findOneAndUpdate(
{ email: 'john@example.com' },
{ $set: { name: 'John', age: 25 } },
{
upsert: true, // Create if doesn't exist
new: true
}
);Update with Custom Logic:
const user = await User.findById(userId);
user.age += 1;
user.lastModified = Date.now();
await user.save(); // Triggers pre/post save hooksDelete (D)
Delete One:
// Find by ID and delete
const user = await User.findByIdAndDelete(userId);
console.log('Deleted user:', user);
// Find one and delete
const user = await User.findOneAndDelete({ email: 'john@example.com' });Delete Many:
// Delete all matching
const result = await User.deleteMany({ isActive: false });
console.log(`${result.deletedCount} users deleted`);
// Delete all (dangerous!)
const result = await User.deleteMany({});Delete with Middleware (Recommended):
const user = await User.findById(userId);
await user.remove(); // Triggers pre/post remove hooksAdvanced Queries
Comparison Operators
// Greater than ($gt, $gte)
const users = await User.find({ age: { $gt: 18 } });
const users = await User.find({ age: { $gte: 18 } });
// Less than ($lt, $lte)
const users = await User.find({ age: { $lt: 60 } });
const users = await User.find({ age: { $lte: 60 } });
// Not equal ($ne)
const users = await User.find({ role: { $ne: 'admin' } });
// In array ($in)
const users = await User.find({ role: { $in: ['admin', 'moderator'] } });
// Not in array ($nin)
const users = await User.find({ role: { $nin: ['banned', 'suspended'] } });
// Range
const users = await User.find({
age: { $gte: 18, $lte: 60 }
});Logical Operators
// AND (implicit)
const users = await User.find({
age: { $gte: 18 },
isActive: true
});
// AND (explicit)
const users = await User.find({
$and: [
{ age: { $gte: 18 } },
{ isActive: true }
]
});
// OR
const users = await User.find({
$or: [
{ role: 'admin' },
{ role: 'moderator' }
]
});
// NOT
const users = await User.find({
age: { $not: { $lt: 18 } }
});
// NOR
const users = await User.find({
$nor: [
{ role: 'admin' },
{ isActive: false }
]
});
// Complex combination
const users = await User.find({
$and: [
{ age: { $gte: 18 } },
{
$or: [
{ role: 'admin' },
{ isPremium: true }
]
}
]
});Element Operators
// Field exists
const users = await User.find({ phone: { $exists: true } });
// Type check
const users = await User.find({ age: { $type: 'number' } });Array Operators
// Array contains all
const users = await User.find({
tags: { $all: ['nodejs', 'mongodb'] }
});
// Array size
const users = await User.find({
tags: { $size: 3 }
});
// Element match
const users = await User.find({
orders: {
$elemMatch: { status: 'pending', amount: { $gt: 100 } }
}
});String Operators (Regex)
// Contains (case-sensitive)
const users = await User.find({
name: /john/
});
// Contains (case-insensitive)
const users = await User.find({
name: { $regex: 'john', $options: 'i' }
});
// Starts with
const users = await User.find({
name: /^john/i
});
// Ends with
const users = await User.find({
name: /son$/i
});Query Chaining
const users = await User
.find({ isActive: true })
.where('age').gte(18).lte(60)
.where('role').in(['user', 'premium'])
.select('name email age')
.sort('-createdAt')
.limit(20)
.skip(0)
.exec();Pagination Helper
async function paginate(page = 1, limit = 10, filters = {}) {
const skip = (page - 1) * limit;
const [users, total] = await Promise.all([
User.find(filters)
.select('-password')
.sort('-createdAt')
.skip(skip)
.limit(limit),
User.countDocuments(filters)
]);
return {
users,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit),
hasNext: page * limit < total,
hasPrev: page > 1
}
};
}
// Usage
const result = await paginate(2, 10, { isActive: true });Relationships & Population - Complete Guide
MongoDB তে relationships SQL এর মতো built-in না, তবে references এবং embedding দিয়ে implement করা যায়।
Relationship Types
1. One-to-One (1:1)
// User has one Profile
const userSchema = new Schema({
name: String,
email: String,
profile: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Profile'
}
});
const profileSchema = new Schema({
bio: String,
avatar: String,
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});2. One-to-Many (1:N)
// User has many Posts
const userSchema = new Schema({
name: String,
email: String
});
const postSchema = new Schema({
title: String,
content: String,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
}
});3. Many-to-Many (M:N)
// Student enrolls in many Courses, Course has many Students
const studentSchema = new Schema({
name: String,
courses: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Course'
}]
});
const courseSchema = new Schema({
title: String,
students: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Student'
}]
});Population (Join-like operation)
Basic Population:
// Create post with author reference
const post = await Post.create({
title: 'My Post',
content: 'Content here',
author: userId // Just store the ID
});
// Populate author details
const posts = await Post.find().populate('author');
// Result:
// {
// _id: '...',
// title: 'My Post',
// author: {
// _id: '...',
// name: 'John Doe',
// email: 'john@example.com'
// }
// }Select Specific Fields:
// Populate only name and email
const posts = await Post.find().populate('author', 'name email');
// Exclude _id
const posts = await Post.find().populate('author', 'name email -_id');Multiple Population:
const posts = await Post.find()
.populate('author', 'name email')
.populate('category', 'name')
.populate('comments');Nested Population:
const posts = await Post.find().populate({
path: 'comments',
populate: {
path: 'author',
select: 'name avatar'
}
});
// Result: Post → Comments → AuthorConditional Population:
const posts = await Post.find().populate({
path: 'author',
match: { isActive: true }, // Only populate if active
select: 'name email'
});Populate with Options:
const posts = await Post.find().populate({
path: 'comments',
options: {
sort: { createdAt: -1 },
limit: 10
}
});Deep Population:
const posts = await Post.find().populate({
path: 'author',
populate: {
path: 'profile',
populate: {
path: 'address'
}
}
});
// Post → Author → Profile → Address (3 levels deep)Virtual Populate (Reverse Population)
// User schema
const userSchema = new Schema({
name: String,
email: String
});
// Virtual field (not stored in DB)
userSchema.virtual('posts', {
ref: 'Post',
localField: '_id',
foreignField: 'author'
});
// Enable virtuals in JSON
userSchema.set('toJSON', { virtuals: true });
userSchema.set('toObject', { virtuals: true });
// Usage
const user = await User.findById(userId).populate('posts');
// Result:
// {
// _id: '...',
// name: 'John',
// posts: [
// { title: 'Post 1', ... },
// { title: 'Post 2', ... }
// ]
// }Embedding vs Referencing
Embedding (Denormalization):
// Good for: Data that's always accessed together
const userSchema = new Schema({
name: String,
email: String,
address: { // Embedded
street: String,
city: String,
country: String
},
tags: [String] // Embedded array
});
// Pros: Fast reads (single query), atomicity
// Cons: Data duplication, document size limit (16MB)Referencing (Normalization):
// Good for: Large or shared data
const userSchema = new Schema({
name: String,
email: String,
posts: [{ // Referenced
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}]
});
// Pros: No duplication, smaller documents
// Cons: Multiple queries, no atomicityDecision Guide:
Use Embedding when:
✅ Data is small
✅ Data doesn't change often
✅ Data is always accessed together
✅ Need atomic operations
✅ 1-to-1 or 1-to-few relationships
Use Referencing when:
✅ Data is large
✅ Data changes frequently
✅ Data is shared across documents
✅ Need to query independently
✅ 1-to-many or many-to-many relationshipsReal-World Example: Blog System
// User Model
const userSchema = new Schema({
name: String,
email: { type: String, unique: true },
avatar: String,
role: { type: String, enum: ['user', 'admin'], default: 'user' }
}, { timestamps: true });
// Virtual: User's posts
userSchema.virtual('posts', {
ref: 'Post',
localField: '_id',
foreignField: 'author'
});
const User = mongoose.model('User', userSchema);
// Category Model
const categorySchema = new Schema({
name: { type: String, unique: true },
slug: String,
description: String
}, { timestamps: true });
const Category = mongoose.model('Category', categorySchema);
// Post Model
const postSchema = new Schema({
title: { type: String, required: true },
slug: String,
content: String,
excerpt: String,
// References
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category'
},
// Embedded
tags: [String],
// Stats (embedded)
stats: {
views: { type: Number, default: 0 },
likes: { type: Number, default: 0 }
},
status: {
type: String,
enum: ['draft', 'published'],
default: 'draft'
}
}, { timestamps: true });
// Virtual: Post's comments
postSchema.virtual('comments', {
ref: 'Comment',
localField: '_id',
foreignField: 'post'
});
const Post = mongoose.model('Post', postSchema);
// Comment Model
const commentSchema = new Schema({
content: { type: String, required: true },
post: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Post',
required: true
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
parent: { // For nested comments
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
}, { timestamps: true });
const Comment = mongoose.model('Comment', commentSchema);
// Usage Examples:
// 1. Get all posts with author and category
const posts = await Post.find()
.populate('author', 'name avatar')
.populate('category', 'name')
.sort('-createdAt');
// 2. Get single post with comments and their authors
const post = await Post.findOne({ slug: 'my-post' })
.populate('author', 'name avatar email')
.populate('category')
.populate({
path: 'comments',
populate: {
path: 'author',
select: 'name avatar'
},
options: { sort: { createdAt: -1 } }
});
// 3. Get user with all their posts
const user = await User.findById(userId)
.populate({
path: 'posts',
match: { status: 'published' },
options: { sort: { createdAt: -1 }, limit: 10 }
});
// 4. Create post with relationships
const post = await Post.create({
title: 'My New Post',
content: 'Content here',
author: userId,
category: categoryId,
tags: ['nodejs', 'mongodb']
});
// 5. Add comment to post
const comment = await Comment.create({
content: 'Great post!',
post: postId,
author: userId
});
// 6. Get comments for a post
const comments = await Comment.find({ post: postId })
.populate('author', 'name avatar')
.sort('-createdAt');Aggregation Pipeline - Advanced Data Processing
Aggregation framework allows complex data processing and analysis.
Basic Aggregation
// $match - Filter documents
const results = await User.aggregate([
{ $match: { isActive: true } }
]);
// $group - Group by field
const results = await User.aggregate([
{
$group: {
_id: '$role',
count: { $sum: 1 },
avgAge: { $avg: '$age' }
}
}
]);
// $sort - Sort results
const results = await User.aggregate([
{ $sort: { createdAt: -1 } }
]);
// $limit & $skip - Pagination
const results = await User.aggregate([
{ $skip: 10 },
{ $limit: 5 }
]);
// $project - Select fields
const results = await User.aggregate([
{
$project: {
name: 1,
email: 1,
age: 1,
_id: 0
}
}
]);Advanced Aggregation
// Complete example: User statistics by role
const stats = await User.aggregate([
// Stage 1: Filter active users
{ $match: { isActive: true } },
// Stage 2: Group by role
{
$group: {
_id: '$role',
count: { $sum: 1 },
avgAge: { $avg: '$age' },
minAge: { $min: '$age' },
maxAge: { $max: '$age' },
users: { $push: '$name' }
}
},
// Stage 3: Sort by count
{ $sort: { count: -1 } },
// Stage 4: Project (rename fields)
{
$project: {
role: '$_id',
totalUsers: '$count',
averageAge: '$avgAge',
_id: 0
}
}
]);
// Result:
// [
// { role: 'user', totalUsers: 150, averageAge: 32.5 },
// { role: 'admin', totalUsers: 5, averageAge: 35.2 }
// ]Aggregation Operators
// $lookup - Join collections (like SQL JOIN)
const orders = await Order.aggregate([
{
$lookup: {
from: 'users', // Collection to join
localField: 'userId', // Field from Order
foreignField: '_id', // Field from User
as: 'user' // Output array field
}
},
{ $unwind: '$user' } // Convert array to object
]);
// $unwind - Deconstruct array
const posts = await Post.aggregate([
{ $unwind: '$tags' }, // One document per tag
{
$group: {
_id: '$tags',
count: { $sum: 1 }
}
}
]);
// $addFields - Add computed fields
const users = await User.aggregate([
{
$addFields: {
fullName: { $concat: ['$firstName', ' ', '$lastName'] },
isAdult: { $gte: ['$age', 18] }
}
}
]);
// $facet - Multiple pipelines
const results = await Product.aggregate([
{
$facet: {
byCategory: [
{ $group: { _id: '$category', count: { $sum: 1 } } }
],
priceRange: [
{
$bucket: {
groupBy: '$price',
boundaries: [0, 100, 500, 1000],
default: '1000+',
output: { count: { $sum: 1 } }
}
}
],
avgPrice: [
{ $group: { _id: null, avg: { $avg: '$price' } } }
]
}
}
]);Real-World Example: Analytics
// Monthly sales report
const monthlySales = await Order.aggregate([
// Filter this year
{
$match: {
createdAt: { $gte: new Date('2026-01-01') },
status: 'completed'
}
},
// Group by month
{
$group: {
_id: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' }
},
totalSales: { $sum: '$amount' },
totalOrders: { $sum: 1 },
avgOrderValue: { $avg: '$amount' }
}
},
// Sort by date
{ $sort: { '_id.year': 1, '_id.month': 1 } },
// Format output
{
$project: {
month: {
$concat: [
{ $toString: '$_id.year' },
'-',
{ $toString: '$_id.month' }
]
},
totalSales: { $round: ['$totalSales', 2] },
totalOrders: 1,
avgOrderValue: { $round: ['$avgOrderValue', 2] },
_id: 0
}
}
]);Transactions - ACID Compliance
MongoDB 4.0+ supports multi-document transactions.
Basic Transaction
const session = await mongoose.startSession();
session.startTransaction();
try {
// Operation 1: Deduct from sender
await Account.findByIdAndUpdate(
senderId,
{ $inc: { balance: -amount } },
{ session }
);
// Operation 2: Add to receiver
await Account.findByIdAndUpdate(
receiverId,
{ $inc: { balance: amount } },
{ session }
);
// Commit transaction
await session.commitTransaction();
console.log('Transaction successful');
} catch (error) {
// Rollback transaction
await session.abortTransaction();
console.error('Transaction failed:', error);
throw error;
} finally {
session.endSession();
}Transaction with Multiple Operations
async function transferMoney(senderId, receiverId, amount) {
const session = await mongoose.startSession();
try {
await session.withTransaction(async () => {
// Check sender balance
const sender = await Account.findById(senderId).session(session);
if (sender.balance < amount) {
throw new Error('Insufficient balance');
}
// Deduct from sender
sender.balance -= amount;
await sender.save({ session });
// Add to receiver
const receiver = await Account.findById(receiverId).session(session);
receiver.balance += amount;
await receiver.save({ session });
// Log transaction
await Transaction.create([{
from: senderId,
to: receiverId,
amount: amount,
timestamp: new Date()
}], { session });
});
console.log('Transfer successful');
} catch (error) {
console.error('Transfer failed:', error);
throw error;
} finally {
session.endSession();
}
}Indexing - Performance Optimization
Indexes dramatically improve query performance.
Create Indexes
// Single field index
userSchema.index({ email: 1 }); // Ascending
userSchema.index({ createdAt: -1 }); // Descending
// Compound index (multiple fields)
userSchema.index({ age: 1, name: 1 });
// Unique index
userSchema.index({ email: 1 }, { unique: true });
// Text index (full-text search)
postSchema.index({ title: 'text', content: 'text' });
// Sparse index (only indexed documents with the field)
userSchema.index({ phone: 1 }, { sparse: true });
// TTL index (auto-delete after time)
sessionSchema.index({ createdAt: 1 }, { expireAfterSeconds: 3600 });
// Partial index (conditional)
userSchema.index(
{ email: 1 },
{ partialFilterExpression: { isActive: true } }
);Using Indexes
// Text search
const posts = await Post.find({ $text: { $search: 'nodejs mongodb' } })
.select({ score: { $meta: 'textScore' } })
.sort({ score: { $meta: 'textScore' } });
// Compound index query (uses index)
const users = await User.find({ age: 25, name: 'John' });
// Query that doesn't use index (slow!)
const users = await User.find({ age: { $gte: 18 } }).sort({ email: 1 });
// age is indexed but email is not in same compound indexIndex Management
// List all indexes
const indexes = await User.collection.getIndexes();
// Drop index
await User.collection.dropIndex('email_1');
// Explain query (check if index is used)
const explain = await User.find({ email: 'john@example.com' }).explain();
console.log(explain.executionStats);
// Look for: "IXSCAN" (index scan) vs "COLLSCAN" (collection scan)Index Best Practices
// ✅ Good: Query uses index
User.find({ email: 'john@example.com' }); // email is indexed
// ✅ Good: Compound index for common queries
userSchema.index({ country: 1, city: 1, age: -1 });
User.find({ country: 'BD', city: 'Dhaka', age: { $gte: 18 } });
// ❌ Bad: Sort field not in index (slow!)
User.find({ age: 25 }).sort({ createdAt: -1 });
// Solution: Create compound index { age: 1, createdAt: -1 }
// ❌ Bad: Too many indexes (slow writes)
// Each index slows down inserts/updates
// Only create indexes for frequently queried fieldsPerformance Optimization
1. Use Lean Queries (Faster)
// Normal query (returns Mongoose documents)
const users = await User.find(); // Slow, includes Mongoose features
// Lean query (returns plain JavaScript objects)
const users = await User.find().lean(); // 5-10x faster!
// Use lean() when you don't need:
// - Virtual properties
// - Getters/setters
// - save() method
// - Mongoose features2. Select Only Needed Fields
// ❌ Bad: Fetches all fields
const users = await User.find();
// ✅ Good: Only fetch needed fields
const users = await User.find().select('name email');3. Limit Results
// ❌ Bad: Fetches all documents
const users = await User.find();
// ✅ Good: Limit results
const users = await User.find().limit(100);4. Use Indexes
// ❌ Bad: No index on email (COLLSCAN)
const user = await User.findOne({ email: 'john@example.com' });
// ✅ Good: Create index
userSchema.index({ email: 1 });
// Now uses IXSCAN (much faster!)5. Avoid N+1 Queries
// ❌ Bad: N+1 queries
const posts = await Post.find();
for (const post of posts) {
post.author = await User.findById(post.author); // N queries!
}
// ✅ Good: Use populate (1 query)
const posts = await Post.find().populate('author');6. Batch Operations
// ❌ Bad: Multiple queries
for (const userData of users) {
await User.create(userData);
}
// ✅ Good: Single batch operation
await User.insertMany(users);7. Connection Pooling
mongoose.connect(uri, {
maxPoolSize: 10, // Max concurrent connections
minPoolSize: 5 // Min connections maintained
});8. Query Optimization
// Use explain() to analyze queries
const explain = await User.find({ age: 25 }).explain('executionStats');
console.log('Execution time:', explain.executionStats.executionTimeMillis);
console.log('Documents examined:', explain.executionStats.totalDocsExamined);
console.log('Documents returned:', explain.executionStats.nReturned);
// Ideal: totalDocsExamined === nReturned (perfect index usage)২. PostgreSQL/MySQL & Sequelize (Relational)
Sequelize = Powerful ORM for SQL databases (PostgreSQL, MySQL, SQLite, MSSQL)
Why Use Sequelize?
Without ORM (Raw SQL):
- Write SQL queries manually
- Manual data mapping
- Database-specific syntax
- SQL injection risks
- Complex migrations
With Sequelize:
✅ JavaScript-based queries
✅ Automatic data mapping
✅ Database-agnostic code
✅ Built-in SQL injection protection
✅ Migration management
✅ Validation & hooks
✅ Associations (relationships)Installation & Setup
Install Sequelize
# Core package
npm install sequelize
# Database drivers (choose one)
npm install pg pg-hstore # PostgreSQL
npm install mysql2 # MySQL
npm install sqlite3 # SQLite
npm install tedious # Microsoft SQL ServerBasic Connection
const { Sequelize } = require('sequelize');
// PostgreSQL
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'postgres',
port: 5432
});
// MySQL
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql',
port: 3306
});
// SQLite (file-based)
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'database.sqlite'
});
// Test connection
try {
await sequelize.authenticate();
console.log('Database connected ✅');
} catch (error) {
console.error('Connection failed ❌', error);
}Connection with Options
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'postgres',
// Connection pool
pool: {
max: 10, // Maximum connections
min: 0, // Minimum connections
acquire: 30000, // Max time to get connection (ms)
idle: 10000 // Max idle time (ms)
},
// Logging
logging: console.log, // Log all SQL queries
// logging: false, // Disable logging
// Timezone
timezone: '+06:00',
// Define options for all models
define: {
timestamps: true, // createdAt, updatedAt
underscored: true, // Use snake_case (created_at)
freezeTableName: true // Don't pluralize table names
}
});Environment-based Connection
// .env
DATABASE_URL=postgres://user:pass@localhost:5432/dbname
// config/database.js
require('dotenv').config();
const sequelize = new Sequelize(process.env.DATABASE_URL, {
dialect: 'postgres',
logging: process.env.NODE_ENV === 'development'
});
module.exports = sequelize;Model Definition
Basic Model
const { DataTypes } = require('sequelize');
const User = sequelize.define('User', {
// Primary key (auto-created if not defined)
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
age: {
type: DataTypes.INTEGER
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'users', // Custom table name
timestamps: true // createdAt, updatedAt
});All Data Types
const Product = sequelize.define('Product', {
// String types
name: DataTypes.STRING, // VARCHAR(255)
description: DataTypes.TEXT, // TEXT
shortDesc: DataTypes.STRING(100), // VARCHAR(100)
// Number types
quantity: DataTypes.INTEGER, // INTEGER
price: DataTypes.DECIMAL(10, 2), // DECIMAL(10,2)
rating: DataTypes.FLOAT, // FLOAT
views: DataTypes.BIGINT, // BIGINT
// Boolean
isAvailable: DataTypes.BOOLEAN, // BOOLEAN
// Date types
releaseDate: DataTypes.DATE, // DATETIME
dateOnly: DataTypes.DATEONLY, // DATE (no time)
// JSON
metadata: DataTypes.JSON, // JSON
specs: DataTypes.JSONB, // JSONB (PostgreSQL only, faster)
// Enum
status: DataTypes.ENUM('active', 'inactive', 'pending'),
// UUID
uuid: DataTypes.UUID,
// Binary
image: DataTypes.BLOB,
// Array (PostgreSQL only)
tags: DataTypes.ARRAY(DataTypes.STRING)
});Validation
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: { msg: 'Name is required' },
notEmpty: { msg: 'Name cannot be empty' },
len: {
args: [3, 50],
msg: 'Name must be 3-50 characters'
}
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: { msg: 'Invalid email format' }
}
},
age: {
type: DataTypes.INTEGER,
validate: {
min: { args: 18, msg: 'Must be 18+' },
max: { args: 120, msg: 'Invalid age' },
isInt: { msg: 'Age must be integer' }
}
},
website: {
type: DataTypes.STRING,
validate: {
isUrl: { msg: 'Invalid URL' }
}
},
password: {
type: DataTypes.STRING,
validate: {
is: {
args: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
msg: 'Password too weak'
}
}
}
});Custom Validation
const User = sequelize.define('User', {
password: {
type: DataTypes.STRING,
validate: {
isStrongPassword(value) {
if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(value)) {
throw new Error('Password too weak');
}
}
}
},
email: {
type: DataTypes.STRING,
validate: {
async isUnique(value) {
const user = await User.findOne({ where: { email: value } });
if (user) {
throw new Error('Email already exists');
}
}
}
}
});CRUD Operations - Complete Guide
Create (C)
// Method 1: create()
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
age: 25
});
// Method 2: build() + save()
const user = User.build({
name: 'John Doe',
email: 'john@example.com'
});
await user.save();
// Bulk create
const users = await User.bulkCreate([
{ name: 'User 1', email: 'user1@example.com' },
{ name: 'User 2', email: 'user2@example.com' },
{ name: 'User 3', email: 'user3@example.com' }
], {
validate: true, // Run validations
ignoreDuplicates: true // Skip duplicates
});Read (R)
// Find all
const users = await User.findAll();
// Find with conditions
const users = await User.findAll({
where: {
isActive: true,
age: { [Op.gte]: 18 }
}
});
// Find one
const user = await User.findOne({
where: { email: 'john@example.com' }
});
// Find by primary key
const user = await User.findByPk(1);
// Find or create
const [user, created] = await User.findOrCreate({
where: { email: 'john@example.com' },
defaults: { name: 'John', age: 25 }
});
// Count
const count = await User.count({
where: { isActive: true }
});
// Exists
const exists = await User.findOne({
where: { email: 'john@example.com' }
}) !== null;Select Specific Fields:
// Include only
const users = await User.findAll({
attributes: ['id', 'name', 'email']
});
// Exclude fields
const users = await User.findAll({
attributes: { exclude: ['password', 'createdAt'] }
});
// With alias
const users = await User.findAll({
attributes: [
'id',
'name',
['email', 'emailAddress'] // Alias
]
});
// With aggregation
const users = await User.findAll({
attributes: [
'role',
[sequelize.fn('COUNT', sequelize.col('id')), 'userCount']
],
group: ['role']
});Where Operators:
const { Op } = require('sequelize');
// Comparison
const users = await User.findAll({
where: {
age: { [Op.gt]: 18 }, // >
age: { [Op.gte]: 18 }, // >=
age: { [Op.lt]: 60 }, // <
age: { [Op.lte]: 60 }, // <=
age: { [Op.ne]: 25 }, // !=
age: { [Op.between]: [18, 60] },
age: { [Op.notBetween]: [0, 18] }
}
});
// In/Not In
const users = await User.findAll({
where: {
role: { [Op.in]: ['admin', 'moderator'] },
status: { [Op.notIn]: ['banned', 'suspended'] }
}
});
// String operations
const users = await User.findAll({
where: {
name: { [Op.like]: '%John%' }, // LIKE
name: { [Op.notLike]: '%Admin%' },
name: { [Op.iLike]: '%john%' }, // Case-insensitive (PostgreSQL)
name: { [Op.startsWith]: 'John' },
name: { [Op.endsWith]: 'Doe' },
name: { [Op.substring]: 'oh' }
}
});
// Logical operators
const users = await User.findAll({
where: {
[Op.and]: [
{ age: { [Op.gte]: 18 } },
{ isActive: true }
]
}
});
const users = await User.findAll({
where: {
[Op.or]: [
{ role: 'admin' },
{ isPremium: true }
]
}
});
// NULL checks
const users = await User.findAll({
where: {
deletedAt: { [Op.is]: null },
phone: { [Op.not]: null }
}
});Ordering & Pagination:
// Order
const users = await User.findAll({
order: [
['age', 'ASC'],
['name', 'DESC']
]
});
// Pagination
const users = await User.findAll({
limit: 10,
offset: 20 // Skip 20
});
// Pagination helper
async function paginate(page = 1, limit = 10) {
const offset = (page - 1) * limit;
const { count, rows } = await User.findAndCountAll({
limit,
offset,
where: { isActive: true }
});
return {
users: rows,
totalUsers: count,
totalPages: Math.ceil(count / limit),
currentPage: page
};
}Update (U)
// Update instance
const user = await User.findByPk(1);
user.name = 'John Updated';
user.age = 26;
await user.save();
// Update with findOne + save
const user = await User.findOne({ where: { email: 'john@example.com' } });
if (user) {
await user.update({ name: 'John Updated', age: 26 });
}
// Direct update (returns affected count)
const [affectedCount] = await User.update(
{ isActive: false },
{ where: { id: 1 } }
);
// Update multiple
await User.update(
{ status: 'inactive' },
{ where: { lastLogin: { [Op.lt]: new Date('2025-01-01') } } }
);
// Increment/Decrement
await User.increment('views', { by: 1, where: { id: 1 } });
await User.decrement('stock', { by: 5, where: { id: productId } });Delete (D)
// Delete instance
const user = await User.findByPk(1);
await user.destroy();
// Direct delete (returns affected count)
const deletedCount = await User.destroy({
where: { id: 1 }
});
// Delete multiple
await User.destroy({
where: { isActive: false }
});
// Soft delete (if paranoid: true)
const User = sequelize.define('User', { ... }, {
paranoid: true // Adds deletedAt column
});
await user.destroy(); // Sets deletedAt
await user.restore(); // Unsets deletedAt
// Permanent delete (even with paranoid)
await user.destroy({ force: true });Associations (Relationships)
One-to-One
// User has one Profile
User.hasOne(Profile, { foreignKey: 'userId' });
Profile.belongsTo(User, { foreignKey: 'userId' });
// Create
const user = await User.create({ name: 'John' });
const profile = await Profile.create({
bio: 'Developer',
userId: user.id
});
// Query with association
const user = await User.findByPk(1, {
include: Profile
});
console.log(user.Profile);One-to-Many
// User has many Posts
User.hasMany(Post, { foreignKey: 'authorId' });
Post.belongsTo(User, { foreignKey: 'authorId' });
// Create
const user = await User.create({ name: 'John' });
const post = await Post.create({
title: 'My Post',
authorId: user.id
});
// Or use association methods
const post = await user.createPost({ title: 'My Post' });
// Query
const user = await User.findByPk(1, {
include: Post
});
console.log(user.Posts); // Array of postsMany-to-Many
// Student enrolls in many Courses
Student.belongsToMany(Course, { through: 'Enrollments' });
Course.belongsToMany(Student, { through: 'Enrollments' });
// Create
const student = await Student.create({ name: 'John' });
const course = await Course.create({ title: 'Node.js' });
await student.addCourse(course);
// Query
const student = await Student.findByPk(1, {
include: Course
});
console.log(student.Courses);Nested Includes
const user = await User.findByPk(1, {
include: [{
model: Post,
include: [{
model: Comment,
include: [User]
}]
}]
});
// User → Posts → Comments → UserTransactions (Sequelize)
// Managed transaction (automatic commit/rollback)
try {
const result = await sequelize.transaction(async (t) => {
// All queries must use { transaction: t }
// Deduct from sender
await Account.update(
{ balance: sequelize.literal('balance - ' + amount) },
{ where: { id: senderId }, transaction: t }
);
// Add to receiver
await Account.update(
{ balance: sequelize.literal('balance + ' + amount) },
{ where: { id: receiverId }, transaction: t }
);
// Log transaction
await Transaction.create({
from: senderId,
to: receiverId,
amount
}, { transaction: t });
return { success: true };
});
console.log('Transaction successful');
} catch (error) {
console.error('Transaction failed (rolled back)', error);
}
// Manual transaction
const t = await sequelize.transaction();
try {
await User.update({ balance: 100 }, { where: { id: 1 }, transaction: t });
await User.update({ balance: 200 }, { where: { id: 2 }, transaction: t });
await t.commit();
} catch (error) {
await t.rollback();
}Migrations - Database Version Control
Setup Sequelize CLI
npm install --save-dev sequelize-cli
# Initialize
npx sequelize-cli init
# Creates:
# - config/config.json (database config)
# - models/ (models directory)
# - migrations/ (migration files)
# - seeders/ (seed files)Create Migration
# Create migration file
npx sequelize-cli migration:generate --name create-users-tableMigration File
// migrations/20260117000000-create-users-table.js
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING,
allowNull: false
},
email: {
type: Sequelize.STRING,
unique: true,
allowNull: false
},
age: {
type: Sequelize.INTEGER
},
createdAt: {
type: Sequelize.DATE,
allowNull: false
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false
}
});
// Add index
await queryInterface.addIndex('users', ['email']);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('users');
}
};Run Migrations
# Run all pending migrations
npx sequelize-cli db:migrate
# Undo last migration
npx sequelize-cli db:migrate:undo
# Undo all migrations
npx sequelize-cli db:migrate:undo:allAdd Column Migration
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('users', 'phone', {
type: Sequelize.STRING
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('users', 'phone');
}
};Seeders - Sample Data
# Create seeder
npx sequelize-cli seed:generate --name demo-users// seeders/20260117000000-demo-users.js
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.bulkInsert('users', [
{
name: 'John Doe',
email: 'john@example.com',
age: 25,
createdAt: new Date(),
updatedAt: new Date()
},
{
name: 'Jane Smith',
email: 'jane@example.com',
age: 30,
createdAt: new Date(),
updatedAt: new Date()
}
]);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.bulkDelete('users', null, {});
}
};# Run seeders
npx sequelize-cli db:seed:all
# Undo seeders
npx sequelize-cli db:seed:undo:allRaw SQL Queries
Sequelize Raw Queries
// Simple query
const [results, metadata] = await sequelize.query(
'SELECT * FROM users WHERE age > :age',
{
replacements: { age: 18 },
type: QueryTypes.SELECT
}
);
// Insert
await sequelize.query(
'INSERT INTO users (name, email) VALUES (:name, :email)',
{
replacements: { name: 'John', email: 'john@example.com' }
}
);
// Using literal in Sequelize query
const users = await User.findAll({
where: sequelize.literal('age > 18 AND isActive = true')
});Native Database Drivers
PostgreSQL (pg):
const { Pool } = require('pg');
const pool = new Pool({
host: 'localhost',
user: 'user',
password: 'pass',
database: 'mydb',
port: 5432,
max: 20 // Connection pool size
});
// Query
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
console.log(result.rows);
// Transaction
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [100, senderId]);
await client.query('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [100, receiverId]);
await client.query('COMMIT');
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}MySQL (mysql2):
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'localhost',
user: 'user',
password: 'pass',
database: 'mydb',
waitForConnections: true,
connectionLimit: 10
});
// Query
const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [userId]);
// Prepared statement
const [rows] = await pool.execute(
'SELECT * FROM users WHERE age > ? AND role = ?',
[18, 'user']
);৩. Other Database Solutions
Knex.js - Query Builder
npm install knex pg # or mysql, sqlite3const knex = require('knex')({
client: 'pg',
connection: {
host: 'localhost',
user: 'user',
password: 'pass',
database: 'mydb'
}
});
// Query
const users = await knex('users')
.where('age', '>', 18)
.andWhere('isActive', true)
.select('id', 'name', 'email')
.orderBy('createdAt', 'desc')
.limit(10);
// Insert
const [id] = await knex('users').insert({
name: 'John',
email: 'john@example.com'
});
// Update
await knex('users')
.where({ id: 1 })
.update({ name: 'John Updated' });
// Delete
await knex('users').where({ id: 1 }).del();
// Join
const posts = await knex('posts')
.join('users', 'posts.author_id', 'users.id')
.select('posts.*', 'users.name as authorName');
// Transaction
await knex.transaction(async trx => {
await trx('accounts').where({ id: senderId }).decrement('balance', amount);
await trx('accounts').where({ id: receiverId }).increment('balance', amount);
});Prisma - Modern ORM
npm install prisma @prisma/client
npx prisma initschema.prisma:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}# Generate migrations
npx prisma migrate dev --name init
# Generate Prisma Client
npx prisma generateUsage:
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
// Create
const user = await prisma.user.create({
data: {
name: 'John',
email: 'john@example.com'
}
});
// Read
const users = await prisma.user.findMany({
where: {
age: { gte: 18 },
isActive: true
},
include: { posts: true },
orderBy: { createdAt: 'desc' },
take: 10
});
// Update
await prisma.user.update({
where: { id: 1 },
data: { name: 'John Updated' }
});
// Delete
await prisma.user.delete({ where: { id: 1 } });
// Transaction
await prisma.$transaction([
prisma.user.create({ data: { name: 'John', email: 'john@example.com' } }),
prisma.post.create({ data: { title: 'My Post', authorId: 1 } })
]);Database Best Practices
1. Connection Management
// ✅ Good: Use connection pooling
mongoose.connect(uri, {
maxPoolSize: 10,
minPoolSize: 5
});
// ✅ Good: Reuse connections
// Create one connection and export it
// Don't create new connections for each request
// ✅ Good: Handle connection errors
mongoose.connection.on('error', (err) => {
console.error('Database error:', err);
});
// ✅ Good: Graceful shutdown
process.on('SIGTERM', async () => {
await mongoose.connection.close();
process.exit(0);
});2. Schema Design
// ✅ Good: Use appropriate data types
{
price: { type: Number }, // Not String!
isActive: { type: Boolean }, // Not 0/1
createdAt: { type: Date } // Not String!
}
// ✅ Good: Add indexes for frequently queried fields
userSchema.index({ email: 1 });
userSchema.index({ age: 1, role: 1 });
// ✅ Good: Use default values
{
status: { type: String, default: 'active' },
createdAt: { type: Date, default: Date.now }
}
// ✅ Good: Add validation
{
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
validate: {
validator: (v) => /^\S+@\S+\.\S+$/.test(v),
message: 'Invalid email'
}
}
}3. Query Optimization
// ❌ Bad: N+1 query problem
const users = await User.find();
for (const user of users) {
user.posts = await Post.find({ author: user._id });
}
// ✅ Good: Use populate/join
const users = await User.find().populate('posts');
// ❌ Bad: Select all fields
const users = await User.find();
// ✅ Good: Select only needed fields
const users = await User.find().select('name email');
// ❌ Bad: No limit
const users = await User.find();
// ✅ Good: Always limit
const users = await User.find().limit(100);
// ✅ Good: Use lean() when you don't need Mongoose features
const users = await User.find().lean(); // 5-10x faster!4. Error Handling
// ✅ Good: Always handle errors
try {
const user = await User.create(userData);
} catch (error) {
if (error.name === 'ValidationError') {
// Handle validation error
} else if (error.code === 11000) {
// Handle duplicate key error
} else {
// Handle other errors
}
}
// ✅ Good: Use transactions for critical operations
const session = await mongoose.startSession();
try {
await session.withTransaction(async () => {
// Your operations
});
} catch (error) {
// Handle error
} finally {
session.endSession();
}5. Security
// ✅ Good: Never store plain passwords
userSchema.pre('save', async function() {
if (this.isModified('password')) {
this.password = await bcrypt.hash(this.password, 10);
}
});
// ✅ Good: Use parameterized queries (prevent SQL injection)
await User.findOne({ email: userInput }); // Safe with Mongoose/Sequelize
// ❌ Bad: String concatenation (SQL injection!)
await sequelize.query(`SELECT * FROM users WHERE email = '${userInput}'`);
// ✅ Good: Use replacements
await sequelize.query('SELECT * FROM users WHERE email = :email', {
replacements: { email: userInput }
});
// ✅ Good: Don't expose sensitive fields
userSchema.methods.toJSON = function() {
const obj = this.toObject();
delete obj.password;
return obj;
};6. Performance
// ✅ Good: Create indexes for frequently queried fields
userSchema.index({ email: 1, isActive: 1 });
// ✅ Good: Use projection to limit fields
const users = await User.find().select('name email');
// ✅ Good: Use pagination
const users = await User.find().skip(skip).limit(limit);
// ✅ Good: Use aggregation for complex queries
const stats = await User.aggregate([
{ $group: { _id: '$role', count: { $sum: 1 } } }
]);
// ✅ Good: Cache frequently accessed data
const redis = require('redis');
const client = redis.createClient();
async function getUser(id) {
// Check cache first
const cached = await client.get(`user:${id}`);
if (cached) return JSON.parse(cached);
// If not cached, get from DB
const user = await User.findById(id);
// Cache for 1 hour
await client.setEx(`user:${id}`, 3600, JSON.stringify(user));
return user;
}Summary
MongoDB (Mongoose):
- ✅ Flexible schema
- ✅ Fast for simple queries
- ✅ Easy to scale horizontally
- ✅ Good for unstructured data
- ❌ No ACID transactions (before 4.0)
- ❌ No built-in relationships
SQL (Sequelize):
- ✅ ACID transactions
- ✅ Strong relationships
- ✅ Complex queries (JOINs)
- ✅ Data integrity
- ❌ Fixed schema
- ❌ Harder to scale horizontally
Choose MongoDB when:
- Rapid development
- Flexible/changing schema
- Document-based data
- Horizontal scalability needed
Choose SQL when:
- Complex relationships
- ACID compliance critical
- Structured data
- Complex queries needed
এই documentation দিয়ে আপনি যেকোনো Node.js প্রজেক্টে database integration করতে পারবেন! 🚀