DEV Community

Cover image for 10X Your NodeJS: Performance Tricks for 100ms Responses
Fahim Hasnain Fahad
Fahim Hasnain Fahad

Posted on

10X Your NodeJS: Performance Tricks for 100ms Responses

πŸš€ The Wake-Up Call

It was 2 AM when my phone rang. The CEO's voice was tense. "The app is crawling. Users are complaining. Fix it."

I still remember that night vividly. Our NodeJS app that handled customer orders was buckling under traffic. Response times had gone from snappy to sluggishβ€”2-3 seconds per request. 😱

Sound familiar? If you're a junior developer working with NodeJS, you might have faced (or will face) this scenario. Let me share how I transformed our application from a tortoise to a cheetah! ⚑

πŸ•΅οΈ Diagnosing the Problem

The next morning, bleary-eyed but determined, I started investigating. Our stack looked pretty standard:

Technology Purpose
NodeJS/Express API server
MongoDB Database
Redis Caching
AWS EC2 Hosting

The app was structured like most Express applications, with routes handling requests and controllers processing business logic. But something was clearly wrong.

πŸ” The Bottlenecks

I discovered three main issues that were throttling our performance:

  1. Database queries were inefficient 🐒
  2. No caching strategy πŸ’Ύ
  3. Blocking operations in the event loop ⛓️

πŸ› οΈ The Transformation Journey

Step 1: Optimizing MongoDB Queries ⚑

I found queries like this throughout our codebase:

const users = await User.find({});
Enter fullscreen mode Exit fullscreen mode

This was loading ALL users into memory before filtering on the application side!

I replaced them with properly indexed, filtered queries:

const users = await User.find({ active: true }).select('name email').limit(10);
Enter fullscreen mode Exit fullscreen mode

Quick Win πŸ†: Adding appropriate indexes reduced query times by 80%!

Query Type Before After Improvement
User Search 1200ms 95ms 92% faster
Product Listing 1800ms 120ms 93% faster
Order History 2100ms 150ms 93% faster

Step 2: Implementing Redis Caching πŸš€

For frequently accessed data that rarely changes (like product categories), I implemented Redis:

const data = await redisClient.get('categories') || await fetchAndCacheCategories();
Enter fullscreen mode Exit fullscreen mode

This simple pattern eliminated repetitive database queries for common data. Think of Redis as your refrigerator - why go grocery shopping (query the database) every time you want a snack? 🍎

Step 3: Non-Blocking Operations πŸ”„

I discovered we were reading files synchronously in some routes:

// Before: Blocking the event loop πŸ˜–
const template = fs.readFileSync('./templates/email.html');

// After: Non-blocking 😊
const template = await fs.promises.readFile('./templates/email.html');
Enter fullscreen mode Exit fullscreen mode

Remember: NodeJS is like a single-lane highway. One slow truck (blocking operation) causes traffic for everyone!

🎯 The Memory Leak Mystery

Our server would occasionally crash after running for a few days. Using node --inspect and Chrome DevTools, I discovered we were accumulating references to large objects.

The culprit? Event listeners that weren't being properly removed:

// Fixed by adding proper cleanup
emitter.removeListener('data', handleData);
Enter fullscreen mode Exit fullscreen mode

⚑ Pro Tip: Async/Await Patterns

One pattern that significantly improved our code readability and performance:

// Use Promise.all for parallel operations
const [users, products, categories] = await Promise.all([
  fetchUsers(),
  fetchProducts(),
  fetchCategories()
]);
Enter fullscreen mode Exit fullscreen mode

This runs all three operations concurrently rather than sequentially, cutting response time by ~66%!

πŸ“Š The Results

Metric Before After
Avg Response Time 2300ms 95ms
Server Memory 1.2GB 450MB
Crash Frequency Every 2-3 days None in 3 months

🎯 Key Takeaways

  • Index everything that you query frequently in MongoDB πŸ”
  • Cache aggressively with Redis for data that changes infrequently πŸ’Ύ
  • Avoid blocking the event loop - use async operations wherever possible πŸ”„
  • Monitor memory usage to catch leaks before they become problems πŸ“ˆ
  • Use Promise.all for concurrent operations ⚑
  • Profile regularly - what's fast today might be slow tomorrow πŸ•’

The CEO called me a week later: "Whatever you did, it worked. The app feels lightning fast."

Sometimes the biggest performance gains come from the simplest changes. Happy optimizing! πŸš€

Top comments (0)