“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());
- In NGINX:
gzip on;
gzip_types text/plain application/json;
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
Example (GraphQL):
query {
users(first: 10, after: "cursor") {
name
email
}
}
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" });
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,
});
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 }));
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.