HTTP Methods for REST APIs: GET, POST, PUT, DELETE
How RESTful operations work: GET, POST, PUT, PATCH, DELETE, and OPTIONS
Table of Contents
Overview
HTTP methods (also called verbs) define the action to do on a resource. RFC 9110 (HTTP Semantics) defines them formally. In RESTful APIs, these methods map to CRUD operations (Create, Read, Update, Delete). They give a standardized way to interact with resources. Each method returns appropriate HTTP status codes and should follow REST best practices.
| Method | CRUD | Idempotent | Safe | Request Body | Response Body |
|---|---|---|---|---|---|
| GET | Read | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes |
| POST | Create | ❌ No | ❌ No | ✅ Yes | ✅ Yes |
| PUT | Update/Replace | ✅ Yes | ❌ No | ✅ Yes | Optional |
| PATCH | Partial Update | ❌ No* | ❌ No | ✅ Yes | ✅ Yes |
| DELETE | Delete | ✅ Yes | ❌ No | Optional | Optional |
| N/A | ✅ Yes | ✅ Yes | ❌ No | Optional |
*PATCH can be idempotent. This depends on the implementation.
GET - Retrieve Resources
The GET method retrieves data from the server. It should never modify server state and is considered a "safe" method. GET requests can be cached, bookmarked, and remain in browser history.
When to Use GET
- To fetch a list of resources
- To retrieve a single resource by ID
- To search or filter resources
- To load data for display
Examples
Get All Users
{
"data": [
{"id": 1, "name": "John Doe", "email": "john@example.com"},
{"id": 2, "name": "Jane Smith", "email": "jane@example.com"}
],
"pagination": {"page": 1, "total": 50}
}
Get Single User
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"created_at": "2023-01-15T10:30:00Z"
}
Get with Query Parameters
{
"data": [...],
"pagination": {"page": 2, "limit": 10, "total": 150}
}
Best Practices
- Never use GET for operations that modify data
- Keep URLs under 2048 characters
- Use query parameters for filtering, sorting, and pagination
- Return appropriate cache headers
POST - Create Resources
The POST method creates new resources on the server. Each POST request can create a new resource. Thus POST is not idempotent. Two identical POST requests create two resources.
When to Use POST
- To create a new resource
- To submit form data
- To upload files
- To start complex operations that do not fit other methods
Examples
Create User
Request Body:
{
"name": "Jane Smith",
"email": "jane@example.com",
"password": "securepassword123"
}
Response (201 Created):
{
"id": 124,
"name": "Jane Smith",
"email": "jane@example.com",
"created_at": "2023-06-20T14:30:00Z"
}
Create Order
Request Body:
{
"user_id": 123,
"items": [
{"product_id": 456, "quantity": 2},
{"product_id": 789, "quantity": 1}
],
"shipping_address": "123 Main St, City, Country"
}
Best Practices
- Return 201 Created with the new resource in the response body
- Include a Location header that points to the new resource
- Validate all input data before you create the resource
- Use idempotency keys for financial transactions
PUT - Update/Replace Resources
The PUT method replaces an entire resource with the data you supply. PUT is idempotent, thus multiple identical requests produce the same result. If a resource does not exist, PUT can create it (upsert behavior).
When to Use PUT
- To replace an entire resource
- To update a resource when you have all its data
- To create a resource at a specific URL (client-determined ID)
Examples
Update User (Full Replace)
Request Body:
{
"name": "John Doe Updated",
"email": "john.updated@example.com",
"role": "admin",
"status": "active"
}
Response (200 OK):
{
"id": 123,
"name": "John Doe Updated",
"email": "john.updated@example.com",
"role": "admin",
"status": "active",
"updated_at": "2023-06-20T15:00:00Z"
}
PUT vs POST
| PUT | POST |
|---|---|
| Idempotent | Not idempotent |
| Client specifies the URI | Server determines the URI |
| Replace entire resource | Create new resource |
PATCH - Partial Updates
The PATCH method applies partial modifications to a resource. Unlike PUT, you only send the fields that need to be updated. This is more efficient for large resources where only a few fields change.
When to Use PATCH
- To update specific fields of a resource
- When you do not have or need the entire resource
- Bandwidth optimization for large resources
Examples
Update User Email Only
Request Body:
{
"email": "newemail@example.com"
}
Update Order Status
Request Body:
{
"status": "shipped",
"tracking_number": "1Z999AA10123456784"
}
PUT vs PATCH
| PUT | PATCH |
|---|---|
| Replace entire resource | Modify specific fields |
| Must send all fields | Send only changed fields |
| Always idempotent | Can be idempotent |
Making PATCH Idempotent
PATCH is not inherently idempotent, but you can design it to be. You must make sure that the same PATCH request always produces the same state.
Non-idempotent PATCH (counter increment — avoid):
{ "view_count": { "increment": 1 } } // Each call adds 1 — NOT idempotentIdempotent PATCH (set absolute value — prefer):
{ "status": "published" } // Same result every time — idempotentUpsert: Create or Update with HTTP Methods
An upsert (insert + update) creates a resource if it does not exist, or updates it if it does — all in a single request. REST APIs implement upserts primarily with PUT. You can also add conditional logic.
Upsert with PUT (Recommended)
PUT is naturally suited for upserts when the client controls the resource ID. The server creates the resource if it does not exist, or replaces it if it does:
PUT Upsert — Create or Replace User Profile
Request Body:
{
"bio": "Backend engineer",
"location": "San Francisco",
"website": "https://example.com"
}Response — if created: 201 Created | if updated: 200 OK
Conditional Upsert with If-None-Match Header
// Request headers:
If-None-Match: * // Only create — fail if already exists (409 Conflict)
// OR
If-Match: "abc123" // Only update if ETag matches (optimistic locking)Upsert with PATCH (Less Common)
// Request header:
X-Upsert: true
// Body — fields to set (create if not exists, update if exists):
{
"theme": "dark",
"notifications": true
}When to Use Which Approach
| Scenario | Method | Reason |
|---|---|---|
| Client knows the ID, replace entire resource | PUT | Idempotent, semantically correct |
| Client knows the ID, update partial fields | PATCH | Bandwidth efficient |
| Server assigns the ID | POST | Server controls URI |
| Sync/replicate resource state | PUT | Full replacement ensures consistency |
DELETE - Remove Resources
The DELETE method removes a resource from the server. DELETE is idempotent. Two or more DELETE requests for the same resource have the same effect as one request. The resource no longer exists.
When to Use DELETE
- To remove a resource permanently
- To cancel orders or subscriptions
- To revoke access tokens
Examples
Delete User
Response (204 No Content or 200 OK):
// 204 No Content - empty body
// or 200 OK with confirmation:
{
"message": "User successfully deleted",
"deleted_at": "2023-06-20T16:00:00Z"
}
Delete with Soft Delete
Response (200 OK):
{
"id": 789,
"status": "deleted",
"deleted_at": "2023-06-20T16:00:00Z",
"recoverable_until": "2023-07-20T16:00:00Z"
}
Best Practices
- Return 204 No Content for successful deletion
- Return 404 if the resource does not exist (or 204 for idempotency)
- Consider soft deletes for recoverable data
- Be careful with cascade deletes
OPTIONS - Get Options
The OPTIONS method describes the communication options for a target resource. Clients commonly use OPTIONS in CORS (Cross-Origin Resource Sharing) preflight requests. These requests check which methods and headers are permitted.
When OPTIONS is Used
- CORS preflight requests
- To discover the permitted methods for a resource
- API introspection and documentation
Examples
CORS Preflight Request
Response Headers:
HTTP/1.1 204 No Content
Allow: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
JavaScript fetch() Examples for Every HTTP Method
The browser's fetch() API is the modern way to call REST endpoints from JavaScript. Here are complete, copy-paste examples for each HTTP method, including proper headers and error handling.
GET — Fetch a resource
// GET /api/v1/users/123
const response = await fetch('https://api.example.com/v1/users/123', {
method: 'GET',
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
}
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const user = await response.json();
console.log(user);POST — Create a resource
// POST /api/v1/users
const response = await fetch('https://api.example.com/v1/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
},
body: JSON.stringify({
name: 'Jane Smith',
email: 'jane@example.com'
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
const newUser = await response.json(); // 201 Created
console.log('Created:', newUser.id);PUT — Replace a resource
// PUT /api/v1/users/123 — full replacement, all fields required
const response = await fetch('https://api.example.com/v1/users/123', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
},
body: JSON.stringify({
name: 'Jane Smith',
email: 'jane@example.com',
role: 'admin',
status: 'active'
})
});
const updated = await response.json(); // 200 OKPATCH — Partial update
// PATCH /api/v1/users/123 — only the fields you want to change
const response = await fetch('https://api.example.com/v1/users/123', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
},
body: JSON.stringify({
email: 'newemail@example.com' // only changed field
})
});
const updated = await response.json(); // 200 OKDELETE — Remove a resource
// DELETE /api/v1/users/123
const response = await fetch('https://api.example.com/v1/users/123', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer YOUR_TOKEN'
}
});
if (response.status === 204) {
console.log('Deleted successfully'); // 204 No Content — no body
} else if (response.status === 404) {
console.log('User not found');
}Reusable fetch() Wrapper
For real applications, wrap fetch in a utility that handles auth headers and error parsing automatically:
async function apiFetch(path, options = {}) {
const response = await fetch(`https://api.example.com${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`,
...options.headers,
},
body: options.body ? JSON.stringify(options.body) : undefined,
});
if (!response.ok) {
const err = await response.json().catch(() => ({ message: response.statusText }));
throw Object.assign(new Error(err.message), { status: response.status });
}
return response.status === 204 ? null : response.json();
}
// Usage
const user = await apiFetch('/v1/users/123'); // GET
const created = await apiFetch('/v1/users', { method: 'POST', body: { name: 'Ana' } }); // POST
await apiFetch('/v1/users/123', { method: 'DELETE' }); // DELETEError Handling by HTTP Method
Each HTTP method has a predictable set of error responses. Knowing which status codes to expect — and how to handle them — is essential for building robust API clients.
| Method | Common Error Status | Cause | Client Action |
|---|---|---|---|
| GET | 404 Not Found | Resource does not exist | Show empty state, do not retry |
| GET | 304 Not Modified | ETag / If-None-Match matched | Use cached response (not an error) |
| POST | 409 Conflict | Duplicate resource (e.g. email taken) | Show field-level validation error |
| POST | 422 Unprocessable Entity | Validation failed (bad field values) | Display errors per field from response body |
| PUT | 404 Not Found | Resource to replace does not exist | Create it via POST or handle upsert |
| PUT | 412 Precondition Failed | If-Match ETag mismatch (concurrent edit) | Re-fetch, merge, retry (optimistic lock) |
| PATCH | 422 Unprocessable Entity | Invalid field values in patch body | Show validation error from response |
| DELETE | 404 Not Found | Resource already deleted or never existed | Treat as success (idempotent intent) |
| DELETE | 409 Conflict | Resource has dependent children blocking delete | Delete children first or use cascade param |
| Any | 405 Method Not Allowed | Endpoint does not support this method | Check Allow header for valid methods |
| Any | 429 Too Many Requests | Rate limit exceeded | Back off using Retry-After header |
Always Inspect the Response Body on Errors
A good REST API returns structured error details in the body, not just a status code. Always parse the error body — it tells you which field failed validation, what the conflict is, and how to fix it. See our Error Handling guide for the recommended error response format.
Example 422 error body
{
"error": "validation_failed",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Email is already taken" },
{ "field": "name", "message": "Name must be at least 2 characters" }
]
}HTTP Method Override
Some environments — legacy firewalls, certain corporate proxies, older HTML forms — only allow GET and POST requests. The HTTP Method Override pattern lets clients tunnel PUT, PATCH, and DELETE requests through POST using a special header.
The X-HTTP-Method-Override Header
Send a POST request with the X-HTTP-Method-Override header set to the intended method. The server reads the header and routes the request as if it received a PUT, PATCH, or DELETE:
Simulating PUT via POST
POST /api/v1/users/123 HTTP/1.1
Content-Type: application/json
X-HTTP-Method-Override: PUT
{
"name": "Jane Smith",
"email": "jane@example.com",
"role": "admin"
}
// Server treats this as: PUT /api/v1/users/123Simulating DELETE via POST
POST /api/v1/users/123 HTTP/1.1
X-HTTP-Method-Override: DELETE
// Server treats this as: DELETE /api/v1/users/123Alternative: _method query parameter
POST /api/v1/users/123?_method=DELETE HTTP/1.1
// Some frameworks (Rails, Laravel) also support the _method query stringWhen to Use Method Override
- HTML forms — forms only support GET and POST. Use a hidden
_methodfield for PUT and DELETE - Restrictive proxies — corporate networks that block non-GET/POST requests
- Legacy client libraries — older HTTP clients that do not support all verbs
Important: Only enable method override on the server if you actually need it. It is an escape hatch — modern clients using proper HTTP have no need for it. Always validate that the overridden method is permitted for that endpoint.
Additional Methods
Get Headers Only
Identical to GET, but returns only response headers and no body. HEAD is safe and idempotent, like GET. Use it to check that a resource exists, to read metadata (Content-Length, Last-Modified, ETag), or to validate cache freshness. HEAD does not have the bandwidth cost of a full download.
HEAD /api/users/123
Practical HEAD use cases
- Existence check:
HEAD /files/report.pdf— 200 means it exists, 404 means it does not - Content-Length: Check file size before deciding whether to download
- Cache validation: Compare ETag or Last-Modified before re-fetching
HEAD with curl
curl -I https://api.example.com/v1/files/report.pdf
# Response headers only:
HTTP/2 200
content-type: application/pdf
content-length: 204800
etag: "abc123"
last-modified: Mon, 10 Jun 2026 08:00:00 GMTLoop-Back Test
Performs a message loop-back test to the target resource. Use it to debug. Most servers disable TRACE in production for security.
TRACE /api/debug
Establish Tunnel
Establishes a tunnel to the server identified by the target resource. Used primarily for HTTPS connections through proxies.
CONNECT example.com:443
Common Mistakes with HTTP Methods
Even experienced developers make mistakes with HTTP method semantics. Here are the most common pitfalls to avoid.
Using POST for Everything (RPC-Style Anti-Pattern)
Some developers use POST for all operations, and treat the API like Remote Procedure Calls. This breaks caching and idempotency guarantees. It also makes the API more difficult to understand.
POST /api/getUser?id=123POST /api/deleteOrder?id=456POST /api/updateUserStatus
GET /api/users/123DELETE /api/orders/456PATCH /api/users/123 { "status": "active" }
Sending GET Requests with a Body
HTTP does not forbid a body in a GET request, but we do not recommend it. Many proxies, CDNs, and HTTP clients ignore or strip GET request bodies. Use query parameters instead.
GET /api/usersBody: { "filter": { "status": "active" } }
GET /api/users?status=active
Ignoring Idempotency in Distributed Systems
In distributed systems, network failures can cause clients to retry requests. PUT and DELETE are idempotent, thus you can safely retry them. POST is not idempotent. If you retry a POST request to create a user, you create duplicate users. Use idempotency keys for POST operations in critical paths.
POST /api/paymentsIdempotency-Key: unique-client-generated-uuid
Using DELETE Without Considering Cascades
A DELETE request often has side effects on related resources. A deleted user can have orders, comments, and uploaded files. Plan cascade behavior explicitly and record it. Consider soft deletes for recoverable data.
DELETE /api/users/123// Deletes user, anonymizes their orders, removes personal data// Returns 200 with summary of what was deleted
Frequently Asked Questions
What are the main HTTP methods used in REST APIs?
The main HTTP methods in REST APIs are:
- GET — Retrieve a resource (safe, idempotent)
- POST — Create a new resource
- PUT — Replace an entire resource (idempotent)
- PATCH — Partially update a resource
- DELETE — Remove a resource (idempotent)
- OPTIONS — Describe allowed operations on a resource
What is the difference between PUT and PATCH?
PUT replaces the entire resource (all fields required). PATCH applies partial changes (only send changed fields). PUT is always idempotent. PATCH can be idempotent, but this depends on your implementation. For upsert patterns, see the Upsert section above.
Is GET idempotent?
Yes. GET is both safe (no side effects) and idempotent (same result every call). GET responses can also be cached. You should never use GET for operations that modify data on the server.
When should I use POST instead of PUT?
Use POST when the server assigns the resource ID or URL. For example, the server generates the ID for a new user. Use PUT when the client knows the exact URL and must create or replace the resource at that location.
What does idempotent mean in REST APIs?
An idempotent operation produces the same result no matter how many times you call it. GET, PUT, DELETE, and OPTIONS are idempotent. POST is not idempotent, because two POST requests create two resources. Idempotency is critical in distributed systems where network failures can cause retries.
References & Further Reading
Related Topics
HTTP methods are one of the most common REST API interview topics. Prepare with our REST API Interview Questions guide — which covers idempotency, PUT vs PATCH, and real-world design scenarios.
📊 Status Codes
Learn which HTTP status codes to return with each method.
✨ Best Practices
URL design, versioning, and API design patterns.
💻 API Examples
Real-world examples of HTTP methods in action.
⚠️ Error Handling
Handle errors gracefully for each HTTP method.
🩹 PATCH Deep Dive
Partial updates, JSON Patch format, and idempotency in detail.
🔀 OPTIONS & CORS
CORS preflight requests, headers, and browser security model.