Web Development

Understanding REST APIs: A Developer’s Introduction

Understanding REST APIs: A Developer’s Introduction

REST (Representational State Transfer) is an architectural style for building web APIs. It was defined by Roy Fielding in his 2000 doctoral dissertation at UC Irvine. REST uses the HTTP protocol that the web already runs on, mapping CRUD operations to HTTP methods and identifying resources through URLs. This simplicity is why REST became the dominant approach for web service communication — if you understand HTTP, you already understand the foundation of REST.

Nearly every web and mobile application relies on REST APIs. When a React front end fetches user data, when a mobile app loads a product catalog, when a third-party integration syncs calendar events — these are all REST API calls. Understanding how to design, consume, and debug REST APIs is a core skill for every web developer.

REST Principles

Fielding defined six constraints that characterize a RESTful architecture:

1. Client-Server Separation

The client (front end) and server (back end) are independent. The client does not need to know about database schemas or server logic. The server does not need to know about the UI. They communicate exclusively through the API contract.

2. Statelessness

Each request from the client contains all the information the server needs to process it. The server does not store client session state between requests. Authentication tokens, pagination cursors, and other context travel with each request. This makes scaling straightforward — any server in a cluster can handle any request.

3. Cacheability

Responses must declare whether they are cacheable. Proper cache headers reduce redundant requests, improve performance, and decrease server load. GET responses for static data should include Cache-Control headers. POST, PUT, and DELETE responses typically should not be cached.

4. Uniform Interface

All resources are accessed through a consistent, standardized interface. URLs identify resources. HTTP methods define operations. Standard status codes communicate outcomes. This uniformity means developers can work with any REST API without learning a proprietary protocol.

5. Layered System

The client cannot tell whether it is connected directly to the application server or to an intermediary like a load balancer, CDN, or API gateway. This enables infrastructure flexibility without changing the client.

6. Code on Demand (Optional)

Servers can optionally send executable code (like JavaScript) to clients. This is the only optional constraint and is rarely used in practice.

HTTP Methods as CRUD Operations

REST maps the four basic data operations (Create, Read, Update, Delete) to HTTP methods:

Method   Operation   Example                         Idempotent
────────────────────────────────────────────────────────────────
GET      Read        GET /api/v1/posts                Yes
POST     Create      POST /api/v1/posts               No
PUT      Replace     PUT /api/v1/posts/42              Yes
PATCH    Partial     PATCH /api/v1/posts/42            Yes
DELETE   Delete      DELETE /api/v1/posts/42           Yes

Idempotent means calling the same request multiple times produces the same result. GET, PUT, PATCH, and DELETE are idempotent. POST is not — sending the same POST twice creates two resources.

GET: Retrieve Resources

# Get all posts
curl https://api.example.com/v1/posts

# Get a specific post
curl https://api.example.com/v1/posts/42

# Get posts with query parameters
curl "https://api.example.com/v1/posts?status=published&limit=10&offset=20"

POST: Create Resources

# Create a new post
curl -X POST https://api.example.com/v1/posts \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -d '{
    "title": "Understanding REST APIs",
    "body": "REST is an architectural style...",
    "status": "draft"
  }'

PUT and PATCH: Update Resources

# PUT replaces the entire resource
curl -X PUT https://api.example.com/v1/posts/42 \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Updated Title",
    "body": "Complete new body text",
    "status": "published"
  }'

# PATCH updates specific fields only
curl -X PATCH https://api.example.com/v1/posts/42 \
  -H "Content-Type: application/json" \
  -d '{"status": "published"}'

DELETE: Remove Resources

# Delete a post
curl -X DELETE https://api.example.com/v1/posts/42 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

HTTP Status Codes

Status codes communicate the result of every API request. They are grouped into five categories:

2xx: Success

200 OK              — Request succeeded, response contains data
201 Created         — Resource created successfully (POST)
204 No Content      — Success, no response body (DELETE)

3xx: Redirection

301 Moved Permanently  — Resource has a new permanent URL
304 Not Modified       — Cached version is still valid

4xx: Client Errors

