One of the questions I get asked most often by developers new to API design is: “How do I know which HTTP method to use?” It seems simple at first, but once you get into real-world scenarios like partial updates, bulk actions, or non-CRUD operations, it gets trickier. In this article, I am going to break down every common HTTP method, explain exactly when I use each one, and cover the edge cases that trip most people up.
Why HTTP Methods Matter So Much
HTTP methods are not just labels. They carry real meaning that both humans and machines rely on:
- Browsers and proxies cache
GETrequests but notPOSTrequests - Load balancers and API gateways can safely retry
GET,PUT, andDELETErequests because they are “idempotent,” but they generally cannot safely retryPOST - Developers reading your API can predict behavior just from the method name, without reading extra documentation
Choosing the wrong method breaks these assumptions and creates confusing, unpredictable behavior.
GET: Retrieve Data, Never Change It
I use GET whenever I want to read data, and never for anything that changes state on the server.
GET /users/45
GET /orders?status=shipped
Key properties of GET that I always respect:
- Safe: It must never modify data.
- Idempotent: Calling it once or a hundred times produces the same result.
- Cacheable: Responses can be cached by browsers, CDNs, and proxies.
I never accept a request body with GET. If I need to pass complex filtering criteria, I use query parameters, and if the filtering criteria is too large or complex for a query string, I create a dedicated search endpoint using POST instead (more on that below).
POST: Create a New Resource or Trigger an Action
I use POST in two main situations:
1. Creating a New Resource
POST /orders
Content-Type: application/json
{
"customerId": 45,
"items": [{ "productId": 12, "quantity": 2 }]
}
The server typically responds with a 201 Created status and a Location header pointing to the new resource.
2. Triggering an Action That Doesn’t Fit Other Methods
Sometimes an operation is not a simple create, update, or delete. For example, sending an email, processing a payment, or running a search with a complex body. In these cases, I still use POST, treating the action as its own sub-resource:
POST /orders/901/cancellation
POST /reports/generate
Key properties of POST:
- Not safe: It changes server state.
- Not idempotent: Calling it twice can create two resources (this is why I always design idempotency keys for critical operations like payments).
PUT: Replace a Resource Completely
I use PUT when I want to replace an entire resource with a new version. The client must send the complete representation of the resource, not just the fields that changed.
PUT /users/45
Content-Type: application/json
{
"firstName": "Ayesha",
"lastName": "Khan",
"email": "ayesha@example.com",
"accountStatus": "active"
}
If the client leaves out a field, I treat that as intentionally clearing it, because PUT means “this is now the full state of the resource.”
Key properties of PUT:
- Idempotent: Sending the same
PUTrequest multiple times results in the same final state. - Not safe: It changes data.
- Can sometimes be used to create a resource too, if the client specifies the ID (for example,
PUT /users/45when user 45 does not exist yet), though I use this pattern carefully and only when IDs are client-generated.
PATCH: Update Part of a Resource
I use PATCH when the client only wants to change some fields, not the entire resource.
PATCH /users/45
Content-Type: application/json
{
"email": "ayesha.khan@example.com"
}
This only updates the email field and leaves everything else untouched. This is the method I reach for most often in real applications, because clients rarely have the full resource state on hand and usually just want to update one or two fields.
Key properties of PATCH:
- Not guaranteed idempotent by the HTTP spec, though I always try to design my
PATCHendpoints to behave idempotently in practice. - Often uses a “merge patch” format (send only the fields you want to change), though some APIs use the more formal JSON Patch format with explicit operations:
[
{ "op": "replace", "path": "/email", "value": "ayesha.khan@example.com" }
]
I usually go with the simpler merge-patch style unless the API genuinely needs precise, operation-based patching (for example, array manipulation).
DELETE: Remove a Resource
I use DELETE when the client wants to remove a resource entirely.
DELETE /orders/901
Key properties of DELETE:
- Idempotent: Deleting the same resource multiple times results in the same end state — the resource stays deleted. I typically return
204 No Contenton the first successful delete, and either404 Not Foundor another204on subsequent calls, depending on the design philosophy I am following. - Not safe: It clearly changes state.
For resources I don’t want removed instantly (for example, user accounts), I often implement a soft delete — the record is marked as deleted internally, but I still use DELETE as the public-facing method, because that is what best represents the client’s intent.
HEAD and OPTIONS: The Lesser-Known Methods
I don’t use these every day, but they are worth understanding:
- HEAD: Works exactly like
GET, but returns only the headers, no response body. Useful for checking if a resource exists or checking metadata like content length without downloading the full response. - OPTIONS: Used to discover what methods are allowed on a resource, and it is what browsers automatically send as a “preflight” request for CORS.
A Quick Decision Table I Use
| I want to… | Method |
|---|---|
| Read a resource or collection | GET |
| Create a new resource | POST |
| Replace an entire resource | PUT |
| Update part of a resource | PATCH |
| Remove a resource | DELETE |
| Trigger an action with no clean noun | POST (as a sub-resource) |
| Check if a resource exists, headers only | HEAD |
| Discover allowed methods | OPTIONS |
Common Mistakes I See (and Used to Make Myself)
- Using
GETwith a body to pass complex filters — this breaks caching and is not reliably supported by all HTTP clients. - Using
POSTfor everything, including updates and deletes, which throws away all the useful semantics HTTP already gives you for free. - Using
PUTfor partial updates, accidentally wiping out fields the client did not include in the request. - Making
POSTactions non-idempotent for critical operations like payments, without adding an idempotency key to prevent duplicate charges. - Forgetting that
DELETEshould be idempotent, and returning an error on the second delete attempt instead of treating it gracefully.
Final Thoughts
Choosing the right HTTP method is about respecting the meaning that is already built into the protocol. When I use GET, POST, PUT, PATCH, and DELETE the way they are meant to be used, my API becomes predictable, cacheable where it should be, and safe to retry where it needs to be — all without extra documentation.