keecode logokeecode
Beginner
rest api
restful api
rest api tutorial
what is rest
rest api explained
rest principles

Understanding REST APIs: A Complete Beginner's Guide

Learn what REST APIs are, how they work, and how to use them. Comprehensive guide covering REST principles, HTTP methods, endpoints, and practical examples.

Updated January 15, 2025

REST (Representational State Transfer) is the most popular architectural style for building web APIs. This comprehensive guide explains REST principles, how REST APIs work, and how to use them effectively.

Table of Contents

  1. What is REST?
  2. The 6 REST Principles
  3. REST API Components
  4. HTTP Methods in REST
  5. REST API Endpoints
  6. Request and Response Examples
  7. Best Practices

What is REST?

REST (Representational State Transfer) is an architectural style for designing networked applications. It relies on a stateless, client-server protocol - almost always HTTP.

Simple and Standardized: Uses HTTP methods everyone knows
Stateless: Each request is independent (easy to scale)
Cacheable: Responses can be cached for better performance
Works Everywhere: Any platform that supports HTTP can use it
Human-Readable: URLs and responses make sense

REST vs Other API Styles

styleprotocolformatcomplexityuse
RESTHTTPJSON/XMLSimpleMost web APIs
GraphQLHTTPJSONMediumComplex data needs
SOAPMultipleXMLComplexEnterprise systems
gRPCHTTP/2Protocol BuffersComplexMicroservices

The 6 REST Principles

REST APIs must follow these architectural constraints:

1. Client-Server Architecture

The client (frontend) and server (backend) are separate and independent.

CLIENT (Your App/Website)
    ↓ Request
    ↓
  API (REST)
    ↓
    ↓ Response
SERVER (Database/Logic)

Benefits:
- Frontend and backend can evolve independently
- Multiple clients can use the same API
- Easier to scale and maintain

Example:

// Client (React app)
function UserProfile() {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetch('https://api.example.com/users/123')  // REST API
      .then(res => res.json())
      .then(data => setUser(data));
  }, []);
  
  return <div>{user?.name}</div>;
}

// Server doesn't care if the client is React, Vue, or a mobile app!

2. Stateless

Each request from client to server must contain all information needed to understand and process the request. The server doesn't store any client context between requests.

BAD (Stateful):
Request 1: "Login as user123"
Server: "OK, I'll remember you're user123"
Request 2: "Get my profile"
Server: "I know you're user123, here's your profile"

GOOD (Stateless):
Request 1: "Login as user123"
Server: "Here's your token: abc123"
Request 2: "Get profile for user123" + token
Server: "Verified token, here's profile for user123"

Example:

// ✅ Stateless - Every request includes auth token
fetch('https://api.example.com/users/me', {
  headers: {
    'Authorization': 'Bearer abc123token'
  }
});

// ❌ Stateful - Relying on session stored on server
fetch('https://api.example.com/users/me');
// Server: "Who are you? I need to check my session storage"

Benefits:

  • Easy to scale (any server can handle any request)
  • More reliable (no session data to lose)
  • Simpler to debug

3. Cacheable

Responses must define themselves as cacheable or non-cacheable. This improves performance by reducing unnecessary requests.

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600  ← Cacheable for 1 hour

{
  "id": 123,
  "name": "John Doe"
}

Example:

// First request - Goes to server
fetch('https://api.example.com/users/123')
  .then(res => res.json())
  .then(data => console.log(data));

// Second request within cache time - Uses cached data (faster!)
fetch('https://api.example.com/users/123')
  .then(res => res.json())
  .then(data => console.log(data));  // Instant!

4. Uniform Interface

REST APIs should have a consistent, predictable structure. This is achieved through:

a) Resource Identification: Each resource has a unique URL

/users           ← All users
/users/123       ← Specific user
/users/123/posts ← That user's posts

b) Resource Manipulation: Use HTTP methods consistently

GET    /users/123  → Read user
POST   /users      → Create user
PUT    /users/123  → Update user
DELETE /users/123  → Delete user

c) Self-Descriptive Messages: Responses include everything needed

{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "links": {
    "self": "/users/123",
    "posts": "/users/123/posts"
  }
}

d) HATEOAS: Hypermedia as the Engine of Application State (advanced)

{
  "id": 123,
  "name": "John Doe",
  "_links": {
    "self": { "href": "/users/123" },
    "posts": { "href": "/users/123/posts" },
    "friends": { "href": "/users/123/friends" }
  }
}

5. Layered System

Client can't tell if it's connected directly to the end server or an intermediary (load balancer, cache, etc.).

CLIENT
  ↓
LOAD BALANCER
  ↓
CACHE SERVER
  ↓
API SERVER
  ↓
DATABASE

