DEV Community

Md Shahjalal
Md Shahjalal

Posted on

Mastering Debugging why APIs Slow & How to Solve

It was just another normal day at work. I pushed a new feature, merged my PR, and everything looked fine. Until…
the support team messaged:

β€œHey, users are complaining the dashboard is taking forever to load. Can you check?”

πŸ˜… Uh oh. My once-fast API was now sluggish.

That’s when I started my debugging journey.


πŸ”Ž Step 1: The First Clue β€” Measuring the Problem

The first rule of debugging: don’t guess β€” measure.

I added a simple middleware in my NestJS app to log response times:

// logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const start = Date.now();
    res.on('finish', () => {
      const duration = Date.now() - start;
      console.log(`${req.method} ${req.originalUrl} - ${duration}ms`);
    });
    next();
  }
}
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ Boom. The metrics showed some endpoints were taking 800ms+ just to respond. Too slow.


πŸ’» Step 2: Client-Side Payloads

The first bottleneck wasn’t even the backendβ€”it was sending too much data.
Our API was returning entire tables in one response.

Fix β†’ Pagination + Compression

// main.ts
import * as compression from 'compression';
app.use(compression()); // reduce payload size
Enter fullscreen mode Exit fullscreen mode
// users.service.ts
async getUsers(page = 1, limit = 20) {
  return this.userRepo.find({
    skip: (page - 1) * limit,
    take: limit,
  });
}
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ Just like that, the response shrank from 2MB β†’ 200KB. Faster load times already.


πŸ—„ Step 3: The Real Culprit β€” Database

Next, I checked Postgres queries. Running EXPLAIN ANALYZE revealed the users lookup by email was scanning the whole table.

Fix β†’ Add an Index

CREATE INDEX idx_users_email ON users(email);
Enter fullscreen mode Exit fullscreen mode

And update the NestJS query:

async getUserByEmail(email: string) {
  return this.userRepo.findOne({ where: { email } });
}
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ Query time dropped from 500ms β†’ 20ms. Huge win.


⚑ Step 4: Redis to the Rescue

Some queries were still slow because the same data was being fetched repeatedly.
Solution? Cache it in Redis.

// users.service.ts
async getUserCached(id: number) {
  const key = `user:${id}`;
  let user = await this.cache.get(key);

  if (user) return { source: 'cache', user };

  user = await this.userRepo.findOne({ where: { id } });
  if (user) await this.cache.set(key, user, 60); // cache for 1 min

  return { source: 'db', user };
}
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ Second request was now 5ms instead of hitting the DB again.


πŸ”— Step 5: Slow External APIs

One endpoint was calling a third-party API. Sometimes it just hung for 10+ seconds 😱.

Fix β†’ Add Timeout + Retry

// external.service.ts
import axios from 'axios';

async fetchData() {
  try {
    const response = await axios.get('https://api.example.com/data', {
      timeout: 3000, // 3s timeout
    });
    return response.data;
  } catch (err) {
    console.error('API failed:', err.message);
    return null; // graceful fallback
  }
}
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ No more stuck requests.


πŸ— Step 6: Heavy Workloads

We also had tasks like sending emails and generating reports. They were blocking the API.

Fix β†’ Offload to Background Jobs (BullMQ)

// producer.service.ts
import { Queue } from 'bullmq';
const queue = new Queue('emailQueue');

await queue.add('sendEmail', { to: 'user@test.com' });
Enter fullscreen mode Exit fullscreen mode
// worker.ts
import { Worker } from 'bullmq';
new Worker('emailQueue', async job => {
  console.log('Sending email:', job.data);
});
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ API responds instantly while jobs run in the background.


βš™οΈ Step 7: Scaling Up

Finally, with everything optimized, we scaled the service horizontally:

pm2 start dist/main.js -i max
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ Multiple processes β†’ better use of all CPU cores.


βœ… The Happy Ending

After all these fixes:

  • Endpoints went from 800ms β†’ under 100ms.
  • Database CPU load dropped by 70%.
  • Users stopped complaining πŸŽ‰.

✨ Lessons Learned

Debugging a slow API isn’t about magic. It’s about following a process:

  1. Measure β†’ find the bottleneck.
  2. Isolate β†’ confirm where the slowdown is.
  3. Fix β†’ apply the right optimization.

And most importantly: fix the biggest bottleneck first, then repeat.


πŸ‘‰ Next time your API slows down, don’t panic. Just follow the Measure β†’ Isolate β†’ Fix cycle, and maybe you’ll even enjoy the detective work πŸ˜‰.

Top comments (0)