Every time your browser loads a page, submits a form, or fetches data from an API — it's sending an HTTP request with a specific method attached to it. GET, POST, PUT, PATCH, DELETE — these aren't just random words. They tell the server exactly what you want to do: read something, create something, update something, or remove something.
If you've ever wondered why your form data disappears on page refresh (hint: GET vs POST), why your API returns 405 Method Not Allowed, or whether to use PUT or PATCH for updates — this guide covers all of it. We'll go through each method with real code examples, a comparison table, and practical guidance on when to use which.
What Are HTTP Methods?
HTTP methods (sometimes called HTTP verbs) are the action part of an HTTP request. When your client sends a request to a server, it includes a method that tells the server what operation to perform on the resource at that URL.
Think of it like this:
GET /users/42 → "Give me user 42" POST /users → "Create a new user" PUT /users/42 → "Replace user 42 with this data" PATCH /users/42 → "Update these specific fields on user 42" DELETE /users/42 → "Delete user 42"
The URL identifies which resource. The method identifies what to do with it. That's the entire foundation of REST APIs — resources + verbs.
Each HTTP Method Explained
Let's go through each method in detail. For each one, I'll explain what it does, why it exists, when to use it, and whether it's idempotent.
• GET
What: Retrieves a resource from the server. The most common HTTP method — every page load, every image, every API fetch starts with GET.
Why: You need to read data without changing anything. GET is the "look but don't touch" method.
When to use: Fetching user profiles, loading product lists, reading blog posts, downloading files, any read-only operation.
Idempotent? Yes — calling GET 100 times returns the same result and never changes server state.
// Fetch API
const response = await fetch("https://api.example.com/users/42");
const user = await response.json();
// Axios
const { data } = await axios.get("/users/42");
// cURL
// curl -X GET https://api.example.com/users/42• POST
What: Sends data to the server to create a new resource. The server decides where to put it and usually returns the new resource with an assigned ID.
Why: You need to create something new — a user, an order, a comment. POST says "here's some data, please create a resource from it."
When to use: User registration, submitting forms, creating orders, uploading files, any operation that creates new data.
Idempotent? No — calling POST twice creates two resources. That's why you see duplicate orders when users double-click submit buttons.
// Fetch API
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice", email: "alice@example.com" })
});
const newUser = await response.json(); // { id: 43, name: "Alice", ... }
// Axios
const { data } = await axios.post("/users", { name: "Alice", email: "alice@example.com" });
// cURL
// curl -X POST https://api.example.com/users \
// -H "Content-Type: application/json" \
// -d '{"name":"Alice","email":"alice@example.com"}'• PUT
What: Replaces an entire resource at a specific URL. You send the complete object, and the server swaps out whatever was there before.
Why: You have the full updated version of a resource and want to replace it entirely. Think of PUT as "here's the new version — swap it in."
When to use: Updating a user profile (full form), replacing a configuration object, overwriting a document. Use when you're sending ALL fields.
Idempotent? Yes — calling PUT with the same data multiple times produces the same result. The resource ends up in the same state regardless.
// Fetch API
const response = await fetch("https://api.example.com/users/42", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice Smith", email: "alice@new.com", role: "admin" })
});
// Axios
const { data } = await axios.put("/users/42", {
name: "Alice Smith", email: "alice@new.com", role: "admin"
});
// cURL
// curl -X PUT https://api.example.com/users/42 \
// -H "Content-Type: application/json" \
// -d '{"name":"Alice Smith","email":"alice@new.com","role":"admin"}'• PATCH
What: Partially updates a resource. You only send the fields you want to change — everything else stays the same.
Why: You want to change one field without sending the entire object. Updating just a user's email shouldn't require re-sending their name, address, and avatar.
When to use: Toggling a status flag, updating a single field, making small changes to large objects. More efficient than PUT for partial updates.
Idempotent? Not guaranteed. It depends on the implementation — PATCH with { "email": "new@test.com" } is idempotent, but PATCH with { "views": "increment" } is not.
// Fetch API — only updating email
const response = await fetch("https://api.example.com/users/42", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "alice@updated.com" })
});
// Axios
const { data } = await axios.patch("/users/42", { email: "alice@updated.com" });
// cURL
// curl -X PATCH https://api.example.com/users/42 \
// -H "Content-Type: application/json" \
// -d '{"email":"alice@updated.com"}'• DELETE
What: Removes a resource from the server. After DELETE, the resource should no longer exist at that URL.
Why: You want to permanently remove data — a user account, a file, a comment, a cart item.
When to use: Deleting accounts, removing items from a list, canceling subscriptions, any destructive operation.
Idempotent? Yes — deleting something twice has the same end result: the resource doesn't exist. First call returns 200/204, subsequent calls return 404.
// Fetch API
const response = await fetch("https://api.example.com/users/42", {
method: "DELETE"
});
// Usually returns 204 No Content
// Axios
await axios.delete("/users/42");
// cURL
// curl -X DELETE https://api.example.com/users/42• HEAD
What: Identical to GET but returns only response headers — no body. Like asking "does this exist and how big is it?" without downloading the actual content.
Why: Check if a resource exists, get its size (Content-Length), or check cache headers — all without transferring the full response body.
When to use: Link checking (is this URL alive?), checking file size before downloading, validating cache freshness, health checks.
Idempotent? Yes — same as GET, no side effects.
// Fetch API
const response = await fetch("https://api.example.com/files/report.pdf", {
method: "HEAD"
});
console.log(response.headers.get("Content-Length")); // "2048576"
console.log(response.status); // 200 = exists, 404 = not found
// cURL (curl -I is shorthand for HEAD)
// curl -I https://api.example.com/files/report.pdf• OPTIONS
What: Asks the server what methods and headers are allowed for a given URL. Returns supported methods in the Allow header.
Why: Used by browsers for CORS preflight requests — before sending a cross-origin POST/PUT/DELETE, the browser sends OPTIONS first to check if it's permitted.
When to use: You rarely send OPTIONS manually. Browsers do it automatically for CORS. But it's useful for API discovery and documentation.
Idempotent? Yes — purely informational, no side effects.
// Fetch API
const response = await fetch("https://api.example.com/users", {
method: "OPTIONS"
});
console.log(response.headers.get("Allow")); // "GET, POST, OPTIONS"
console.log(response.headers.get("Access-Control-Allow-Methods")); // CORS methods
// cURL
// curl -X OPTIONS https://api.example.com/users -iHTTP Methods Comparison Table
Here's a side-by-side comparison of the five main HTTP methods. Bookmark this — it answers 90% of "which method should I use?" questions.
| Property | GET | POST | PUT | PATCH | DELETE |
|---|---|---|---|---|---|
| Idempotent | ✅ Yes | ❌ No | ✅ Yes | ⚠️ Not guaranteed | ✅ Yes |
| Safe | ✅ Yes | ❌ No | ❌ No | ❌ No | ❌ No |
| Request body | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes | Optional |
| Cacheable | ✅ Yes | ❌ Rarely | ❌ No | ❌ No | ❌ No |
| Use case | Read data | Create new | Replace entire | Update partial | Remove |
| Typical response | 200 + data | 201 Created | 200 / 204 | 200 + updated | 204 No Content |
| URL pattern | /resources or /resources/:id | /resources | /resources/:id | /resources/:id | /resources/:id |
Safe means the method doesn't modify server state. Only GET, HEAD, and OPTIONS are safe.Idempotent means calling it multiple times produces the same result as calling it once.
When to Use Which Method (Decision Guide)
Still unsure which method to pick? Here's a decision tree that works for 95% of REST API scenarios:
📖 "I need to read/fetch data"
→ Use GET. Always. Pass filters via query params (?status=active&page=2).
➕ "I need to create something new"
→ Use POST. Send data in the body. Server assigns the ID and returns the created resource with 201.
🔄 "I need to update an existing resource (full replacement)"
→ Use PUT. Send the complete object. Any field you omit gets set to null/default. Think "overwrite."
✏️ "I need to update just a few fields"
→ Use PATCH. Only send the fields that changed. The rest remain untouched.
🗑️ "I need to remove something"
→ Use DELETE. Return 204 on success. Consider soft-delete (mark as deleted) for audit trails.
🔍 "I need to check if something exists without downloading it"
→ Use HEAD. Same as GET but without the response body — just headers and status code.
🤔 "I'm not sure what methods are supported"
→ Use OPTIONS. Check the Allow header in the response. Browsers do this automatically for CORS.
Common Mistakes Developers Make
I've reviewed dozens of APIs and these same mistakes keep showing up. Avoid them and your API will be significantly more predictable and easier to consume.
⚠️ Using GET for actions that modify data
Never use GET /users/42/delete or GET /cart/clear. GET must be safe — crawlers, prefetchers, and browser preloading will trigger those URLs. Use DELETE or POST for destructive operations.
⚠️ Using POST for everything
"Just POST it" is lazy API design. Clients can't cache POST responses, proxies can't optimize them, and other developers can't predict your API behavior. Use the right method for the operation.
⚠️ Using PUT when you mean PATCH
PUT means "replace everything." If you send PUT with only one field, a strict implementation should null out all other fields. If you only want to update one field, use PATCH.
⚠️ Not handling duplicate POST submissions
POST isn't idempotent. Users double-click, networks retry, browsers resend on refresh. Implement idempotency keys or deduplication logic to prevent duplicate resource creation.
⚠️ Putting sensitive data in GET query parameters
GET parameters end up in browser history, server logs, referrer headers, and CDN logs. Never put passwords, tokens, or PII in query strings. Use POST with a body, or send credentials in headers.
⚠️ Returning wrong status codes
POST should return 201 (Created), not 200. DELETE should return 204 (No Content), not 200 with an empty body. PUT/PATCH should return 200 with the updated resource or 204 if no body. Wrong codes confuse clients and break expectations.
⚠️ Not supporting OPTIONS for CORS
If your API serves browsers, you need to handle OPTIONS preflight requests. Many frameworks don't do this automatically. Missing OPTIONS handling = "CORS error" in the browser console, even when your other endpoints work fine.
⚠️ Ignoring method semantics in middleware
Rate limiters, caches, and loggers should treat methods differently. GET requests can be cached aggressively, POST cannot. Rate limiting should be stricter on write methods (POST, PUT, DELETE) than read methods (GET).
Best Practices for HTTP Methods
Follow these and your API will be consistent, predictable, and easy for other developers to integrate with.
1. Use nouns for URLs, verbs for methods
Good: DELETE /users/42. Bad: POST /deleteUser. The method IS the verb. Your URL should identify the resource, not the action.
2. Return appropriate status codes
GET → 200, POST → 201 + Location header, PUT/PATCH → 200 or 204, DELETE → 204. Use 404 for resources that don't exist, 405 for unsupported methods, 409 for conflicts.
3. Make PUT truly replace the resource
If your PUT endpoint only updates provided fields (like PATCH), you're confusing clients. PUT should require the full representation. If a field is missing, set it to null or default.
4. Implement idempotency keys for POST
Accept an Idempotency-Key header on POST requests. If the client sends the same key twice, return the original response instead of creating a duplicate. Stripe does this — it works.
5. Return the created/updated resource
After POST, return the full resource (with server-generated fields like id, createdAt). After PUT/PATCH, return the updated resource. This saves clients from making a follow-up GET.
6. Use PATCH with JSON Merge Patch or JSON Patch
For simple partial updates, use JSON Merge Patch (Content-Type: application/merge-patch+json). For complex operations (add to array, remove field), use JSON Patch (Content-Type: application/json-patch+json).
7. Support HEAD on all GET endpoints
If you support GET, support HEAD on the same URL. Most frameworks handle this automatically. It costs nothing and helps clients check resource availability efficiently.
8. Consider soft-delete over hard DELETE
Instead of permanently removing data, add a deletedAt timestamp. Still use the DELETE method — but mark the record as deleted rather than dropping the row. Helps with audit trails and recovery.
9. Validate Content-Type on write methods
POST, PUT, and PATCH should check that Content-Type matches what your API expects (usually application/json). Return 415 Unsupported Media Type if the client sends something unexpected.
10. Document allowed methods per endpoint
Use OpenAPI/Swagger to document which methods each endpoint supports. Return 405 Method Not Allowed (with an Allow header listing valid methods) for unsupported methods.
Real-World REST API Example
Here's how a well-designed REST API uses all five methods on a single resource. This is the standard pattern you'll see in most professional APIs:
// A typical "users" resource in a REST API
// List all users (with pagination)
GET /api/users?page=1&limit=20
→ 200 OK + [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
// Get a single user
GET /api/users/42
→ 200 OK + { id: 42, name: "Alice", email: "alice@example.com", role: "admin" }
// Create a new user
POST /api/users
Body: { "name": "Charlie", "email": "charlie@example.com" }
→ 201 Created + { id: 43, name: "Charlie", ... }
Location: /api/users/43
// Replace a user completely
PUT /api/users/42
Body: { "name": "Alice Smith", "email": "alice@new.com", "role": "admin" }
→ 200 OK + { id: 42, name: "Alice Smith", email: "alice@new.com", role: "admin" }
// Update just the email
PATCH /api/users/42
Body: { "email": "alice@updated.com" }
→ 200 OK + { id: 42, name: "Alice Smith", email: "alice@updated.com", role: "admin" }
// Delete a user
DELETE /api/users/42
→ 204 No Content
// Check if a user exists (without downloading data)
HEAD /api/users/42
→ 200 OK (user exists) or 404 Not Found
// Check which methods are supported
OPTIONS /api/users
→ 204 No Content
Allow: GET, POST, OPTIONS
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETEPUT vs PATCH — The Difference That Confuses Everyone
This is the most common question developers ask about HTTP methods. Let me make it crystal clear with a concrete example:
// Current user in database:
{
"id": 42,
"name": "Alice",
"email": "alice@old.com",
"role": "editor",
"avatar": "https://cdn.example.com/alice.jpg"
}
// PUT /users/42 — sends COMPLETE replacement
// If you omit a field, it gets removed or set to null
{
"name": "Alice",
"email": "alice@new.com",
"role": "editor",
"avatar": "https://cdn.example.com/alice.jpg"
}
// Result: All fields are set to what you sent. Nothing more.
// PATCH /users/42 — sends ONLY what changed
{
"email": "alice@new.com"
}
// Result: Only email changes. name, role, avatar stay the same.Rule of thumb: If your form has all fields and the user can edit any of them, use PUT. If you're updating a single toggle, status, or field — use PATCH. Most real-world update operations are PATCH, not PUT.
POST vs PUT — When to Create with Which
Both POST and PUT can create resources, but there's a key difference in who controls the URL:
| Scenario | Method | Example |
|---|---|---|
| Server assigns ID | POST | POST /users → server creates user with id: 43 |
| Client knows the ID | PUT | PUT /users/my-custom-id → creates at that exact URL |
| Create if not exists | PUT | PUT /config/theme → creates or replaces config |
| Always creates new | POST | POST /orders → always creates a new order |
In practice: use POST 90% of the time for creation. PUT-as-create is mostly used for idempotent upsert operations (like setting a user's preferences at a known key).
Frequently Asked Questions
What are HTTP methods?
What is the difference between PUT and PATCH?
Is GET idempotent?
Can GET requests have a body?
What does idempotent mean in HTTP?
When should I use POST vs PUT?
What are HEAD and OPTIONS methods used for?
Why is DELETE idempotent if the resource is already gone?
Related Articles & Tools
HTTP Status Codes Explained
Every HTTP status code with examples
HTTP Request Headers
Complete guide to request headers
REST vs GraphQL
When to use REST vs GraphQL
API Authentication Methods
OAuth, JWT, API keys compared
CORS Explained
Cross-origin resource sharing guide
JSON Formatter Tool
Format & validate API responses
Conclusion
HTTP methods are the backbone of every REST API. Get them right and your API becomes intuitive, predictable, and easy to integrate with. Get them wrong and you'll spend hours debugging unexpected behavior, caching issues, and CORS failures.
The short version: GET for reading, POST for creating, PUT for replacing, PATCH for partial updates, DELETE for removing. HEAD to check without downloading, OPTIONS for CORS and discovery. Match your status codes to your methods, make your endpoints idempotent where the spec says they should be, and your API consumers will thank you.
If you take away one thing: use the method that matches the operation's semantics. Don't tunnel everything through POST just because it works. The method choice communicates intent to every client, proxy, cache, and developer who interacts with your API.