Client just sees: api.example.com
Doesn't know about all these layers!

6. Code on Demand (Optional)

Servers can extend client functionality by sending executable code (like JavaScript).

// Server sends JavaScript that client executes
// Rarely used in modern REST APIs

REST API Components

URLs (Uniform Resource Locators)

REST APIs use URLs to identify resources:

https://api.example.com/v1/users/123/posts?limit=10
└──┬──┘ └────┬────┘ └┬┘ └─┬──┘└┬┘└─┬─┘ └───┬───┘
Protocol  Domain    Ver Path  ID  Res   Query

Breakdown:

  • https:// - Protocol (always use HTTPS for security)
  • api.example.com - Domain
  • /v1 - API version
  • /users - Resource collection
  • /123 - Specific resource ID
  • /posts - Related sub-resource
  • ?limit=10 - Query parameter

HTTP Methods

REST uses standard HTTP methods (also called HTTP verbs):

MethodPurposeSafe?Idempotent?Example
GETRead/Retrieve✅ Yes✅ YesGet user data
POSTCreate❌ No❌ NoCreate new user
PUTUpdate/Replace❌ No✅ YesUpdate entire user
PATCHPartial Update❌ No❌ NoUpdate user email only
DELETERemove❌ No✅ YesDelete user
HEADGet Headers Only✅ Yes✅ YesCheck if resource exists
OPTIONSGet Available Methods✅ Yes✅ YesCORS preflight

Safe: Doesn't modify data
Idempotent: Same result if called multiple times

Learn more about HTTP methods →

Headers

Headers provide metadata about the request or response:

# Request Headers
GET /users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer abc123token
Content-Type: application/json
Accept: application/json
User-Agent: Mozilla/5.0

# Response Headers
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600
ETag: "abc123"
Content-Length: 256

Status Codes

REST APIs use HTTP status codes to indicate results:

Success (2xx):

  • 200 OK - Successful GET, PUT, PATCH, or DELETE
  • 201 Created - Successful POST
  • 204 No Content - Successful DELETE (no body)

Client Errors (4xx):

  • 400 Bad Request - Invalid syntax
  • 401 Unauthorized - Authentication required
  • 403 Forbidden - No permission
  • 404 Not Found - Resource doesn't exist
  • 422 Unprocessable Entity - Validation failed

Server Errors (5xx):

  • 500 Internal Server Error - Server crashed
  • 503 Service Unavailable - Server overloaded

View all HTTP status codes →

Request/Response Body

Data sent to or from the API, usually in JSON format:

// Request Body (POST/PUT/PATCH)
{
  "name": "John Doe",
  "email": "john@example.com",
  "age": 30
}

// Response Body
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "age": 30,
  "createdAt": "2025-01-15T10:30:00Z"
}

HTTP Methods in REST

GET - Retrieve Data

Get data from the server. Should never modify data.

// Get all users
fetch('https://api.example.com/users')
  .then(res => res.json())
  .then(users => console.log(users));

// Get specific user
fetch('https://api.example.com/users/123')
  .then(res => res.json())
  .then(user => console.log(user));

// Get with query parameters
fetch('https://api.example.com/users?role=admin&limit=10')
  .then(res => res.json())
  .then(users => console.log(users));

Response:

// GET /users/123
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com"
}

POST - Create New Resource

Create a new resource on the server.

// Create new user
fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer abc123token'
  },
  body: JSON.stringify({
    name: 'Jane Smith',
    email: 'jane@example.com',
    age: 28
  })
})
.then(res => res.json())
.then(newUser => console.log('Created:', newUser));

Response:

HTTP/1.1 201 Created
Location: /users/124
Content-Type: application/json

{
  "id": 124,
  "name": "Jane Smith",
  "email": "jane@example.com",
  "age": 28,
  "createdAt": "2025-01-15T10:30:00Z"
}

PUT - Update/Replace Resource

Replace entire resource with new data.

// Update user (replace all fields)
fetch('https://api.example.com/users/123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer abc123token'
  },
  body: JSON.stringify({
    name: 'John Updated',
    email: 'john.new@example.com',
    age: 31
  })
})
.then(res => res.json())
.then(user => console.log('Updated:', user));

PATCH - Partial Update

Update only specific fields of a resource.

// Update only email (keep name and age)
fetch('https://api.example.com/users/123', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer abc123token'
  },
  body: JSON.stringify({
    email: 'john.new@example.com'
  })
})
.then(res => res.json())
.then(user => console.log('Patched:', user));

DELETE - Remove Resource

Delete a resource from the server.

// Delete user
fetch('https://api.example.com/users/123', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer abc123token'
  }
})
.then(res => {
  if (res.status === 204) {
    console.log('User deleted successfully');
  }
});

Response:

