Building Scalable API Integrations: Best Practices & Patterns
Modern applications rarely exist in isolation. API integrations connect your application to payment processors, CRMs, analytics platforms, and countless other services. Building these integrations correctly is crucial for maintainability and scalability.
Integration Patterns
1. RESTful APIs
Most common pattern for web services:
// Axios client with retry logic
import axios from 'axios'
import axiosRetry from 'axios-retry'
const client = axios.create({
baseURL: process.env.API_BASE_URL,
timeout: 10000,
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`,
'Content-Type': 'application/json'
}
})
axiosRetry(client, {
retries: 3,
retryDelay: axiosRetry.exponentialDelay
})
2. GraphQL
Flexible data fetching:
import { GraphQLClient } from 'graphql-request'
const client = new GraphQLClient(endpoint, {
headers: {
authorization: `Bearer ${token}`
}
})
const data = await client.request(query, variables)
3. Webhooks
Event-driven integrations:
// Stripe webhook handler
export async function POST(req: Request) {
const sig = req.headers.get('stripe-signature')
const body = await req.text()
const event = stripe.webhooks.constructEvent(body, sig, secret)
switch (event.type) {
case 'payment_intent.succeeded':
await handlePaymentSuccess(event.data)
break
}
return new Response(JSON.stringify({ received: true }))
}
Error Handling
Robust error handling is critical:
class APIError extends Error {
constructor(
message: string,
public statusCode: number,
public response?: any
) {
super(message)
}
}
async function apiCall<T>(endpoint: string): Promise<T> {
try {
const response = await fetch(endpoint)
if (!response.ok) {
throw new APIError(
'API request failed',
response.status,
await response.json()
)
}
return response.json()
} catch (error) {
// Log to monitoring service
logger.error('API call failed', { endpoint, error })
throw error
}
}
Rate Limiting
Respect API limits and implement backoff:
import pLimit from 'p-limit'
const limit = pLimit(5) // Max 5 concurrent requests
const results = await Promise.all(
items.map(item => limit(() => fetchData(item)))
)
Caching Strategy
Reduce API calls and improve performance:
import { Redis } from '@upstash/redis'
const redis = new Redis({
url: process.env.REDIS_URL,
token: process.env.REDIS_TOKEN
})
async function getCachedData(key: string) {
const cached = await redis.get(key)
if (cached) return cached
const data = await fetchFromAPI(key)
await redis.setex(key, 3600, JSON.stringify(data)) // 1 hour TTL
return data
}
Security Best Practices
✅ Store API keys in environment variables ✅ Use HTTPS for all requests ✅ Implement request signing when available ✅ Validate webhook signatures ✅ Rotate keys regularly ✅ Use least-privilege access
Monitoring & Observability
Track integration health:
- Response times
- Error rates
- Rate limit usage
- Webhook delivery success
- Data freshness
Testing Strategies
// Mock API responses for testing
import { rest } from 'msw'
import { setupServer } from 'msw/node'
const server = setupServer(
rest.get('/api/users/:id', (req, res, ctx) => {
return res(ctx.json({ id: req.params.id, name: 'Test User' }))
})
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
Need help building robust API integrations? Contact our team for expert assistance.
Tags
LetsGrow Dev Team
Marketing Technology Experts
