DEV Community

Cover image for How to Improve API Performance: Tips, Techniques & Best Practices
Mrinal Maheshwari
Mrinal Maheshwari

Posted on • Originally published at blog.mrinalmaheshwari.com

How to Improve API Performance: Tips, Techniques & Best Practices

“APIs have been a real pain point for me. Even after delivering the product and pushing it to production, the APIs didn’t perform as expected. What should ideally take less than a second often ends up taking 4–5 seconds — and that’s frustrating, both as a developer and a user.”

I’ve been in that situation more than once — staring at network logs, wondering why a seemingly simple endpoint is taking ages to respond. After a lot of debugging, Googling, and diving into books on algorithms and software architecture, I started identifying patterns. Eventually, I collated a list of practical techniques that genuinely helped improve API performance.

In this article, I’m sharing those learnings — 15 real-world strategies that have worked for me across different projects. Whether you’re trying to fix a slow endpoint or just build more scalable APIs from day one, I hope these insights help you save time and headaches.

1. Optimize Database Access

Why it matters: Your API is only as fast as the database queries behind it.

How to implement:

  • Use indexes on frequently queried columns.

  • Avoid N+1 query problems (where fetching related records causes repeated database calls).

  • In Sequelize: use .include to load relationships.

  • In Prisma: use include to fetch relations in a single query.

  • Batch requests using IN clauses or union queries when possible.

Real life: If you’re querying user orders, fetch them with a single join rather than looping through user IDs and hitting the DB repeatedly.

2. Use Efficient Data Formats

Why it matters: Data serialization and size directly affect response time.

Alternatives to JSON:

  • Protocol Buffers (Protobuf): Small, fast, and language-neutral (commonly used with gRPC).

  • MessagePack: Binary format, compatible with many languages and frameworks.

When to use:

  • Use JSON for public or third-party APIs.

  • Use Protobuf or MessagePack in internal microservices for performance gains.

3. Reduce Payload Size

Why it matters: Every extra byte adds to the transfer time and processing load.

How to implement:

  • Only send required fields using field selection or select.

  • Avoid deeply nested JSON if not necessary.

  • Trim strings (no need to send 500-character bios in a list view).

  • Don’t return large blobs (e.g., images, files) directly — use URLs or S3 links.

4. Enable Compression

Why it matters: You can reduce response size by up to 90%.

How to implement:

  • In Express.js, use the compression middleware:
const compression = require('compression');
app.use(compression());
Enter fullscreen mode Exit fullscreen mode
  • In NGINX:
gzip on;
gzip_types text/plain application/json;
Enter fullscreen mode Exit fullscreen mode

Tip: Use Brotli for better compression than GZIP if your infra supports it.

5. Caching Strategies

Why it matters: Why calculate or fetch the same data again and again?

Caching layers:

  • Browser/Client: Use Cache-Control, ETag, and Last-Modified headers.

  • CDN: Use Cloudflare, Fastly, or AWS CloudFront to cache static responses.

  • Application: Cache DB queries or computed data using Redis.

  • Database: Materialized views or result cache.

6. Use Pagination & Filtering

Why it matters: Large data dumps kill bandwidth and frontend performance.

How to implement:

  • Add limit, offset, or cursor-based pagination.

  • Filter data server-side using query parameters (?status=active&type=premium).

  • Use fields query to return only requested fields.

Example (REST):

GET /users?limit=10&offset=20
Enter fullscreen mode Exit fullscreen mode

Example (GraphQL):

query {
  users(first: 10, after: "cursor") {
    name
    email
  }
}
Enter fullscreen mode Exit fullscreen mode

7. Asynchronous Processing

Why it matters: Long-running operations (e.g., image processing, email sending) should not block the response.

What to do:

  • Offload tasks to a queue: RabbitMQ, Redis, Kafka, or SQS.

  • Respond immediately with 202 Accepted.

  • Use webhooks, push notifications, or polling to notify clients when processing is complete.

Real world example:

// Enqueue job for PDF generation
await queue.add('generate-pdf', { userId: 123 });
res.status(202).json({ message: "Processing started" });
Enter fullscreen mode Exit fullscreen mode

8. Connection Management

Why it matters: Poor connection handling leads to leaks, bottlenecks, and eventual crashes.

Tips:

  • Use keep-alive to avoid reconnecting repeatedly.

  • Enable connection pooling with DBs.

  • Set timeouts for DB, HTTP, and external APIs.

Example (PostgreSQL + pg-pool):

const pool = new Pool({
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});
Enter fullscreen mode Exit fullscreen mode

9. Load Balancing

Why it matters: Distributes traffic, avoids server overload, improves uptime.

What you can use:

  • NGINX or HAProxy for simple round-robin or least-connections strategies.

  • Cloud providers (AWS ELB, GCP Load Balancer) for auto-scaling and health checks.

  • Use sticky sessions only if absolutely necessary.

10. Rate Limiting & Throttling

Why it matters: Protects your API from abuse and ensures fair usage.

How to do it:

  • Limit by IP, user ID, or API key.

  • Use sliding window or token bucket algorithms.

  • Return 429 Too Many Requests with a retry-after header.

Example (Express):

const rateLimit = require("express-rate-limit");
app.use(rateLimit({ windowMs: 60000, max: 100 }));
Enter fullscreen mode Exit fullscreen mode

11. HTTP/2 or gRPC

Why it matters: More efficient protocol = faster response times.

Benefits of HTTP/2:

  • Multiplexing multiple requests over one connection.

  • Header compression.

  • Faster loading for clients making many requests.

gRPC advantages:

  • Uses Protobuf, extremely fast and compact.

  • Ideal for internal APIs, microservices.

  • Supports client-side codegen.

Setup: Use Envoy or NGINX as HTTP/2 proxy, or gRPC with Node, Go, Java.

12. Use CDN for Static Assets

Why it matters: Don’t make your server serve files — push them to the edge.

What to serve via CDN:

  • Images

  • PDF files

  • JS/CSS bundles

  • Videos

Tools: Cloudflare, AWS CloudFront, Vercel Edge, Netlify

Bonus tip: Use signed URLs for protected file downloads.

13. Optimize Server Code

Why it matters: Even fast APIs break if backend logic is inefficient.

Tips:

  • Avoid blocking operations (e.g., fs.readFileSync, long loops).

  • Use asynchronous patterns (async/await, Promise.all).

  • Profile your app using Node.js built-in profiler or tools like clinic.js.

14. Monitoring & Profiling

Why it matters: You can’t fix what you don’t measure.

Track:

  • Latency (average, P95, P99)

  • Error rates

  • Slow endpoints

  • Memory usage

Tools:

  • APM: Datadog, New Relic, Dynatrace

  • Logging: Winston, Pino, ELK Stack

  • Tracing: OpenTelemetry + Jaeger

15. Secure Without Bottlenecks

Why it matters: Don’t compromise speed for security, or vice versa.

How:

  • Validate JWTs efficiently using in-memory public keys.

  • Use lightweight middlewares for token validation.

  • Don’t do heavy DB lookups before authentication.

Final Thoughts

Improving API performance is a continuous process. Start by measuring, then fix the biggest pain points first. Whether you’re working with REST, GraphQL, or gRPC — these techniques are universal.

By applying even a handful of these best practices, you’ll create faster, more scalable, and user-friendly APIs.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.