HTTP/1.1 204 No Content
# No response body

REST API Endpoints

Resource Naming Conventions

Use nouns, not verbs:

✅ GOOD (nouns)
/users
/posts
/comments
/products

❌ BAD (verbs)
/getUsers
/createPost
/deleteComment
/fetchProducts

Use plural nouns:

✅ GOOD
/users           ← Collection
/users/123       ← Specific item

❌ BAD
/user
/user/123

Use hierarchical structure for relationships:

✅ GOOD
/users/123/posts           ← User's posts
/users/123/posts/456       ← Specific post by user
/users/123/posts/456/comments  ← Comments on that post

❌ BAD
/getUserPosts?userId=123
/getPostComments?postId=456

Common Endpoint Patterns

# Collection Operations
GET    /users              → List all users
POST   /users              → Create new user

# Single Resource Operations
GET    /users/123          → Get user 123
PUT    /users/123          → Update user 123 (full)
PATCH  /users/123          → Update user 123 (partial)
DELETE /users/123          → Delete user 123

# Sub-resource Operations
GET    /users/123/posts    → Get posts by user 123
POST   /users/123/posts    → Create post for user 123
GET    /users/123/posts/456 → Get post 456 by user 123

# Filtering and Pagination
GET    /users?role=admin                → Filter by role
GET    /users?page=2&limit=10          → Pagination
GET    /users?sort=name&order=asc      → Sorting
GET    /users?search=john              → Search
GET    /users?fields=id,name,email     → Partial response

Query Parameters

Use query strings for filtering, sorting, and pagination:

// Filtering
GET /products?category=electronics&minPrice=100&maxPrice=500

// Pagination
GET /users?page=2&limit=20

// Sorting
GET /users?sort=createdAt&order=desc

// Searching
GET /users?search=john&searchFields=name,email

// Field Selection
GET /users?fields=id,name,email

// Combined
GET /products?category=electronics&sort=price&order=asc&page=1&limit=20

Request and Response Examples

Example 1: User Management API

// ========================================
// GET /users - List all users
// ========================================

fetch('https://api.example.com/users?limit=2')
  .then(res => res.json())
  .then(console.log);

// Response:
{
  "data": [
    {
      "id": 1,
      "name": "John Doe",
      "email": "john@example.com"
    },
    {
      "id": 2,
      "name": "Jane Smith",
      "email": "jane@example.com"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 2,
    "total": 100,
    "totalPages": 50
  }
}

// ========================================
// GET /users/1 - Get specific user
// ========================================

fetch('https://api.example.com/users/1')
  .then(res => res.json())
  .then(console.log);

// Response:
{
  "id": 1,
  "name": "John Doe",
  "email": "john@example.com",
  "role": "admin",
  "createdAt": "2025-01-01T00:00:00Z",
  "updatedAt": "2025-01-15T10:30:00Z"
}

// ========================================
// POST /users - Create new user
// ========================================

fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your-token-here'
  },
  body: JSON.stringify({
    name: 'Bob Wilson',
    email: 'bob@example.com',
    password: 'securepass123'
  })
})
.then(res => res.json())
.then(console.log);

// Response (201 Created):
{
  "id": 3,
  "name": "Bob Wilson",
  "email": "bob@example.com",
  "role": "user",
  "createdAt": "2025-01-15T10:35:00Z"
}

// ========================================
// PUT /users/3 - Update user (full replacement)
// ========================================

fetch('https://api.example.com/users/3', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your-token-here'
  },
  body: JSON.stringify({
    name: 'Robert Wilson',
    email: 'robert@example.com',
    role: 'moderator'
  })
})
.then(res => res.json())
.then(console.log);

// Response (200 OK):
{
  "id": 3,
  "name": "Robert Wilson",
  "email": "robert@example.com",
  "role": "moderator",
  "updatedAt": "2025-01-15T10:40:00Z"
}

// ========================================
// PATCH /users/3 - Partial update
// ========================================

fetch('https://api.example.com/users/3', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your-token-here'
  },
  body: JSON.stringify({
    email: 'robert.new@example.com'
  })
})
.then(res => res.json())
.then(console.log);

// Response (200 OK):
{
  "id": 3,
  "name": "Robert Wilson",
  "email": "robert.new@example.com",
  "role": "moderator",
  "updatedAt": "2025-01-15T10:45:00Z"
}

// ========================================
// DELETE /users/3 - Delete user
// ========================================

fetch('https://api.example.com/users/3', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer your-token-here'
  }
})
.then(res => {
  if (res.status === 204) {
    console.log('User deleted successfully');
  }
});

// Response (204 No Content):
// Empty response body

Example 2: Blog API

// ========================================
// GET /posts - Get all posts
// ========================================