400 Bad Request        — Invalid request data (validation failed)
401 Unauthorized       — Authentication required or token expired
403 Forbidden          — Authenticated but not authorized
404 Not Found          — Resource does not exist
409 Conflict           — Request conflicts with current state
422 Unprocessable Entity — Valid JSON but semantic errors
429 Too Many Requests  — Rate limit exceeded

5xx: Server Errors

500 Internal Server Error — Unexpected server failure
502 Bad Gateway           — Upstream service unavailable
503 Service Unavailable   — Server overloaded or in maintenance

Authentication

REST APIs use several authentication mechanisms. The choice depends on your security requirements and client type.

API Keys

Simple string tokens passed in headers or query parameters. Easy to implement but offer limited security — if the key leaks, it must be revoked and regenerated.

curl https://api.example.com/v1/data \
  -H "X-API-Key: sk_live_abc123def456"

Bearer Tokens (JWT)

JSON Web Tokens are self-contained tokens that encode user identity and permissions. The server validates the token signature without hitting a database, making JWT authentication fast and scalable.

// JWT structure: header.payload.signature
const token = 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0Mn0.signature';

// Fetch with Bearer token
const response = await fetch('https://api.example.com/v1/me', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  }
});

OAuth 2.0

OAuth 2.0 is the standard for third-party authorization. Instead of sharing credentials, users grant limited access through tokens issued by an authorization server. Google, GitHub, and most social login providers use OAuth 2.0.

API Versioning

APIs evolve over time. Breaking changes — renaming fields, changing response structures, removing endpoints — can break existing clients. Versioning lets you evolve the API while maintaining backward compatibility.

URL Path Versioning (Most Common)

GET /api/v1/posts
GET /api/v2/posts

Header Versioning

curl https://api.example.com/posts \
  -H "Accept: application/vnd.example.v2+json"

Query Parameter Versioning

GET /api/posts?version=2

URL path versioning is the most widely adopted approach because it is explicit and easy to understand. The version is visible in every URL, making debugging and documentation straightforward.

Consuming REST APIs in JavaScript

The Fetch API is the modern standard for making HTTP requests in the browser and Node.js:

// GET request
async function getPosts() {
  const response = await fetch('https://api.example.com/v1/posts');

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  return response.json();
}

// POST request with error handling
async function createPost(title, body) {
  const response = await fetch('https://api.example.com/v1/posts', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${getToken()}`
    },
    body: JSON.stringify({ title, body, status: 'draft' })
  });

  if (response.status === 422) {
    const errors = await response.json();
    throw new ValidationError(errors);
  }

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  return response.json();
}

// Pagination
async function getAllPosts() {
  let posts = [];
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await fetch(
      `https://api.example.com/v1/posts?page=${page}&per_page=50`
    );
    const data = await response.json();
    posts = posts.concat(data.items);
    hasMore = data.has_next;
    page++;
  }

  return posts;
}

API Design Best Practices

URL Structure

  • Use nouns, not verbs: /posts not /getPosts
  • Use plural nouns: /posts not /post
  • Nest related resources: /posts/42/comments
  • Keep URLs lowercase with hyphens: /user-profiles not /userProfiles
  • Limit nesting depth to two levels: /posts/42/comments is fine, /posts/42/comments/7/likes/3 is too deep

Response Format

{
  "data": {
    "id": 42,
    "type": "post",
    "attributes": {
      "title": "Understanding REST APIs",
      "status": "published",
      "created_at": "2026-01-15T10:30:00Z",
      "updated_at": "2026-01-16T14:22:00Z"
    },
    "relationships": {
      "author": { "id": 7, "name": "Jane Smith" },
      "comments": { "count": 12, "href": "/api/v1/posts/42/comments" }
    }
  },
  "meta": {
    "request_id": "req_abc123",
    "response_time_ms": 45
  }
}

Error Responses

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      { "field": "title", "message": "Title is required" },
      { "field": "status", "message": "Must be one of: draft, published, archived" }
    ]
  }
}

Pagination, Filtering, and Sorting

APIs that return collections need pagination to prevent returning thousands of records in a single response:

