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)