You're reading for free via habtesoft's Friend Link. Become a member to access the best of Medium.
Member-only story

Node.js has become one of the most popular environments for backend development, thanks to its speed, efficiency, and flexibility. However, moving beyond beginner-level Node.js requires mastering more advanced techniques and adopting best practices that can make your applications faster, more maintainable, and scalable. Here are ten essential tips to level up your Node.js skills.
1. Use async/await
with Caution in Loops
While async/await
can simplify asynchronous code, using it in loops can slow down your application. When you use await
inside a loop, each iteration waits for the previous one to complete, making it synchronous. Instead, use Promise.all()
to run multiple async operations in parallel.
Example:
// Slower: awaits each iteration
for (let userId of userIds) {
await getUserDetails(userId);
}
// Faster: executes all promises in parallel
await Promise.all(userIds.map(id => getUserDetails(id)));
2. Stream Data for Better Performance
Node.js supports streams, which allow you to handle data in chunks rather than loading everything into memory at once. This is particularly useful for handling large files or network requests and improves performance by reducing memory usage.
Example:
const fs = require('fs');
const readStream = fs.createReadStream('largeFile.txt');
readStream.on('data', chunk => {
console.log('New chunk received:', chunk);
});
3. Use Environment Variables Securely
Environment variables are crucial for configuration in Node.js applications. Use libraries like dotenv
to load them from a .env
file and always exclude sensitive values like API keys or database credentials from your codebase. Never hardcode sensitive information.
Example:
// Load environment variables from a .env file
require('dotenv').config();
const dbPassword = process.env.DB_PASSWORD;
4. Avoid Memory Leaks with Proper Garbage Collection
Memory leaks can creep into Node.js applications if you’re not careful. To minimize leaks, avoid holding onto unused variables and always remove listeners with removeListener
or off
when they're no longer needed. Profiling tools like Chrome DevTools and node --inspect
are great for finding and fixing memory leaks.
Example:
const EventEmitter = require('events');
const emitter = new EventEmitter();
function handler() {
console.log('Event handled');
}
// Add listener
emitter.on('myEvent', handler);
// Remove listener to prevent memory leak
emitter.removeListener('myEvent', handler);
5. Optimize Database Queries
Database queries can often be the bottleneck in an application. Use indexes, avoid SELECT *
statements, and optimize your queries. For MongoDB, use indexes effectively and leverage aggregation pipelines where possible. Libraries like Sequelize
and Mongoose
offer built-in ways to optimize queries.
Example:
// Inefficient query
const users = await User.find({ age: { $gte: 21 } });
// With an index on the age field, this query will be much faster
6. Enable GZIP Compression
GZIP compression can significantly reduce the size of responses sent to clients, improving loading times and bandwidth usage. Use the compression
middleware in your Express app to enable GZIP compression.
Example:
const express = require('express');
const compression = require('compression');
const app = express();
app.use(compression()); // Enable compression for all routes
7. Use Cluster Mode to Utilize All CPU Cores
Node.js runs on a single thread by default, but you can take advantage of all CPU cores using clustering. The cluster
module creates child processes to handle requests in parallel, which improves performance in high-traffic applications.
Example:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {
cluster.fork(); // Fork workers
}
} else {
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello World\n');
}).listen(8000);
}
8. Monitor and Handle Uncaught Exceptions
Errors are inevitable, but unhandled exceptions can crash your application. Use error-handling middleware and listen for uncaughtException
and unhandledRejection
events to log errors and prevent your app from crashing.
Example:
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
process.exit(1); // Exit process after logging
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection:', reason);
// Handle or log the rejection here
});
9. Use Caching for Frequent Operations
For data that doesn’t change often (e.g., user sessions or frequently accessed data), caching can reduce database calls and improve performance. Libraries like node-cache
or Redis
can be used to implement caching in Node.js.
Example:
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 100, checkperiod: 120 });
// Check cache first
let data = cache.get("myData");
if (!data) {
// If not in cache, fetch from database and set cache
data = await getDataFromDB();
cache.set("myData", data);
}
10. Use pm2
for Process Management
pm2
is a powerful process manager for Node.js that can keep your application alive, restart it if it crashes, and enable load balancing across CPU cores. It’s also helpful for monitoring and automatically managing your app in production.
Installation and usage:
# Install pm2 globally
npm install -g pm2
# Start your app with pm2
pm2 start app.js
# View process logs and status
pm2 status
Moving beyond beginner-level Node.js development requires implementing practices that make your applications faster, safer, and more efficient. By using async/await
effectively, managing memory, optimizing database queries, and using tools like pm2
and caching mechanisms, you’ll be on your way to writing production-ready Node.js code. Master these tips, and you’ll be well-prepared to tackle the challenges of real-world Node.js development.