From Zero to AI

Lesson 4.2: HTTP Methods

Duration: 60 minutes

Learning Objectives

By the end of this lesson, you will be able to:

  1. Explain the purpose of each HTTP method (GET, POST, PUT, PATCH, DELETE)
  2. Understand the difference between safe and unsafe methods
  3. Understand idempotent vs non-idempotent operations
  4. Choose the correct HTTP method for different operations
  5. Map CRUD operations to HTTP methods

Introduction

HTTP methods (also called HTTP verbs) tell the server what action you want to perform. Just like verbs in language describe actions (run, eat, sleep), HTTP methods describe what you want to do with a resource (get, create, update, delete).

Think of a library:

  • GET: "Show me this book"
  • POST: "Add this new book to the collection"
  • PUT: "Replace this book with a new edition"
  • PATCH: "Fix a typo on page 42"
  • DELETE: "Remove this book from the collection"

The Main HTTP Methods

GET - Retrieve Data

GET requests data from a server. It should only retrieve data and never modify it.

// Get all users
const response = await fetch('https://api.example.com/users');
const users = await response.json();

// Get a specific user
const userResponse = await fetch('https://api.example.com/users/1');
const user = await userResponse.json();

// Get with query parameters (filtering)
const activeUsers = await fetch('https://api.example.com/users?status=active');

Characteristics of GET:

  • Does not have a request body
  • Parameters are passed via URL (path or query string)
  • Should be safe (no side effects)
  • Should be idempotent (same request = same result)
  • Responses can be cached

When to use GET:

  • Fetching a list of resources
  • Fetching a single resource by ID
  • Searching or filtering data
  • Any read-only operation

POST - Create New Data

POST sends data to the server to create a new resource.

// Create a new user
const response = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'John Doe',
    email: 'john@example.com',
  }),
});

const newUser = await response.json();
console.log(newUser.id); // Server assigns an ID

Characteristics of POST:

  • Has a request body with data
  • Server typically returns the created resource with its new ID
  • Returns status code 201 (Created) on success
  • Not idempotent (calling twice creates two resources)
  • Not safe (creates side effects)

When to use POST:

  • Creating new resources
  • Submitting form data
  • Uploading files
  • Operations that do not fit other methods

PUT - Replace Data

PUT replaces an entire resource with new data. If the resource does not exist, it may create it.

// Replace user 1 entirely
const response = await fetch('https://api.example.com/users/1', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'John Smith',
    email: 'johnsmith@example.com',
    role: 'admin',
  }),
});

const updatedUser = await response.json();

Characteristics of PUT:

  • Has a request body with complete resource data
  • Replaces the entire resource
  • Idempotent (same PUT = same result)
  • Returns 200 (OK) or 204 (No Content) on success
  • May return 201 (Created) if resource did not exist

When to use PUT:

  • Replacing a resource entirely
  • When you have all fields of the resource
  • Updating with a complete new version

PATCH - Partial Update

PATCH applies partial modifications to a resource. You only send the fields you want to change.

// Update only the email of user 1
const response = await fetch('https://api.example.com/users/1', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email: 'newemail@example.com',
  }),
});

const updatedUser = await response.json();

Characteristics of PATCH:

  • Has a request body with partial data
  • Only updates specified fields
  • Other fields remain unchanged
  • Returns 200 (OK) on success

When to use PATCH:

  • Updating one or few fields
  • When you do not have the complete resource
  • Partial modifications

DELETE - Remove Data

DELETE removes a resource from the server.

// Delete user 1
const response = await fetch('https://api.example.com/users/1', {
  method: 'DELETE',
});

if (response.ok) {
  console.log('User deleted successfully');
}

Characteristics of DELETE:

  • Usually has no request body
  • Idempotent (deleting twice has same effect as once)
  • Returns 200 (OK), 204 (No Content), or 202 (Accepted)
  • The resource should no longer exist after successful deletion

When to use DELETE:

  • Removing a resource
  • Canceling a subscription
  • Revoking access

PUT vs PATCH: Understanding the Difference

This is a common source of confusion. Let us clarify with an example.

Consider this user resource:

{
  "id": 1,
  "name": "John Doe",
  "email": "john@example.com",
  "role": "user",
  "active": true
}

Using PUT (Full Replacement)

// PUT replaces the ENTIRE resource
// You must send ALL fields
await fetch('/users/1', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'John Doe',
    email: 'john.doe@newmail.com', // Changed
    role: 'user',
    active: true,
    // Missing fields would be removed or set to defaults!
  }),
});

If you forget a field with PUT, it may be deleted or set to null.

Using PATCH (Partial Update)

// PATCH updates only specified fields
// Other fields remain unchanged
await fetch('/users/1', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    email: 'john.doe@newmail.com', // Only this changes
  }),
});

The name, role, and active fields remain unchanged.

Quick Decision Guide

Need to update one or few fields? → PATCH
Have the complete new resource?   → PUT
Not sure?                         → PATCH is usually safer

Safe and Idempotent Methods

Safe Methods

A method is safe if it does not modify the resource. It only reads data.

Method Safe?
GET Yes
POST No
PUT No
PATCH No
DELETE No

Safe methods can be called repeatedly without worry.

Idempotent Methods

A method is idempotent if calling it multiple times produces the same result as calling it once.

Method Idempotent?
GET Yes
POST No
PUT Yes
PATCH Depends
DELETE Yes

Examples:

// GET is idempotent
// Calling 10 times returns the same user
GET /users/1
GET /users/1
GET /users/1

// POST is NOT idempotent
// Calling 3 times creates 3 users!
POST /users { name: "John" }
POST /users { name: "John" }
POST /users { name: "John" }

// PUT is idempotent
// Calling 10 times results in the same final state
PUT /users/1 { name: "John", email: "john@example.com" }
PUT /users/1 { name: "John", email: "john@example.com" }

// DELETE is idempotent
// After first call, user is gone. Next calls change nothing.
DELETE /users/1
DELETE /users/1  // No change - already deleted

CRUD to HTTP Mapping

CRUD (Create, Read, Update, Delete) maps to HTTP methods:

CRUD HTTP Example
Create POST POST /users
Read GET GET /users, GET /users/1
Update PUT/PATCH PUT /users/1, PATCH /users/1
Delete DELETE DELETE /users/1

Real-World Examples

User Management API

interface User {
  id: number;
  name: string;
  email: string;
  role: 'user' | 'admin';
}

// CREATE - Add new user
async function createUser(userData: Omit<User, 'id'>): Promise<User> {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(userData),
  });
  return response.json();
}

// READ - Get all users
async function getUsers(): Promise<User[]> {
  const response = await fetch('https://api.example.com/users');
  return response.json();
}

// READ - Get single user
async function getUser(id: number): Promise<User> {
  const response = await fetch(`https://api.example.com/users/${id}`);
  return response.json();
}

// UPDATE - Full replacement
async function replaceUser(id: number, userData: Omit<User, 'id'>): Promise<User> {
  const response = await fetch(`https://api.example.com/users/${id}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(userData),
  });
  return response.json();
}

// UPDATE - Partial update
async function updateUser(id: number, updates: Partial<User>): Promise<User> {
  const response = await fetch(`https://api.example.com/users/${id}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(updates),
  });
  return response.json();
}

// DELETE - Remove user
async function deleteUser(id: number): Promise<void> {
  await fetch(`https://api.example.com/users/${id}`, {
    method: 'DELETE',
  });
}

Blog Post API

interface Post {
  id: number;
  title: string;
  content: string;
  authorId: number;
  published: boolean;
}

const API_BASE = 'https://api.example.com';

// Create a draft post
async function createDraft(title: string, content: string, authorId: number): Promise<Post> {
  const response = await fetch(`${API_BASE}/posts`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      title,
      content,
      authorId,
      published: false,
    }),
  });
  return response.json();
}

