HTTP Methods
By the end of this lesson
Choose between GET, POST, PUT, PATCH and DELETE, and respect safety and idempotency.
Every HTTP request carries two pieces of intent. The path says which thing you mean. The method says what you want done with it.
GET /api/employees/42 and DELETE /api/employees/42 name the same employee. One reads the record, one removes it. Nothing in the path distinguishes them, which is why the method is not decoration — it is half the meaning of the request.
The five methods you will use for almost everything:
- GET
- Retrieve a representation of something. It must not change anything the caller can observe. A query string refines what comes back, for example ?departmentId=3.
- POST
- Create something new, or ask the server to carry out an operation that does not fit the other four. The server decides the address of whatever it created and returns it.
- PUT
- Replace the thing at this address with the body you sent. Replace is the important word: a field you leave out is cleared, not preserved.
- PATCH
- Apply a partial change. Only the fields present in the body are touched, so it suits "change this one email address" without resending the whole record.
- DELETE
- Remove the thing at this address. Whether the row is really deleted or marked inactive is your business; to the caller the resource is gone.
Two properties decide how a method may be used, and they are frequently confused. Safe means the request changes nothing. Idempotent means that sending the request once and sending it five times leave the server in the same state.
| Safe — changes nothing | Idempotent — repeating is harmless | |
|---|---|---|
| GET | Yes. A GET that changes data is a bug, not a shortcut. | Yes. Reading twice leaves the same state. |
| POST | No. It exists to change something. | No. Two identical POSTs to /api/orders create two orders. |
| PUT | No. | Yes. The second call writes the same values the first one did. |
| PATCH | No. | Only if the change is absolute. Setting salary to 52000 repeats safely; adding 1000 to it does not. |
| DELETE | No. | Yes. After the first call the resource is gone, and further calls leave it gone. |
Idempotency is about the state of the server, not about getting an identical response. A second DELETE /api/employees/42 will probably answer 404 rather than 204, and that is still idempotent — the employee is absent either way.
This matters because networks drop responses. A caller sends POST /api/orders, the order is created, and the reply never arrives. The caller cannot tell the difference between "the request never landed" and "the request worked and the answer was lost". Retrying is the natural response, and with POST it creates a second order.
The fix is an idempotency key: the caller generates a unique value for this attempt and sends it with the request. The server records the key alongside the result. If the same key arrives again, it returns the original result instead of creating anything. Retries become safe without changing what POST means.
POST /api/orders HTTP/1.1
Host: api.example.com
Content-Type: application/json
Idempotency-Key: 4b7d2c10-3f41-4a8e-9c62-b0e5f1d78a20
{ "employeeId": 42, "sku": "DESK-LAMP-01", "quantity": 2 }
HTTP/1.1 201 Created
Location: /api/orders/1042
Content-Type: application/json
{ "id": 1042, "employeeId": 42, "sku": "DESK-LAMP-01", "quantity": 2 }- The caller invents the Idempotency-Key value, not the server. It identifies this attempt, so a retry of the same attempt reuses it and a genuinely new order gets a new one.
- The server stores the key with the created order id. A repeat of the same key returns 201 and the same order 1042 rather than creating 1043.
- Location tells the caller where the new order now lives. That is how the caller learns the address it did not get to choose.
- Nothing here makes POST idempotent in general. You have added a mechanism that recognises a duplicate attempt, which is a different and honest claim.
app.MapGet("/api/employees/{id:int}", async (int id, EmployeeService employees) =>
{
var employee = await employees.FindAsync(id);
return employee is null ? Results.NotFound() : Results.Ok(employee);
});
app.MapPost("/api/employees", async (CreateEmployeeRequest request, EmployeeService employees) =>
{
var created = await employees.CreateAsync(request);
return Results.Created($"/api/employees/{created.Id}", created);
});
app.MapPut("/api/employees/{id:int}", async (int id, ReplaceEmployeeRequest request, EmployeeService employees) =>
{
var replaced = await employees.ReplaceAsync(id, request);
return replaced ? Results.NoContent() : Results.NotFound();
});
app.MapDelete("/api/employees/{id:int}", async (int id, EmployeeService employees) =>
{
await employees.DeleteAsync(id);
return Results.NoContent();
});- Four methods, one path pattern. The path never contains a word like get or delete, because the method already says it.
- MapGet returns the employee or a 404. A read never changes anything, which is what makes it cacheable and retryable by anything between the caller and you.
- MapPost returns 201 with a Location header, built with Results.Created. The server chose the id, so it has to tell the caller where the new record is.
- MapPut returns 204 No Content when the replacement succeeded. There is nothing useful to send back — the caller already has the values it just sent.
- MapDelete returns 204 whether or not the record was there. Answering the same way for a repeat keeps the endpoint pleasant to retry. Returning 404 for the second call is also defensible; pick one and apply it everywhere.
Summary
- The method carries half the meaning of a request; the path carries the other half
- Safe means the request changes nothing observable — only GET, HEAD and OPTIONS qualify
- Idempotent means repeating the request leaves the same server state: PUT and DELETE do, POST does not
- PUT replaces the whole resource, PATCH changes only the fields you send
- Retries are a fact of networking, which is why creates often need an idempotency key
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Think about it
Pick the method
Choose a method and path for each of these operations on the employees API, and say why.
1. Read one employee. 2. Hire a new employee. 3. Correct a misspelled email address, leaving everything else alone. 4. Overwrite an employee record with a full set of values from an HR import. 5. Remove an employee who never started.
Show solution
GET /api/employees/42 for the read, because it changes nothing and can be cached and retried.
POST /api/employees for the hire. The server assigns the id, so the caller cannot know the address in advance, and creating twice is genuinely two employees.
PATCH /api/employees/42 for the email correction. Only the field you send changes, so the rest of the record is not at risk from a caller that does not know every field.
PUT /api/employees/42 for the HR import, because the import genuinely holds the whole record and replacement is what you want. If the import is missing a field that exists in your database, PUT will clear it — which is the behaviour to check before choosing PUT.
DELETE /api/employees/42 for the removal. It is idempotent, so a retry after a timeout is harmless.
Think about it
Where the duplicate comes from
A warehouse app on a poor mobile connection creates orders. Support reports occasional duplicate orders, always identical, always seconds apart. The server log shows two complete requests.
Explain the sequence of events, and describe two changes that would stop it.
Show solution
The first request reached the server and created the order. The response did not get back to the app before the connection dropped, so the app saw a timeout and retried. The server had no way to know the second request was the same attempt, so it created a second order.
The server-side change is an idempotency key: the app sends a unique value per attempt, the server stores it with the result, and a repeat returns the original order instead of creating another.
The client-side change is to stop retrying blindly on a timeout for non-idempotent calls, and instead reconcile — ask whether the order exists before sending it again.
Both are worth having. The client change reduces duplicates; only the server change can guarantee it, because the server is the only place that sees every request.
Knowledge check
Nothing is recorded and there is no score. The explanation appears either way.
Saved in this browser only.