Complete Guide to REST APIs

REST (Representational State Transfer) is the most popular architectural style for building web APIs. This guide covers everything you need to know about RESTful APIs.

What is a REST API? #

A REST API is an application programming interface that follows REST architectural principles. It allows different software systems to communicate over HTTP using standard methods.

Key Principles of REST #

1. Stateless Communication #

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

2. Client-Server Architecture #

The client and server are separate entities that communicate over HTTP. This separation allows them to evolve independently.

3. Uniform Interface #

REST APIs use standard HTTP methods and follow consistent naming conventions for resources.

HTTP Methods #

REST APIs use standard HTTP methods to perform operations:

GET - Retrieve Data #

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

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

POST - Create Data #

fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'John Doe',
    email: 'john@example.com'
  })
})
.then(response => response.json())
.then(data => console.log(data));

PUT - Update Data (Complete Replacement) #

fetch('https://api.example.com/users/123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'John Smith',
    email: 'johnsmith@example.com'
  })
})
.then(response => response.json())
.then(data => console.log(data));

PATCH - Update Data (Partial Update) #

fetch('https://api.example.com/users/123', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email: 'newemail@example.com'
  })
})
.then(response => response.json())
.then(data => console.log(data));

DELETE - Remove Data #

fetch('https://api.example.com/users/123', {
  method: 'DELETE'
})
.then(response => {
  if (response.ok) {
    console.log('User deleted successfully');
  }
});

REST API URL Structure #

Good REST API URLs are hierarchical and represent resources:

GET    /users              # Get all users
GET    /users/123          # Get user with ID 123
POST   /users              # Create new user
PUT    /users/123          # Update user 123
DELETE /users/123          # Delete user 123

GET    /users/123/posts    # Get all posts by user 123
GET    /posts/456          # Get post with ID 456
POST   /users/123/posts    # Create new post for user 123

HTTP Status Codes #

REST APIs use standard HTTP status codes to indicate success or failure:

2xx Success

  • 200 OK - Request succeeded
  • 201 Created - Resource created successfully
  • 204 No Content - Request succeeded, no content to return

4xx Client Errors

  • 400 Bad Request - Invalid request format
  • 401 Unauthorized - Authentication required
  • 403 Forbidden - Authenticated but not authorized
  • 404 Not Found - Resource doesn’t exist

5xx Server Errors

  • 500 Internal Server Error - Server encountered an error
  • 503 Service Unavailable - Server temporarily unavailable

Request and Response Format #

REST APIs typically use JSON for data exchange:

Request:

{
  "name": "Jane Doe",
  "email": "jane@example.com",
  "role": "developer"
}

Response:

{
  "id": 124,
  "name": "Jane Doe",
  "email": "jane@example.com",
  "role": "developer",
  "createdAt": "2025-03-15T14:23:17Z"
}

Building a Simple REST API with Express #

Here’s a basic REST API server using Node.js and Express:

const express = require('express');
const app = express();

app.use(express.json());

let users = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' }
];

// GET all users
app.get('/users', (req, res) => {
  res.json(users);
});

// GET user by ID
app.get('/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  if (!user) return res.status(404).json({ error: 'User not found' });
  res.json(user);
});

// POST create user
app.post('/users', (req, res) => {
  const newUser = {
    id: users.length + 1,
    name: req.body.name,
    email: req.body.email
  };
  users.push(newUser);
  res.status(201).json(newUser);
});

// PUT update user
app.put('/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  if (!user) return res.status(404).json({ error: 'User not found' });

  user.name = req.body.name;
  user.email = req.body.email;
  res.json(user);
});

// DELETE user
app.delete('/users/:id', (req, res) => {
  const index = users.findIndex(u => u.id === parseInt(req.params.id));
  if (index === -1) return res.status(404).json({ error: 'User not found' });

  users.splice(index, 1);
  res.status(204).send();
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Best Practices #

  1. Use Nouns for Resources - /users not /getUsers
  2. Use Plural Names - /users not /user
  3. Use HTTP Methods Correctly - Don’t use GET for actions that modify data
  4. Return Appropriate Status Codes - Help clients understand the result
  5. Version Your API - /v1/users allows for future changes
  6. Use Query Parameters for Filtering - /users?role=admin&status=active
  7. Implement Pagination - /users?page=2&limit=20
  8. Provide Clear Error Messages - Include helpful error details in responses

Common Pitfalls to Avoid #

  • Using verbs in URLs (/createUser instead of POST /users)
  • Not handling errors properly
  • Storing state on the server
  • Inconsistent naming conventions
  • Not validating input data
  • Missing authentication and authorization

REST APIs are the backbone of modern web development. Understanding these principles will help you build scalable and maintainable APIs.