# Offset-based pagination
GET /api/v1/posts?page=3&per_page=25

# Cursor-based pagination (better for large datasets)
GET /api/v1/posts?cursor=eyJpZCI6MTAwfQ&limit=25

# Filtering
GET /api/v1/posts?status=published&author_id=7&created_after=2026-01-01

# Sorting
GET /api/v1/posts?sort=-created_at,title  # Descending date, ascending title

Cursor-based pagination is more efficient than offset-based for large datasets because it does not require counting all previous rows. The cursor is an opaque token (typically a base64-encoded ID or timestamp) that points to the last item of the previous page.

Rate Limiting

APIs implement rate limiting to prevent abuse and ensure fair usage. Rate limit information is typically communicated through response headers:

X-RateLimit-Limit: 1000          # Requests allowed per window
X-RateLimit-Remaining: 847       # Requests remaining
X-RateLimit-Reset: 1706889600    # Unix timestamp when limit resets
Retry-After: 60                  # Seconds to wait (when 429 returned)

REST vs GraphQL vs gRPC

REST is not the only API paradigm. Understanding the alternatives helps you choose the right tool:

  • REST — Resource-oriented, uses HTTP methods, widely understood. Best for public APIs, CRUD-heavy applications, and teams that value simplicity
  • GraphQL — Query language that lets clients request exactly the data they need. Solves over-fetching and under-fetching problems. Best for complex front ends with diverse data needs, especially mobile apps where bandwidth matters
  • gRPC — Binary protocol using Protocol Buffers for serialization. Extremely fast, supports bidirectional streaming. Best for microservice-to-microservice communication where performance is critical

For most web applications, REST remains the practical default. Its simplicity, tooling support, and universal understanding make it the lowest-friction choice. GraphQL adds value when your front end has complex, varied data requirements. gRPC shines in internal service meshes where human readability matters less than speed.

API Documentation

Well-documented APIs are usable APIs. The OpenAPI Specification (formerly Swagger) is the industry standard for describing REST APIs in a machine-readable format. Tools like Swagger UI, Redoc, and Stoplight generate interactive documentation from OpenAPI specs.

Frequently Asked Questions

What makes an API “RESTful” versus just using HTTP?

Many APIs labeled “REST” are technically “HTTP APIs” that do not follow all REST constraints. A truly RESTful API uses proper HTTP methods, meaningful status codes, stateless communication, and hypermedia links (HATEOAS). In practice, most production APIs follow REST conventions loosely — using HTTP methods and status codes correctly — without implementing full HATEOAS. This pragmatic approach works well for the vast majority of applications.

Should I use REST or GraphQL for my new project?

Start with REST unless you have a specific reason to choose GraphQL. REST is simpler to implement, easier to cache, and requires less tooling. If your front end makes multiple API calls to assemble a single view, or if you have multiple client types (web, mobile, TV) with different data needs, GraphQL becomes more attractive. Many teams start with REST and add a GraphQL layer later when the complexity justifies it.

How do I handle authentication for a single-page application?

Store the JWT token in memory (a JavaScript variable) for the session and use refresh tokens stored in HTTP-only cookies to obtain new access tokens when they expire. Avoid storing tokens in localStorage — it is accessible to any JavaScript on the page, making it vulnerable to XSS attacks. Short-lived access tokens (15 minutes) combined with secure refresh tokens provide a strong security balance for modern web applications.

What is the best way to handle API errors in a front-end application?

Create a centralized error handler that interprets status codes and formats user-friendly messages. Distinguish between client errors (4xx — show the user what to fix) and server errors (5xx — tell the user to try again later). Log detailed error information to your monitoring service while showing simplified messages in the UI. Retry failed requests with exponential backoff for transient network and server errors.

REST APIs form the communication backbone of modern web architecture. Paired with responsive front ends, solid performance optimization, and a capable development environment, well-designed REST APIs enable teams to build scalable applications where the front end and back end evolve independently. As Tim Berners-Lee built the web on the principle of linked resources identified by URLs, REST APIs extend that same principle to programmatic access.