🟢
Node.js
Event loop, async patterns, NestJS, performance optimization, and best practices.
Event Loop
The Event Loop is the core of Node.js async behavior. Understanding its phases is crucial:
1. **Timers** - Execute setTimeout/setInterval callbacks
2. **Pending callbacks** - Execute I/O callbacks deferred from previous loop
3. **Idle/Prepare** - Internal use only
4. **Poll** - Retrieve new I/O events, execute I/O related callbacks
5. **Check** - Execute setImmediate callbacks
6. **Close callbacks** - Execute close event callbacks
**Key Points:**
- process.nextTick() runs before any phase
- Promises (microtasks) run after each phase
- setImmediate vs setTimeout(fn, 0): setImmediate runs in check phase, setTimeout in timers phase
// Event loop example
console.log('1. Start');
setTimeout(() => console.log('2. setTimeout'), 0);
setImmediate(() => console.log('3. setImmediate'));
Promise.resolve().then(() => console.log('4. Promise'));
process.nextTick(() => console.log('5. nextTick'));
console.log('6. End');
// Output: 1, 6, 5, 4, 2, 3 (or 3, 2 may swap)Async Patterns
**Callbacks → Promises → Async/Await**
Modern Node.js uses async/await built on Promises. Key patterns:
- **Error handling**: Always wrap async code in try/catch
- **Parallel execution**: Use Promise.all() for concurrent operations
- **Sequential execution**: Use for...of with await
- **Rate limiting**: Use p-limit or custom semaphore
// Parallel vs Sequential
// Parallel - faster when operations are independent
const results = await Promise.all([
fetchUser(1),
fetchUser(2),
fetchUser(3),
]);
// Sequential - when order matters
for (const id of [1, 2, 3]) {
await processInOrder(id);
}
// With concurrency limit
import pLimit from 'p-limit';
const limit = pLimit(5); // Max 5 concurrent
const tasks = ids.map(id =>
limit(() => processItem(id))
);
await Promise.all(tasks);NestJS Essentials
**NestJS** is a progressive Node.js framework using TypeScript and decorators.
**Core Concepts:**
- **Modules** - Organize code into cohesive blocks
- **Controllers** - Handle incoming requests
- **Providers/Services** - Business logic, injectable via DI
- **Middleware** - Execute before route handlers
- **Guards** - Authorization logic
- **Interceptors** - Transform/extend behavior
- **Pipes** - Data transformation and validation
// NestJS Service with BullMQ
@Injectable()
export class OrderService {
constructor(
@InjectQueue('orders') private orderQueue: Queue,
private readonly prisma: PrismaService,
) {}
async createOrder(dto: CreateOrderDto) {
const order = await this.prisma.order.create({
data: dto,
});
// Add to queue for async processing
await this.orderQueue.add('process', {
orderId: order.id,
}, {
attempts: 3,
backoff: { type: 'exponential', delay: 1000 },
});
return order;
}
}Performance Tips
**Optimization Strategies:**
1. **Use Streams** for large data - avoid loading everything into memory
2. **Connection pooling** - Reuse database connections
3. **Caching** - Redis for frequently accessed data
4. **Worker Threads** - CPU-intensive tasks off main thread
5. **Cluster mode** - Utilize all CPU cores
6. **Avoid sync operations** - Never block the event loop
// Stream processing example
import { pipeline } from 'stream/promises';
import { createReadStream, createWriteStream } from 'fs';
import { Transform } from 'stream';
const transform = new Transform({
transform(chunk, encoding, callback) {
// Process chunk
this.push(chunk.toString().toUpperCase());
callback();
}
});
await pipeline(
createReadStream('input.txt'),
transform,
createWriteStream('output.txt')
);
// Worker threads for CPU tasks
import { Worker } from 'worker_threads';
function runWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker('./heavy-task.js', {
workerData: data,
});
worker.on('message', resolve);
worker.on('error', reject);
});
}📝 More Node.js content coming soon - Streams, Error handling, Testing strategies...