HTTP Methods Explained: A Zoo Management Story
You just got hired as the lead developer at Maple City Zoo. Your first task: build an API to manage all the animals. The zookeepers are tired of spreadsheets. They want a system where they can look up animals, add new ones, update records, and remove animals that get transferred to other zoos.
This is the story of how you build that system — and in doing so, learn every HTTP method you'll ever need.
The Zoo's Database
Before we write any endpoints, here's what an animal record looks like:
{
"id": 1,
"name": "Koda",
"species": "Koala",
"age": 4,
"exhibit": "Australian Outback",
"diet": "herbivore",
"weight_kg": 9.5
}
The zookeepers need to do five things: look up animals, register new arrivals, fully update records, make small corrections, and remove transfers. Each of these maps perfectly to an HTTP method.
GET — Looking Up Animals
Monday morning. A school group is visiting and the guide needs to pull up information about the koalas. She opens the zoo app and searches.
GET /api/animals
The server responds with every animal in the zoo:
[
{ "id": 1, "name": "Koda", "species": "Koala", "age": 4, ... },
{ "id": 2, "name": "Mango", "species": "Parrot", "age": 12, ... },
{ "id": 3, "name": "Brutus", "species": "Grizzly Bear", "age": 8, ... }
]
She only needs the koalas, so the app filters:
GET /api/animals?species=Koala
And for a specific animal's full profile:
GET /api/animals/1
The rules of GET:
- It never changes data on the server. It's read-only. Always.
- It's idempotent — calling it once or a hundred times gives the same result
- It's cacheable — browsers and CDNs can store the response and serve it again without hitting the server
- Query parameters go in the URL, not in a request body
Performance note: GET requests are the most cache-friendly method. The browser will automatically cache GET responses based on headers like Cache-Control and ETag. For our zoo app, the list of animals doesn't change every second — we can cache it for a few minutes and save the server a lot of work.
GET /api/animals
Cache-Control: max-age=300
That one header means the browser won't even ask the server again for 5 minutes. For a zoo with 200 animals, that's a big deal when hundreds of visitors are checking the app simultaneously.
POST — Registering New Arrivals
Tuesday. A truck pulls up to the loading dock. There's a new penguin from the Antarctic Research Station. Time to register her.
POST /api/animals
{
"name": "Pepper",
"species": "Emperor Penguin",
"age": 3,
"exhibit": "Arctic Adventure",
"diet": "carnivore",
"weight_kg": 23.1
}
The server creates a new record, assigns an ID, and responds:
{
"id": 4,
"name": "Pepper",
"species": "Emperor Penguin",
"age": 3,
"exhibit": "Arctic Adventure",
"diet": "carnivore",
"weight_kg": 23.1
}
Notice we didn't send an id. The server generates that. We just sent the data for the new animal.
The rules of POST:
- It creates a new resource. Every time.
- It is not idempotent — if you accidentally send the same POST twice, you get two penguins named Pepper. The zoo now has a problem.
- The server decides the URL of the new resource (by assigning the ID)
- It's not cacheable by default
Why "not idempotent" matters: Imagine the zookeeper's internet hiccups. The app retries the request. Now there are two Peppers in the system. This is why well-designed APIs return 409 Conflict if a duplicate is detected, or why frontends disable submit buttons after the first click.
PUT — Annual Record Updates
Wednesday. It's annual review day. The vet team has done their checkups, and every animal's record needs to be updated with fresh data. The zookeeper pulls up Koda the koala and submits the full updated record.
PUT /api/animals/1
{
"name": "Koda",
"species": "Koala",
"age": 5,
"exhibit": "Australian Outback",
"diet": "herbivore",
"weight_kg": 10.2
}
Koda had a birthday and gained some weight. But here's the critical thing about PUT — we sent every single field, even the ones that didn't change. The name is still "Koda," the species is still "Koala," but we sent them anyway.
Why? Because PUT means: "Here is the complete, current state of this resource. Replace whatever you have with this."
What happens if you forget a field?
PUT /api/animals/1
{
"name": "Koda",
"age": 5
}
A strict API would set the missing fields to null or their defaults. Koda's species? Gone. His exhibit? Gone. His diet? Unknown. You just gave the server a complete replacement, and it took you literally.
This is the most common mistake people make with PUT. They treat it like PATCH (we'll get there) and only send what changed. Then they wonder why half the record disappeared.
The rules of PUT:
- It replaces the entire resource with what you send
- If the resource exists, it gets updated. If it doesn't exist, some APIs will create it (this depends on the API design)
- It is idempotent — sending the same PUT ten times gives the same result as sending it once. The final state is identical.
- You must send all fields, not just the ones that changed
Performance note: Because PUT is idempotent, it's safe to retry. If the network drops and you're not sure the request went through, just send it again. The outcome is the same. This makes PUT much easier to work with than POST in unreliable network conditions.
PATCH — Quick Corrections
Thursday. A zookeeper notices that Mango the parrot was moved to a new exhibit last week, but the system still shows the old one. She doesn't want to fill out the entire record — she just wants to fix the exhibit field.
PATCH /api/animals/2
{
"exhibit": "Tropical Rainforest"
}
That's it. One field. Everything else about Mango stays exactly as it was. The server responds with the full updated record:
{
"id": 2,
"name": "Mango",
"species": "Parrot",
"age": 12,
"exhibit": "Tropical Rainforest",
"diet": "omnivore",
"weight_kg": 0.4
}
This is the key difference from PUT. With PATCH, you only send what changed. With PUT, you send everything.
Think of it this way:
- PUT is like filling out an entirely new form and handing it in — the old one gets shredded
- PATCH is like using whiteout on one line of the existing form
The rules of PATCH:
- It partially updates a resource — only the fields you include get changed
- Fields you don't send are left untouched
- It's technically not guaranteed to be idempotent (though in practice it usually is)
- It's the most common method for updates in modern APIs
When to use PUT vs PATCH:
| Scenario | Method | |----------|--------| | Annual vet review — every field refreshed | PUT | | Fix a typo in an animal's name | PATCH | | Replace an entire configuration object | PUT | | Toggle a single setting | PATCH | | Form submission with all fields required | PUT | | Inline edit of one cell in a table | PATCH |
In the real world, PATCH is what you'll use 90% of the time. Most updates are small corrections, not full replacements.
DELETE — Transfers and Farewells
Friday. Brutus the grizzly bear is being transferred to a wildlife sanctuary in British Columbia. Time to remove him from the system.
DELETE /api/animals/3
The server responds with 204 No Content — the record is gone. No body needed in the response. Brutus has left the building.
HTTP/1.1 204 No Content
The rules of DELETE:
- It removes the resource
- It is idempotent — deleting the same resource twice should give the same result (the second time just returns
404 Not Foundor still204, depending on the API) - The request usually has no body
- The response usually has no body either
A note on soft deletes: Most production systems don't actually delete the record from the database. They add a deleted_at timestamp and filter it out of queries. This way, if someone accidentally deletes Brutus, the data team can recover him. The API still returns 204 — the client doesn't need to know about the implementation detail.
The Full Week at a Glance
| Day | Action | Method | What It Does | |-----|--------|--------|-------------| | Monday | Look up animals | GET | Read data, change nothing | | Tuesday | Register Pepper the penguin | POST | Create a new resource | | Wednesday | Annual record update for Koda | PUT | Replace entire resource | | Thursday | Fix Mango's exhibit | PATCH | Update specific fields | | Friday | Transfer Brutus | DELETE | Remove a resource |
HTTP Status Codes Worth Knowing
The zoo API doesn't just do things — it tells you what happened.
Success:
200 OK— Here's the data you asked for (GET, PUT, PATCH)201 Created— New resource created, here it is (POST)204 No Content— Done, nothing to send back (DELETE)
Client errors:
400 Bad Request— The data you sent doesn't make sense (missing required fields, wrong types)401 Unauthorized— Who are you? Log in first.403 Forbidden— I know who you are, but you can't do that. (Intern tried to delete an animal.)404 Not Found— That animal doesn't exist409 Conflict— This resource already exists (duplicate POST)422 Unprocessable Entity— The data format is correct but the values are wrong (age can't be negative)
Server errors:
500 Internal Server Error— Something broke on our end, sorry503 Service Unavailable— We're doing maintenance, try again later
Performance: What Actually Matters
Building the zoo API taught me that HTTP performance isn't just about fast servers. It's about using the protocol correctly.
Cache GET requests aggressively. The animal list page gets hit hundreds of times a day. Without caching, that's hundreds of database queries for the same data. Add Cache-Control headers and most of those requests never even reach your server.
Use conditional requests. Send an ETag header with GET responses. When the client requests again, it sends If-None-Match with the old ETag. If nothing changed, the server responds with 304 Not Modified — no body, no bandwidth wasted.
# First request
GET /api/animals/1
→ 200 OK
→ ETag: "abc123"
# Second request
GET /api/animals/1
If-None-Match: "abc123"
→ 304 Not Modified (no body transferred)
Use PATCH over PUT when possible. Sending 7 fields when you only changed 1 is wasteful. On mobile networks with limited bandwidth, this adds up. PATCH keeps payloads small.
Batch operations for bulk changes. If the vet team updates 50 animals on review day, don't send 50 individual PUT requests. Design a batch endpoint:
PATCH /api/animals/batch
[
{ "id": 1, "weight_kg": 10.2 },
{ "id": 2, "weight_kg": 0.45 },
...
]
One request instead of fifty. Fewer round trips, less overhead, happier servers.
Connection reuse. HTTP/2 and HTTP/3 multiplex requests over a single connection. If your zoo app makes 10 API calls on page load, they can all share one connection instead of opening 10 separate ones. Make sure your server supports modern HTTP protocols.
The Cheat Sheet
- GET — Read. Cacheable. Idempotent. No side effects.
- POST — Create. Not cacheable. Not idempotent. Careful with retries.
- PUT — Replace the whole thing. Idempotent. Send all fields.
- PATCH — Update part of it. Send only what changed.
- DELETE — Remove it. Idempotent. Usually no request or response body.
The difference between a junior and senior developer isn't knowing what HTTP methods do — it's knowing when each one is the right choice.
The zoo is running smoothly now. The zookeepers are happy, Pepper the penguin is settling in, and Koda's records are up to date. All because we picked the right HTTP method for each job.