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)

How to Complete This Guide

Choose API Style

Select REST, GraphQL, or tRPC based on your data fetching patterns, client requirements, and team expertise.

Implement Authentication

Set up OAuth 2.0, API keys, or JWT-based auth appropriate to your consumers and security requirements.

Build Error Handling

Implement retry logic with exponential backoff, circuit breakers, and structured error responses.

Configure Rate Limiting & Caching

Set up rate limits, response caching, and stale-while-revalidate patterns for reliability and performance.

Set Up Testing & Monitoring

Write integration tests, implement contract testing, and deploy production monitoring with alerting.

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.

Frequently Asked Questions

Should I use REST or GraphQL for my project?

Use REST for simple CRUD operations, third-party integrations, and when HTTP caching is important. Choose GraphQL when different clients need different data shapes, when you're fetching deeply nested related data, or when reducing network requests for mobile clients is a priority. Many projects use both effectively.

How do I handle API versioning?

The most common approaches are URL versioning (/api/v2/users), header versioning (Accept: application/vnd.api.v2+json), and query parameter versioning (?version=2). URL versioning is the simplest and most explicit. Avoid breaking changes by adding new fields rather than modifying existing ones. Deprecate old versions with clear timelines and migration guides.

What's the best way to test third-party API integrations?

Use recorded HTTP responses (fixtures) for unit and integration tests to avoid hitting live APIs during CI/CD. Tools like Nock, VCR, or Polly.js record actual API responses and replay them during tests. Supplement with contract tests to catch breaking changes. Run occasional smoke tests against live APIs in a staging environment.

How should I store API keys securely?

Never commit API keys to source control. Use environment variables for local development and a secrets manager (AWS Secrets Manager, HashiCorp Vault, Doppler) for production. Rotate keys on a regular schedule and immediately if compromised. Use separate keys for development, staging, and production environments.

What happens when a third-party API goes down?

Implement graceful degradation: serve cached data when available, show informative error messages, and disable affected features without crashing the entire application. Use circuit breakers to stop hammering a failing service. Set up monitoring alerts so your team knows about outages before users report them.

Need Help With API Integrations?

Our development team builds reliable, well-tested API integrations that connect your systems and extend your application's capabilities. From payment processing to CRM syncing, we've integrated hundreds of APIs.