Scaling Your API Part 5: Monitoring & Performance
π This is Part 5 of the βScaling Your APIβ Series
- Part 1: Performance & Infrastructure β - Technical techniques to handle millions of requests
- Part 2: Design & Architecture β - Organizational strategies and API design patterns
- Part 3: Choosing the Right Database β - Database selection for your API
- Part 4: Load Balancing & High Availability β - Keeping your API always available
- Part 5 (this page): Monitoring & Performance - Tracking and improving API performance
Why Monitor Your API?
Think of it like this: Monitoring your API is like having a dashboard in your car. You need to know your speed, fuel level, and engine temperature β before problems happen!
Without Monitoring β
User: "Your API is down!"
You: "What? Since when?"
User: "For the last 2 hours!"
You: *panic* π±
Problem: You didn't know there was an issue until users complained
With Monitoring β
11:00 AM - Monitor detects high error rate
11:01 AM - Alert sent to your phone π±
11:02 AM - You start investigating
11:05 AM - Problem fixed
Problem: Caught and fixed in 5 minutes, most users didn't even notice!
The 4 Golden Signals (What to Monitor)
These 4 metrics tell you everything about your APIβs health:
(How fast?)"] TRAFFIC["π Traffic
(How busy?)"] ERRORS["β Errors
(What's broken?)"] SATURATION["π₯ Saturation
(How full?)"] MONITOR --> LATENCY MONITOR --> TRAFFIC MONITOR --> ERRORS MONITOR --> SATURATION style MONITOR fill:#dbeafe,stroke:#2563eb style LATENCY fill:#d1fae5,stroke:#059669 style TRAFFIC fill:#fef3c7,stroke:#d97706 style ERRORS fill:#fee,stroke:#f00 style SATURATION fill:#fce7f3,stroke:#db2777
1. Latency (How Fast Is Your API?)
Latency = How long it takes to respond to a request
Good API:
User makes request at 10:00:00.000
API responds at 10:00:00.050 (50ms later)
Latency: 50ms β
(Very fast!)
Slow API:
User makes request at 10:00:00.000
API responds at 10:00:02.500 (2.5 seconds later)
Latency: 2,500ms β (Too slow!)
What to Aim For:
Excellent: < 100ms
Good: 100-300ms
Acceptable: 300-1000ms
Slow: > 1000ms (need to optimize!)
Real-World Example:
// Measure latency in your API
app.get('/api/users/:id', async (req, res) => {
const startTime = Date.now();
const user = await database.getUser(req.params.id);
const latency = Date.now() - startTime;
console.log(`Request took ${latency}ms`);
res.json(user);
});
// Output:
// Request took 45ms β
// Request took 52ms β
// Request took 1200ms β (Something is wrong!)
2. Traffic (How Busy Is Your API?)
Traffic = Number of requests your API receives
Why it matters:
Normal day: 1,000 requests/second
Black Friday: 10,000 requests/second
If you don't monitor traffic, you won't know when to add more servers!
Example Traffic Patterns:
What to Track:
- Requests per second (RPS)
- Requests per minute (RPM)
- Most popular endpoints
- Peak traffic times
3. Errors (Whatβs Breaking?)
Errors = Requests that fail
Types of Errors:
200 OK β
Success! Everything worked
400 Bad Request β οΈ User sent invalid data
401 Unauthorized β οΈ User not logged in
404 Not Found β οΈ Endpoint doesn't exist
500 Server Error β YOUR API IS BROKEN!
503 Unavailable β Server overloaded!
Error Rate Example:
Total Requests: 1,000
Errors: 10
Error Rate: 1% (acceptable)
Total Requests: 1,000
Errors: 500
Error Rate: 50% (CRITICAL PROBLEM! π¨)
What Good Looks Like:
Target Error Rate: < 0.1% (1 error per 1,000 requests)
Acceptable: < 1%
Problem: > 5%
Critical: > 10%
4. Saturation (How Full Is Your System?)
Saturation = How close to maximum capacity
Think of it like a highway:
10 cars on highway = 10% saturation β
(Fast and smooth)
50 cars on highway = 50% saturation β
(Still good)
80 cars on highway = 80% saturation β οΈ (Slowing down)
100 cars on highway = 100% saturation β (Traffic jam!)
What to Monitor:
CPU Usage:
- 30% = Healthy β
- 70% = Getting busy β οΈ
- 90% = Near limit! β
- 100% = SLOW! Need more servers!
Memory Usage:
- 40% = Good β
- 80% = Watch carefully β οΈ
- 95% = Critical! β
Database Connections:
- 20/100 = Plenty available β
- 90/100 = Running out! β οΈ
- 100/100 = Requests failing! β
Simple Monitoring Setup (Step-by-Step)
Option 1: Basic Logging (Free, 10 Minutes)
Step 1: Add logging to your API
// Log every request
app.use((req, res, next) => {
const startTime = Date.now();
// When response finishes
res.on('finish', () => {
const duration = Date.now() - startTime;
console.log({
method: req.method,
path: req.path,
status: res.statusCode,
duration: `${duration}ms`,
timestamp: new Date().toISOString()
});
});
next();
});
Step 2: Check your logs
# View recent logs
tail -f /var/log/myapi.log
# Output:
{ method: 'GET', path: '/api/users/123', status: 200, duration: '45ms', timestamp: '2024-01-01T10:00:00Z' }
{ method: 'POST', path: '/api/orders', status: 201, duration: '120ms', timestamp: '2024-01-01T10:00:01Z' }
{ method: 'GET', path: '/api/users/456', status: 500, duration: '5000ms', timestamp: '2024-01-01T10:00:02Z' }
β ERROR! Took 5 seconds!
You now know:
- β Which requests are slow
- β Which requests are failing
- β When problems happen
Option 2: Simple Dashboard (Free, 30 Minutes)
Tools: Express + Simple Stats Endpoint
// Track stats in memory
let stats = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
totalDuration: 0
};
// Update stats for each request
app.use((req, res, next) => {
const startTime = Date.now();
stats.totalRequests++;
res.on('finish', () => {
const duration = Date.now() - startTime;
stats.totalDuration += duration;
if (res.statusCode < 400) {
stats.successfulRequests++;
} else {
stats.failedRequests++;
}
});
next();
});
// Dashboard endpoint
app.get('/admin/stats', (req, res) => {
const avgDuration = stats.totalDuration / stats.totalRequests;
const errorRate = (stats.failedRequests / stats.totalRequests) * 100;
res.json({
totalRequests: stats.totalRequests,
successRate: `${(stats.successfulRequests / stats.totalRequests * 100).toFixed(2)}%`,
errorRate: `${errorRate.toFixed(2)}%`,
avgResponseTime: `${avgDuration.toFixed(0)}ms`
});
});
Visit http://yourapi.com/admin/stats:
{
"totalRequests": 15234,
"successRate": "99.2%",
"errorRate": "0.8%",
"avgResponseTime": "67ms"
}
Now you can check your API health anytime! β
Option 3: Professional Monitoring (Paid, 1 Hour Setup)
Best Tools for Beginners:
| Tool | Cost | Best For | Setup Time |
|---|---|---|---|
| Datadog | $15/month | Beautiful dashboards | 30 min β |
| New Relic | Free tier available | Easy to use | 30 min β |
| Grafana Cloud | Free tier | Open source | 1 hour ββ |
| AWS CloudWatch | ~$10/month | AWS users | 20 min β |
Example: Setting Up Datadog (Simple!)
Step 1: Sign up at datadog.com
Step 2: Install agent on your server
# One command!
DD_API_KEY=your-key bash -c "$(curl -L https://s3.amazonaws.com/dd-agent/scripts/install_script.sh)"
Step 3: Install library in your app
npm install dd-trace
Step 4: Add to your code
// At the very top of your main file
require('dd-trace').init();
// That's it! π
Step 5: Check Datadog dashboard
You now see:
- π Request rate graph
- β±οΈ Average response time
- β Error rate
- π₯οΈ CPU & memory usage
- π Everything automatically!
Setting Up Alerts (Get Notified When Things Break)
Alerts = Automatic notifications when something is wrong
Alert Example: High Error Rate
What you want:
If error rate > 5% for more than 5 minutes
β Send me a text message! π±
How to set up (Example with Datadog):
1. Go to Monitors β New Monitor
2. Choose "Metric Monitor"
3. Set condition:
- Metric: error_rate
- Alert when: above 5%
- For: 5 minutes
4. Set notification:
- Send to: your-phone@sms.com
- Message: "API errors are high! Check immediately"
5. Save!
Now you get alerted the moment things break β
Smart Alerts (What to Alert On)
β DO Alert On These (Important!):
1. Error Rate > 5%
β Something is broken!
2. Response Time > 1 second
β API is too slow!
3. Traffic drops to 0
β API might be down!
4. CPU > 90% for 10 minutes
β Need more servers!
5. Database connections > 95%
β About to run out!
β DONβT Alert On These (Too Noisy):
1. Single error (happens all the time)
2. Response time spike for 1 second (temporary)
3. CPU > 50% (still plenty of capacity)
4. One slow request (users make mistakes)
Golden Rule: Only alert on things that need immediate action!
Performance Optimization (Making Your API Faster)
Step 1: Find the Slow Parts
Add timing to your code:
app.get('/api/orders/:id', async (req, res) => {
console.time('Total Request');
console.time('Database Query');
const order = await db.query('SELECT * FROM orders WHERE id = $1', [req.params.id]);
console.timeEnd('Database Query'); // Output: Database Query: 150ms
console.time('User Lookup');
const user = await db.query('SELECT * FROM users WHERE id = $1', [order.userId]);
console.timeEnd('User Lookup'); // Output: User Lookup: 120ms
console.time('Product Details');
const products = await getProductDetails(order.items);
console.timeEnd('Product Details'); // Output: Product Details: 800ms β SLOW!
console.timeEnd('Total Request'); // Output: Total Request: 1070ms
res.json({ order, user, products });
});
Now you know: Getting product details is the slow part!
Step 2: Add Caching (Make It 10x Faster)
Before (Slow):
// Every request hits database (slow!)
app.get('/api/products/:id', async (req, res) => {
const product = await db.query('SELECT * FROM products WHERE id = $1', [req.params.id]);
res.json(product);
// Response time: 150ms
});
After (Fast):
const redis = require('redis');
const client = redis.createClient();
app.get('/api/products/:id', async (req, res) => {
const cacheKey = `product:${req.params.id}`;
// Try cache first
const cached = await client.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached)); // Response time: 2ms β‘
}
// Cache miss: get from database
const product = await db.query('SELECT * FROM products WHERE id = $1', [req.params.id]);
// Store in cache for 5 minutes
await client.setex(cacheKey, 300, JSON.stringify(product));
res.json(product); // Response time: 150ms first time, then 2ms
});
Result: 75x faster! (150ms β 2ms)
Step 3: Database Indexes (Speed Up Queries)
Problem: Slow Query
-- Without index: Scans ALL 1 million users (SLOW!)
SELECT * FROM users WHERE email = 'john@example.com';
-- Query time: 2000ms β
Solution: Add Index
-- Create index on email column
CREATE INDEX idx_users_email ON users(email);
-- Same query now:
SELECT * FROM users WHERE email = 'john@example.com';
-- Query time: 5ms β
(400x faster!)
When to Add Indexes:
Add index if you query by that column frequently:
- User IDs β
(query all the time)
- Email addresses β
(login queries)
- Product SKUs β
(product lookups)
- Order dates β
(date range queries)
Step 4: Reduce Data Transfer
Bad: Sending Too Much Data
// Returns EVERYTHING (including huge description)
app.get('/api/products', async (req, res) => {
const products = await db.query('SELECT * FROM products');
res.json(products);
// Response: 5 MB of data
// Time: 500ms β
});
Good: Send Only Whatβs Needed
// Returns only ID, name, price (what user sees in list)
app.get('/api/products', async (req, res) => {
const products = await db.query('SELECT id, name, price FROM products');
res.json(products);
// Response: 100 KB of data
// Time: 50ms β
(10x faster!)
});
// Full details only when user clicks product
app.get('/api/products/:id', async (req, res) => {
const product = await db.query('SELECT * FROM products WHERE id = $1', [req.params.id]);
res.json(product);
});
Step 5: Parallel Requests (Do Multiple Things at Once)
Bad: Sequential (Slow)
// Do one thing at a time (SLOW!)
app.get('/api/dashboard', async (req, res) => {
const user = await getUser(); // 100ms
const orders = await getOrders(); // 150ms
const products = await getProducts(); // 200ms
res.json({ user, orders, products });
// Total time: 450ms β
});
Good: Parallel (Fast)
// Do everything at the same time!
app.get('/api/dashboard', async (req, res) => {
const [user, orders, products] = await Promise.all([
getUser(), // β All 3 run simultaneously
getOrders(), // β
getProducts() // β
]);
res.json({ user, orders, products });
// Total time: 200ms β
(2x faster!)
});
Performance Monitoring Dashboard (What to Display)
Simple Dashboard Example:
βββββββββββββββββββββββββββββββββββββββββββ
β API Performance Dashboard β
βββββββββββββββββββββββββββββββββββββββββββ€
β β
β π Requests/Second: 1,234 β
β β±οΈ Avg Response Time: 67ms β
β β
Success Rate: 99.8% β
β β Error Rate: 0.2% β
β β
β π Slowest Endpoints: β
β 1. POST /api/upload - 2.3s β
β 2. GET /api/reports - 890ms β
β 3. GET /api/analytics - 450ms β
β β
β π₯ Resource Usage: β
β CPU: ββββββββββ 45% β
β Memory: ββββββββββ 60% β
β Database: ββββββββββ 30% β
β β
βββββββββββββββββββββββββββββββββββββββββββ
Real-World Monitoring Examples
Example 1: E-Commerce API
What to Monitor:
1. Checkout endpoint response time
β Alert if > 500ms (users abandon slow checkouts!)
2. Product search response time
β Alert if > 200ms (users expect instant search)
3. Payment gateway errors
β Alert immediately on ANY error (lost revenue!)
4. Shopping cart endpoint traffic
β Spike means potential sales increase
5. Database connection pool
β Alert if > 80% (about to run out)
Dashboard:
Top Priority Metrics:
- Checkout success rate: 98.5% β
- Average cart value: $67.32
- Payment failures: 1.2% β οΈ (investigate!)
- Page load time: 1.2s
Example 2: Social Media API
What to Monitor:
1. Feed load time
β Alert if > 300ms (users scroll fast!)
2. Image upload success rate
β Alert if < 95% (broken uploads frustrate users)
3. Real-time notification latency
β Should be < 1 second
4. API calls per user
β Detect unusual patterns (possible abuse)
5. Peak traffic times
β Know when to scale up
Dashboard:
Real-Time Metrics:
- Active users: 45,234
- Posts per second: 127
- Image uploads/min: 890
- Failed uploads: 12 (1.3%) β
- Average feed load: 245ms β
Example 3: Banking API
What to Monitor:
1. Transaction endpoint errors
β Alert on ANY error (money is involved!)
2. Authentication failures
β Spike might indicate attack
3. Account balance query time
β Must be < 100ms (users check often)
4. Database replication lag
β Alert if > 1 second (stale data)
5. Security events
β Failed logins, suspicious patterns
Dashboard:
Critical Metrics:
- Transactions processed: 15,234
- Transaction errors: 0 β
(must be zero!)
- Avg transaction time: 45ms β
- Failed auth attempts: 23 β οΈ (monitor for attacks)
- Uptime today: 100% β
Common Performance Problems & Solutions
Problem 1: Slow Database Queries
Symptom:
API response time: 2000ms (way too slow!)
Diagnosis:
// Add query timing
const start = Date.now();
const users = await db.query('SELECT * FROM users WHERE status = "active"');
console.log(`Query took ${Date.now() - start}ms`);
// Output: Query took 1800ms β PROBLEM FOUND!
Solution:
-- Add index
CREATE INDEX idx_users_status ON users(status);
-- Now query takes 50ms instead of 1800ms β
Problem 2: Memory Leak
Symptom:
Memory usage slowly increases over time
Hour 1: 200 MB
Hour 2: 400 MB
Hour 3: 600 MB
Hour 4: 800 MB
Hour 5: SERVER CRASH! β
Diagnosis:
// Bad: Storing data in global variable (never gets cleaned up!)
const cache = {}; // β Memory leak!
app.get('/api/users/:id', async (req, res) => {
cache[req.params.id] = await getUser(req.params.id);
res.json(cache[req.params.id]);
});
// Cache grows forever, using more and more memory
Solution:
// Good: Use Redis with expiration
app.get('/api/users/:id', async (req, res) => {
const cached = await redis.get(`user:${req.params.id}`);
if (cached) return res.json(JSON.parse(cached));
const user = await getUser(req.params.id);
await redis.setex(`user:${req.params.id}`, 300, JSON.stringify(user)); // Auto-expires in 5 min
res.json(user);
});
// Memory stays constant β
Problem 3: Too Many Database Connections
Symptom:
Error: "Too many connections to database"
Some requests fail randomly
Diagnosis:
// Bad: Creating new connection for each request
app.get('/api/users/:id', async (req, res) => {
const db = new Database(); // β New connection every time!
const user = await db.query(...);
res.json(user);
// Connection not closed!
});
Solution:
// Good: Use connection pool (reuse connections)
const pool = new Pool({
max: 20, // Maximum 20 connections
min: 5 // Always keep 5 ready
});
app.get('/api/users/:id', async (req, res) => {
const user = await pool.query(...); // β Reuses existing connection
res.json(user);
});
// Connections are reused efficiently β
Monitoring Checklist
Before going to production:
- Basic Logging: Every request logged with duration
- Error Tracking: All errors captured with details
- Performance Metrics: Latency, traffic, errors, saturation tracked
- Alerts Set Up: Get notified when problems occur
- Dashboard: Can see API health at a glance
- Database Monitoring: Query performance tracked
- Resource Monitoring: CPU, memory, disk usage tracked
- Uptime Monitoring: External service checks if API is reachable
- Regular Reviews: Check metrics weekly
- Documentation: Team knows how to read dashboards
Simple Monitoring Tools Comparison
| Tool | Cost | Complexity | Best For | Setup Time |
|---|---|---|---|---|
| Console.log | Free | Very Low β | Learning/testing | 5 min |
| Morgan (Express) | Free | Low β | Simple logging | 10 min |
| PM2 Monitoring | Free | Low β | Node.js apps | 15 min |
| Datadog | $15/mo | Medium ββ | Professional teams | 30 min |
| New Relic | Free tier | Medium ββ | Great dashboards | 30 min |
| Grafana + Prometheus | Free | High βββ | Self-hosted | 2 hours |
| AWS CloudWatch | ~$10/mo | Low β | AWS users | 20 min |
Quick Start: 15-Minute Monitoring Setup
Step 1: Install Morgan (Request Logger)
npm install morgan
Step 2: Add to Your App
const morgan = require('morgan');
// Log every request
app.use(morgan('combined'));
// Now you see:
// 127.0.0.1 - - [01/Jan/2024:10:00:00 +0000] "GET /api/users/123 HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
Step 3: Add Error Tracking
app.use((err, req, res, next) => {
console.error({
error: err.message,
stack: err.stack,
path: req.path,
method: req.method,
timestamp: new Date()
});
res.status(500).json({ error: 'Internal server error' });
});
Step 4: Create Health Endpoint
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
uptime: process.uptime(),
memory: process.memoryUsage(),
timestamp: new Date()
});
});
Step 5: Set Up Uptime Monitoring (Free)
Go to uptimerobot.com:
- Sign up (free)
- Add your API:
https://yourapi.com/health - Check every 5 minutes
- Email you if itβs down
Done! You now have basic monitoring in 15 minutes! β
Summary: Monitoring & Performance
The Golden Rules:
- Monitor the 4 Golden Signals β Latency, Traffic, Errors, Saturation
- Set up alerts β Know about problems before users complain
- Start simple β Basic logging is better than nothing
- Measure before optimizing β Find the slow parts first
- Cache aggressively β Can make APIs 10-100x faster
What Good Monitoring Gives You
Without Monitoring:
- Find out about problems from angry users β
- Don't know what's slow β
- Can't prove improvements β
- Constantly putting out fires β
With Monitoring:
- Know about problems before users β
- See exactly what's slow β
- Measure impact of optimizations β
- Proactively prevent issues β
- Sleep better at night π΄β
Progressive Monitoring Approach
Week 1: Basic Logging
- Console.log with timestamps
- Error tracking
- Cost: $0
Week 2: Simple Dashboard
- Request counter
- Average response time
- Error rate
- Cost: $0
Week 3: Add Alerts
- High error rate alert
- Slow response time alert
- Cost: $0 (use free tier)
Week 4: Professional Tool
- Datadog or New Relic
- Beautiful dashboards
- Advanced alerts
- Cost: ~$15/month
Month 2+: Fine-tune
- Optimize based on data
- Add custom metrics
- Improve alerts
Continue the Series
This is Part 5 of the βScaling Your APIβ series:
- Part 1: Performance & Infrastructure β - Technical techniques to handle millions of requests
- Part 2: Design & Architecture β - Organizational strategies and API design patterns
- Part 3: Choosing the Right Database β - Database selection for your API
- Part 4: Load Balancing & High Availability β - Keeping your API always available
- Part 5: Monitoring & Performance β You are here
Further Reading
- Datadog Documentation β Learn professional monitoring
- Prometheus & Grafana Guide β Open source monitoring
- Google SRE Book β How Google monitors services
- New Relic University β Free monitoring courses
Remember: You canβt improve what you donβt measure. Start monitoring today, even if itβs just basic logging. Your future self (and your users) will thank you!