Web Development Guide
API Integration Guide
Modern web applications depend on APIs to connect services, share data, and extend functionality. This guide covers everything from API fundamentals to production-ready integration patterns including authentication, error handling, rate limiting, and monitoring.
Prerequisites
- Understanding of HTTP methods, status codes, and request/response headers
- Familiarity with JSON data format and your language's HTTP client libraries
- Access to API documentation for any third-party services you're integrating
- A testing framework set up in your project (Jest, Vitest, pytest, etc.)
- Monitoring infrastructure or a plan to set one up (Datadog, New Relic, or open-source alternatives)
API Fundamentals: REST & GraphQL
APIs (Application Programming Interfaces) define how software components communicate. For web development, this primarily means HTTP-based APIs that exchange data in JSON format. The two dominant paradigms are REST (Representational State Transfer) and GraphQL, each with distinct strengths that suit different use cases.
REST APIs organize resources around URLs and use standard HTTP methods. GET /api/users retrieves a list of users. POST /api/users creates a new user. GET /api/users/123 retrieves a specific user. PUT or PATCH /api/users/123 updates that user. DELETE /api/users/123 removes them. REST is simple, cacheable, and universally understood. Most third-party APIs (Stripe, Twilio, SendGrid) use REST, making it the default choice for external integrations.
GraphQL provides a query language that lets clients request exactly the data they need in a single request. Instead of making multiple REST calls to /users/123, /users/123/orders, and /users/123/addresses, a GraphQL query retrieves all three in one round trip. This eliminates over-fetching (getting more data than needed) and under-fetching (requiring multiple requests). GraphQL excels in mobile applications where bandwidth is limited and in complex UIs that display data from multiple related entities.
The choice between REST and GraphQL depends on your use case. REST is simpler to implement, easier to cache, and better supported by tooling. GraphQL reduces network requests, provides strong typing via its schema, and is ideal when different clients need different data shapes. Many production applications use both: REST for simple CRUD operations and third-party integrations, and GraphQL for complex data fetching in client applications. Tools like tRPC offer a third option for TypeScript projects, providing end-to-end type safety without REST or GraphQL overhead.
REST APIs
Resource-based URLs with HTTP methods. Simple, cacheable, and universally supported by third-party services.
GraphQL
Query language for precise data fetching. Reduces over-fetching and enables single-request data retrieval.
When to Use REST
External integrations, simple CRUD operations, and when HTTP caching is important.
When to Use GraphQL
Complex UIs, mobile apps with bandwidth constraints, and applications needing flexible data shapes.
tRPC
End-to-end type-safe APIs for TypeScript projects without REST or GraphQL boilerplate.
API Authentication Methods
API authentication verifies the identity of the client making requests. Choosing the right authentication method depends on who's consuming the API (first-party apps, third-party developers, or server-to-server communication), the sensitivity of the data, and the complexity you're willing to manage.
API keys are the simplest authentication method. The client includes a key in the request header (Authorization: Bearer sk_live_xxxx) or query parameter. API keys identify the calling application but don't identify individual users. They're appropriate for server-to-server communication and third-party integrations where you need to track and rate-limit usage. Never expose API keys in client-side JavaScript since they'll be visible in browser developer tools and can be stolen.
OAuth 2.0 is the standard for delegated authorization, allowing users to grant third-party applications limited access to their data without sharing credentials. OAuth defines several flows: Authorization Code (for server-side apps), Authorization Code with PKCE (for single-page and mobile apps), and Client Credentials (for server-to-server). Understanding which flow to use is critical. The implicit flow is deprecated due to security concerns. Most modern SPAs should use Authorization Code with PKCE.
JSON Web Tokens (JWTs) are commonly used as the token format for API authentication. A JWT contains a payload with user information (claims), signed with a secret or public/private key pair. The server can verify the token without a database lookup, making JWTs stateless and scalable. However, JWTs cannot be revoked before expiration unless you maintain a blocklist, which negates the stateless advantage. Use short-lived access tokens (15 minutes) with longer-lived refresh tokens to balance security and usability. For machine-to-machine communication, mutual TLS (mTLS) provides the strongest authentication by verifying both client and server certificates.
API Keys
Simple, application-level auth for server-to-server communication. Never expose in client-side code.
OAuth 2.0
Delegated authorization standard. Use Authorization Code with PKCE for SPAs and mobile apps.
JWT Tokens
Stateless, signed tokens for user authentication. Use short expiration with refresh token rotation.
mTLS
Mutual certificate verification for highest-security machine-to-machine communication.
API Key Management
Rotate keys regularly, use separate keys per environment, and revoke compromised keys immediately.
Error Handling Patterns
Robust error handling separates production-quality integrations from fragile prototypes. When integrating with external APIs, assume that every request can fail. Networks go down, services experience outages, rate limits get hit, and responses contain unexpected data. Your application must handle all of these gracefully without crashing or losing user data.
Use standard HTTP status codes consistently. 2xx codes indicate success. 4xx codes indicate client errors (bad request, unauthorized, not found, rate limited). 5xx codes indicate server errors. When building your own APIs, return structured error responses with a consistent format: error code, human-readable message, and optionally a link to documentation. Avoid returning stack traces or internal error details to API consumers, as these leak implementation details that can aid attackers.
Implement retry logic with exponential backoff for transient failures (5xx errors, network timeouts). A common pattern starts with a 1-second delay, doubles to 2 seconds, then 4 seconds, with random jitter added to prevent thundering herd problems when multiple clients retry simultaneously. Set a maximum retry count (typically 3-5 attempts) and a maximum backoff duration (30-60 seconds). Never retry 4xx errors except for 429 (Too Many Requests), which should respect the Retry-After header.
Use the circuit breaker pattern for integrations that may experience prolonged outages. When a service fails repeatedly, the circuit "opens" and immediately returns a fallback response without making the request, preventing cascading failures and giving the downstream service time to recover. After a timeout period, the circuit allows a test request through. If it succeeds, the circuit closes and normal operation resumes. Libraries like opossum (Node.js) and resilience4j (Java) implement this pattern. Log every error with enough context (request URL, status code, response body, retry count) to diagnose issues without reproducing them.
Consistent Error Responses
Return structured errors with codes, messages, and documentation links. Never expose stack traces.
Exponential Backoff
Retry transient failures with increasing delays and jitter. Set maximum retry counts and backoff duration.
Circuit Breaker
Stop calling failing services after repeated errors to prevent cascading failures. Allow test requests after timeout.
Timeout Configuration
Set explicit connection and read timeouts on every HTTP request. Never use default infinite timeouts.
Error Logging
Log request URL, status code, response body, and retry count for every error to enable diagnosis.
Rate Limiting & Response Caching
Rate limiting protects APIs from abuse and ensures fair resource allocation. Response caching reduces redundant API calls, improves performance, and helps you stay within rate limits. Both are essential for production API integrations.
When consuming third-party APIs, understand their rate limits before you start building. Stripe allows 100 requests per second. The GitHub API allows 5,000 requests per hour with authentication. Google Maps API charges per request above free tier limits. Exceeding rate limits typically results in HTTP 429 responses with a Retry-After header indicating how long to wait. Design your integration to stay well below published limits, and implement 429 handling that respects the Retry-After value.
For APIs you build, implement rate limiting at multiple levels: per IP address (to prevent abuse), per API key (to enforce usage tiers), and per user (to ensure fair access). Use a sliding window or token bucket algorithm rather than fixed windows, which allow burst traffic at window boundaries. Redis is the standard backing store for distributed rate limiting, using atomic increment operations on time-bucketed keys.
Cache API responses aggressively for data that doesn't change frequently. Use HTTP cache headers (Cache-Control, ETag, Last-Modified) to enable client-side and CDN caching. For server-side caching, store API responses in Redis or an in-memory cache with TTLs matched to data volatility. A product catalog that updates daily can be cached for hours. Real-time pricing data might need 30-second TTLs. Implement stale-while-revalidate patterns where your application serves cached data immediately while fetching fresh data in the background, ensuring users never wait for cache misses on frequently accessed endpoints.
Understand API Limits
Document rate limits for every third-party API you integrate. Design to stay well below published limits.
Implement Rate Limiting
Limit by IP, API key, and user using sliding window or token bucket algorithms backed by Redis.
HTTP Cache Headers
Use Cache-Control, ETag, and Last-Modified headers to enable client-side and CDN caching.
Server-Side Caching
Cache API responses in Redis with TTLs matched to data update frequency.
Stale-While-Revalidate
Serve cached data immediately while fetching fresh data in the background for seamless user experience.
API Testing & Monitoring
API integrations are only as reliable as your testing and monitoring. Third-party APIs change, networks fail, and edge cases multiply as your application grows. A comprehensive testing strategy combined with production monitoring ensures your integrations remain reliable over time.
Write integration tests that verify your API clients handle both successful responses and error scenarios. Use mock servers or recorded responses (via tools like Nock for Node.js, VCR for Ruby, or WireMock for Java) to test against realistic API responses without hitting live endpoints during CI/CD. Test every error code your integration might encounter: 400, 401, 403, 404, 429, 500, 502, 503. Verify that timeout handling, retry logic, and circuit breakers work as expected under failure conditions.
Contract testing ensures that the API producer and consumer agree on the request and response format. Tools like Pact create a contract from consumer tests that the producer validates against. This catches breaking changes before deployment. For APIs you control, use OpenAPI (Swagger) specifications as your source of truth. Generate client SDKs, validation middleware, and documentation from the spec to ensure consistency.
In production, monitor API response times, error rates, and throughput for every integration. Set up alerts when error rates exceed thresholds (e.g., >1% 5xx errors) or response times degrade (e.g., p95 exceeds 2 seconds). Use distributed tracing with tools like OpenTelemetry to track requests across multiple services and identify bottlenecks. Log request and response data for debugging, but be careful to redact sensitive information like API keys, passwords, and personal data. Create dashboards that show the health of each integration at a glance so your team can identify problems before they impact users.
Integration Testing
Test both success and error paths with mock servers. Cover every error code and failure scenario.
Contract Testing
Use Pact or OpenAPI to verify API contracts between producers and consumers before deployment.
Production Monitoring
Track response times, error rates, and throughput with alerts for threshold breaches.
Distributed Tracing
Use OpenTelemetry to trace requests across services and identify integration bottlenecks.
Security Logging
Log requests for debugging but redact API keys, passwords, and personal data from logs.
Related Content
Web Accessibility Compliance Guide
Practical web accessibility guide covering WCAG 2.2 standards, semantic HTML, ARIA, keyboard navigation, color contrast, and accessibility testing tools.
How to Choose the Right Tech Stack
A decision framework for choosing frontend frameworks, backend languages, databases, and hosting. Make technology decisions that scale with your business.
CMS Selection Guide for Business
Compare headless vs traditional CMS platforms, evaluate requirements, and choose the right content management system for your business website or application.
Website Migration Guide
Complete website migration guide covering planning, redirect mapping, execution, and post-migration monitoring to protect your SEO rankings and traffic.