fetch('https://api.example.com/posts?status=published&sort=createdAt&order=desc')
  .then(res => res.json())
  .then(console.log);

// Response:
{
  "data": [
    {
      "id": 1,
      "title": "Getting Started with REST APIs",
      "slug": "getting-started-rest-apis",
      "excerpt": "Learn the basics of REST APIs...",
      "author": {
        "id": 1,
        "name": "John Doe"
      },
      "createdAt": "2025-01-15T10:00:00Z",
      "commentsCount": 5
    }
  ]
}

// ========================================
// GET /posts/1 - Get specific post
// ========================================

fetch('https://api.example.com/posts/1')
  .then(res => res.json())
  .then(console.log);

// Response:
{
  "id": 1,
  "title": "Getting Started with REST APIs",
  "slug": "getting-started-rest-apis",
  "content": "Full post content here...",
  "author": {
    "id": 1,
    "name": "John Doe",
    "avatar": "https://example.com/avatars/john.jpg"
  },
  "tags": ["api", "rest", "tutorial"],
  "createdAt": "2025-01-15T10:00:00Z",
  "updatedAt": "2025-01-15T10:30:00Z"
}

// ========================================
// GET /posts/1/comments - Get post comments
// ========================================

fetch('https://api.example.com/posts/1/comments')
  .then(res => res.json())
  .then(console.log);

// Response:
{
  "data": [
    {
      "id": 1,
      "author": "Alice",
      "content": "Great article!",
      "createdAt": "2025-01-15T11:00:00Z"
    },
    {
      "id": 2,
      "author": "Bob",
      "content": "Very helpful, thanks!",
      "createdAt": "2025-01-15T12:00:00Z"
    }
  ]
}

// ========================================
// POST /posts/1/comments - Add comment
// ========================================

fetch('https://api.example.com/posts/1/comments', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your-token-here'
  },
  body: JSON.stringify({
    content: 'This is my comment!'
  })
})
.then(res => res.json())
.then(console.log);

// Response (201 Created):
{
  "id": 3,
  "author": {
    "id": 2,
    "name": "Current User"
  },
  "content": "This is my comment!",
  "createdAt": "2025-01-15T13:00:00Z"
}

Best Practices

1. Version Your API

Always include API version in the URL:

✅ GOOD
https://api.example.com/v1/users
https://api.example.com/v2/users

❌ BAD
https://api.example.com/users  ← No version

Why? You can update v2 without breaking v1 users

2. Use HTTPS Always

✅ GOOD
https://api.example.com

❌ BAD
http://api.example.com  ← Insecure!

HTTPS encrypts data in transit

3. Return Appropriate Status Codes

// ✅ GOOD - Descriptive status codes
if (user) {
  res.status(200).json(user);  // Found
} else {
  res.status(404).json({ error: 'User not found' });  // Not found
}

// ❌ BAD - Always 200
res.status(200).json({ error: 'User not found' });  // Wrong!

4. Use Consistent Naming

// ✅ GOOD - Consistent camelCase
{
  "userId": 123,
  "firstName": "John",
  "createdAt": "2025-01-15"
}

// ❌ BAD - Mixed conventions
{
  "user_id": 123,
  "FirstName": "John",
  "created-at": "2025-01-15"
}

5. Provide Helpful Error Messages

// ✅ GOOD - Descriptive error
{
  "error": {
    "code": "INVALID_EMAIL",
    "message": "Email address is not valid",
    "field": "email",
    "value": "not-an-email"
  }
}

// ❌ BAD - Vague error
{
  "error": "Invalid input"
}

6. Document Your API

Provide clear documentation with examples:

# GET /users/:id

Get a specific user by ID.

## Parameters
- \`id\` (required): User ID

## Response (200 OK)
\`\`\`json
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com"
}
\`\`\`

## Errors
- \`404\`: User not found
- \`401\`: Unauthorized

7. Implement Rate Limiting

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1610000000

# When limit exceeded:
HTTP/1.1 429 Too Many Requests
Retry-After: 3600

8. Support Pagination

// ✅ GOOD - Paginated response
GET /users?page=2&limit=20

{
  "data": [ /* 20 users */ ],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 1000,
    "totalPages": 50,
    "nextPage": "/users?page=3&limit=20",
    "prevPage": "/users?page=1&limit=20"
  }
}

Summary

REST APIs are the foundation of modern web services. By following REST principles and best practices, you can build APIs that are easy to use, maintain, and scale.

Key Takeaways:

✅ REST uses HTTP methods semantically (GET, POST, PUT, DELETE)
✅ Resources are identified by URLs
✅ Each request is stateless
✅ Use proper HTTP status codes
✅ Return JSON for data interchange
✅ Version your API
✅ Always use HTTPS
✅ Provide clear documentation