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)