// Publish a post (partial update)
async function publishPost(postId: number): Promise<Post> {
  const response = await fetch(`${API_BASE}/posts/${postId}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      published: true,
    }),
  });
  return response.json();
}

// Get all published posts
async function getPublishedPosts(): Promise<Post[]> {
  const response = await fetch(`${API_BASE}/posts?published=true`);
  return response.json();
}

Less Common HTTP Methods

Same as GET but returns only headers, no body. Useful for checking if a resource exists or getting metadata.

const response = await fetch('https://api.example.com/files/large.zip', {
  method: 'HEAD',
});

const fileSize = response.headers.get('Content-Length');
console.log(`File size: ${fileSize} bytes`);

OPTIONS

Returns the HTTP methods supported by a URL. Used in CORS preflight requests.

const response = await fetch('https://api.example.com/users', {
  method: 'OPTIONS',
});

const allowedMethods = response.headers.get('Allow');
console.log(`Allowed methods: ${allowedMethods}`);
// Output: "GET, POST, PUT, DELETE, OPTIONS"

Common Mistakes

Mistake 1: Using GET for Modifications

// WRONG - GET should never modify data
fetch('/users/1/delete'); // This is a GET request!

// CORRECT - Use DELETE for deletions
fetch('/users/1', { method: 'DELETE' });

Mistake 2: Using POST for Everything

// WRONG - Using POST for retrieval
fetch('/users/search', {
  method: 'POST',
  body: JSON.stringify({ name: 'John' }),
});

// CORRECT - Use GET with query parameters
fetch('/users?name=John');

Mistake 3: Forgetting Content-Type Header

// WRONG - Missing Content-Type
fetch('/users', {
  method: 'POST',
  body: JSON.stringify({ name: 'John' }), // Server may not parse this!
});

// CORRECT - Include Content-Type
fetch('/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'John' }),
});

Exercises

Exercise 1: Choose the Right Method

What HTTP method would you use for each scenario?

  1. View your profile information
  2. Register a new account
  3. Change your password
  4. Delete your account
  5. Update your profile picture (only the picture, not other fields)
  6. Check if a username is available
Solution
  1. GET - Reading data
  2. POST - Creating a new resource
  3. PATCH - Updating a single field
  4. DELETE - Removing a resource
  5. PATCH - Partial update
  6. GET or HEAD - Checking existence

Exercise 2: Identify the Problem

What is wrong with each API call?

// 1
fetch('/posts/1/update', {
  method: 'GET',
  body: JSON.stringify({ title: 'New Title' }),
});

// 2
fetch('/users', {
  method: 'DELETE',
});

// 3
fetch('/posts', {
  method: 'PUT',
  body: JSON.stringify({ title: 'Hello' }),
});
Solution
  1. GET should not have a body and should not modify data. Use PATCH or PUT.
  2. Deleting a collection is dangerous. Should specify an ID: /users/1
  3. PUT is for updating existing resources, not creating. Should use POST.

Exercise 3: Build a Task API

Define the HTTP methods and endpoints for a task management API that supports:

  • Viewing all tasks
  • Viewing a single task
  • Creating a task
  • Marking a task as complete
  • Deleting a task
Solution
// View all tasks
GET /tasks

// View single task
GET /tasks/:id

// Create a task
POST /tasks
Body: { title: "Buy groceries", completed: false }

// Mark as complete (partial update)
PATCH /tasks/:id
Body: { completed: true }

// Delete a task
DELETE /tasks/:id

Summary Table

Method Purpose Has Body Safe Idempotent
GET Retrieve No Yes Yes
POST Create Yes No No
PUT Replace Yes No Yes
PATCH Update Yes No Usually
DELETE Remove Usually No No Yes

Key Takeaways

  1. GET retrieves data without modifying anything
  2. POST creates new resources
  3. PUT replaces entire resources
  4. PATCH updates specific fields
  5. DELETE removes resources
  6. Safe methods do not modify data (only GET)
  7. Idempotent methods produce same result when called multiple times
  8. Always include Content-Type: application/json when sending JSON data
  9. Match your HTTP method to your intention - do not use GET for modifications

Resources

Resource Type Level
MDN: HTTP Methods Documentation Beginner
HTTP Methods Explained Tutorial Beginner
PUT vs PATCH Article Intermediate
RFC 7231 - HTTP Semantics Specification Advanced

Next Lesson

Now that you understand HTTP methods, let us learn about request headers, query parameters, and request bodies in detail.

Continue to Lesson 4.3: Headers and Body