Web Foundations & Databases · beginner · ~10 min
By the end of this lesson you will be able to: - Read any REST request and say in plain English what it does to which resource. - Match each HTTP method (GET, POST, PUT, PATCH, DELETE) to the action it performs and know which ones are safe or idempotent. - Recognise JSON at a glance and identify its four building blocks: objects, arrays, scalars, and `null`. - Take a REST request apart into its three security-relevant pieces — the method, the JSON body, and the resource ID in the URL. - Read HTTP status codes well enough to tell success from client error from server error. - Drive a real API by hand with `curl`, and explain why the same three pieces are where verb tampering, mass assignment, and IDOR appear.
Almost every modern app — a phone app, a single-page website, one backend calling another — talks to its server through an API (Application Programming Interface). An API is just an agreed set of requests a server promises to answer. The most common style today is REST over HTTP, and the data it carries is almost always JSON.
This lesson builds directly on How the web works: client, server, request, response. There you saw that a client sends an HTTP request (a method, a URL, headers, and an optional body) and the server sends back a response (a status code, headers, and a body). REST is a set of conventions layered on top of that raw request/response machinery. It answers two questions: what does the URL point at? and what do I want to do to it?
In plain language: REST treats the things your app manages — users, orders, photos, comments — as resources, and gives each one a URL, like /api/users/42. The HTTP method is the verb that says what to do with that resource: read it, create one, change it, or delete it. The data going in or coming back rides along as JSON, a simple text format that both humans and programs can read.
The terminology you will meet: resource (a thing with a URL), method or verb (GET/POST/PUT/PATCH/DELETE), endpoint (a method + URL pair the server handles), payload or body (the JSON you send), and status code (the three-digit result). Once these click, an API stops looking like magic and starts looking like a small, predictable vocabulary.
APIs are where the real work — and the real risk — lives. The pretty web page is just a client; the API behind it is the backend's true attack surface, and it will happily answer a request whether it came from the official app or from a script you wrote in thirty seconds.
Being fluent in REST + JSON pays off three ways:
Definition. A resource is any thing your API manages, and REST gives each resource (and each collection of them) a URL.
Think of URLs as an address book for your data. A collection is a plural noun, and a single item is that collection plus an ID:
/api/users -> the collection of all users
/api/users/42 -> one user, the one with id 42
/api/users/42/orders-> the orders belonging to user 42
How it works internally. The server has a router — a table that matches an incoming URL pattern (like /api/users/{id}) to a function. The {id} part is pulled out as a variable and handed to your code, which typically looks it up in a database.
When to use / not to. Model nouns as resources (/api/orders), not actions. A URL like /api/deleteUser?id=42 is a classic beginner smell — the verb belongs in the method, not the path. Use DELETE /api/users/42 instead.
Pitfall. Because the ID sits right there in the URL, it is trivial to change. If the server does not check that you are allowed to see user 42, changing 42 to 43 hands you someone else's data — that is IDOR.
Knowledge check. In
GET /api/projects/7/tasks/3, name every resource the URL references and say which method verb is acting on them.
Definition. The HTTP method states the action to perform on the resource named by the URL.
| Method | Action | Safe? | Idempotent? | Typical body |
|---|---|---|---|---|
| GET | Read a resource | Yes | Yes | none |
| POST | Create a new resource | No | No | JSON |
| PUT | Replace a resource wholesale | No | Yes | full JSON |
| PATCH | Update part of a resource | No | No* | partial JSON |
| DELETE | Remove a resource | No | Yes | usually none |
Safe means "read-only, no server state changes." Idempotent means "doing it twice has the same effect as doing it once" — deleting user 42 twice still leaves user 42 deleted, but POSTing an order twice creates two orders. (PATCH can be idempotent depending on the change, so it is marked with an asterisk.)
How it works internally. The method is the very first token on the HTTP request line: GET /api/users/42 HTTP/1.1. The server routes on the pair (method, path), so GET /api/users/42 and DELETE /api/users/42 can run completely different code.
When to use / not to. Use GET only for reads — never let a GET change data, because browsers, proxies, and crawlers prefetch and cache GETs freely. Use POST to create, PUT/PATCH to update, DELETE to remove.
Pitfall — verb tampering. A UI might only show a "view" button (GET) but the server may still answer DELETE or PUT on the same URL because the developer forgot to restrict it. Hiding a button is not access control.
Knowledge check (predict). You send
DELETE /api/users/42and it returns success. You send the exact same request again. Is the second call safe to make, and what status might it return the second time?
Definition. JSON (JavaScript Object Notation) is a lightweight text format for structured data. It is the default body format for REST APIs.
JSON has exactly four building blocks plus two container types:
{ "key": value, ... } — unordered name/value pairs.[ value, value, ... ] — ordered lists."hi"), numbers (42, 3.14), booleans (true/false).null.{
"id": 42, <- number scalar
"name": "Ada", <- string scalar
"active": true, <- boolean scalar
"nickname": null, <- explicit "no value"
"roles": ["user","beta"], <- array of strings
"address": { <- nested object
"city": "Lovelace"
}
}
How it works internally. JSON is just text on the wire. The sender serializes a data structure into that text; the receiver parses it back into objects your code can read. Keys are always strings in double quotes. There are no comments, no trailing commas, and no single quotes — those are the top three syntax errors.
When to use / not to. JSON is ideal for structured request and response bodies. It is not meant for huge binary blobs (images, video) — those are sent as raw bytes or multipart uploads.
Pitfall — mass assignment. If the server takes the whole JSON body and blindly copies every field onto a database record, a client can add a field the form never showed — like "role": "admin" — and quietly promote itself. The fix is to accept only an explicit allow-list of fields.
Knowledge check (find the bug). Why is
{ 'name': 'Ada', "age": 30, }invalid JSON? Point to two separate problems.
Definition. The response status code is a three-digit number summarising what happened. The first digit is the category.
| Range | Meaning | Common examples |
|---|---|---|
| 2xx | Success | 200 OK, 201 Created, 204 No Content |
| 3xx | Redirection | 301 Moved, 304 Not Modified |
| 4xx | Client error (your request was wrong) | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found |
| 5xx | Server error (the server broke) | 500 Internal Server Error, 503 Service Unavailable |
Why it matters. The status code tells you whose fault it is. A 4xx means fix your request; a 5xx means the server crashed on a request it should have handled. The security-relevant pair is 401 ("I don't know who you are") versus 403 ("I know who you are, and you're not allowed").
Knowledge check (explain). In your own words, why is returning 404 sometimes safer than 403 when a user requests a resource that exists but isn't theirs?
A REST request has four parts. Here is the anatomy, annotated:
POST /api/users/42/orders HTTP/1.1 <- method + resource URL + HTTP version
Host: api.example.com <- which server
Authorization: Bearer eyJhbGci... <- who you are (a token)
Content-Type: application/json <- the body is JSON
Content-Length: 27 <- how many bytes of body follow
<- blank line separates headers from body
{ "item": "book", "qty": 2 } <- the JSON payload
And a response:
HTTP/1.1 201 Created <- status code + reason phrase
Content-Type: application/json
{ "id": 1001, "item": "book", "qty": 2 } <- the created resource, as JSON
Key rules to remember:
Content-Type: application/json announces a JSON body — omit it and many servers ignore your body.Modern apps talk to their backend through APIs. The most common style is REST over HTTP, exchanging JSON data.
In REST, resources are URLs, and the HTTP method is the verb that says what to do with them:
For example:
GET /api/users/42 reads user 42.DELETE /api/users/42 removes user 42.Data travels as JSON (JavaScript Object Notation), a human-readable text format and the default for APIs.
JSON can hold:
{"id":42,"role":"user"}.null.Each part of a REST request maps to a common flaw:
DELETE in the UI but still accept it when called directly. This is verb tampering."role":"admin" to an endpoint that blindly binds whatever fields it receives./users/42 to /users/43 to reach another user's data.A web tester reads and rewrites these requests constantly using curl, Postman, or Burp. The full API attack surface is covered in the API Security track.
Here is a complete, runnable session against a real public sandbox API (jsonplaceholder.typicode.com, a free fake REST API for practice). Every command uses curl, the standard command-line HTTP client. The -s flag silences the progress meter; -i includes the response status line and headers.
# 1) GET — read a single resource (a to-do item). Safe, no body.
curl -s https://jsonplaceholder.typicode.com/todos/1
# 2) GET a collection, then filter to the first two with a query string.
curl -s 'https://jsonplaceholder.typicode.com/todos?_limit=2'
# 3) POST — create a new resource. Send a JSON body and the matching header.
curl -s -i -X POST https://jsonplaceholder.typicode.com/posts \
-H 'Content-Type: application/json' \
-d '{ "title": "hello", "body": "first post", "userId": 1 }'
# 4) PATCH — update part of an existing resource.
curl -s -X PATCH https://jsonplaceholder.typicode.com/posts/1 \
-H 'Content-Type: application/json' \
-d '{ "title": "updated title" }'
# 5) DELETE — remove a resource. No body needed.
curl -s -i -X DELETE https://jsonplaceholder.typicode.com/posts/1
What it does. Command 1 reads to-do #1. Command 2 reads the collection but asks the server for only two items via the _limit query parameter. Command 3 creates a post; because the server invents an ID, it replies 201 Created with the new resource. Command 4 changes just the title of post 1. Command 5 removes post 1 and replies 200 (this sandbox does not actually persist changes, but it echoes what a real server would do).
Expected output (abridged).
# From command 1:
{ "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }
# From command 3, the status line and echoed body:
HTTP/2 201
content-type: application/json; charset=utf-8
{ "title": "hello", "body": "first post", "userId": 1, "id": 101 }
Edge cases to notice. If you forget -H 'Content-Type: application/json' on the POST, the server may not parse your body and you get an empty resource back. If your JSON has a trailing comma or single quotes, the request either fails to parse or the shell mangles it — always wrap the -d payload in single quotes so the shell leaves the double-quoted JSON intact.
Let's trace the POST request (command 3) from keystroke to response.
curl -X POST .../posts — curl opens a TLS connection to the host and writes the request line POST /posts HTTP/2. The method POST tells the server: create a new resource in the posts collection.-H 'Content-Type: application/json' — adds a header announcing that the bytes in the body are JSON. Without it, the server's body parser may skip the body entirely.-d '{ "title": "hello", ... }' — supplies the request body. curl also sets Content-Length automatically to the byte count of this string.(POST, /posts) to the "create post" handler. It parses the JSON text back into a structure, reads the fields, assigns a fresh id (101 here), and stores the record.id, so the client learns the ID it will use later.Here is how the meaningful values change across the round trip:
| Stage | What exists | id value |
|---|---|---|
| Before send | client's 3-field JSON | none |
| On the wire | same JSON as text bytes | none |
| After server parse | server-side object | assigned = 101 |
| In the response | echoed object | 101 |
The DELETE (command 5) is simpler: the router matches (DELETE, /posts/1), the handler removes record 1, and returns 200 (or 204 No Content on many real APIs, meaning "done, nothing to send back"). Notice there is no body going up — the URL alone identifies what to delete.
Mistake 1 — putting the verb in the URL.
GET /api/deleteUser?id=42 <- WRONG: a GET that deletes
Why it's wrong: GET is supposed to be safe (read-only). Browsers, link-prefetchers, and crawlers fetch GET URLs automatically — one of them could silently delete data. Corrected:
DELETE /api/users/42 <- verb in the method, noun in the URL
Recognise it: any URL containing a verb (create, delete, update) paired with GET.
Mistake 2 — sending JSON without the Content-Type header.
curl -X POST .../posts -d '{"title":"hi"}' # server may ignore the body
Why it's wrong: the server doesn't know the body is JSON, so its JSON parser never runs and your fields arrive empty. Corrected: add -H 'Content-Type: application/json'. Recognise it: you POST data but the created resource comes back blank.
Mistake 3 — invalid JSON syntax.
{ 'name': 'Ada', "age": 30, } <- single quotes AND a trailing comma
Why it's wrong: JSON requires double-quoted keys and strings and forbids the trailing comma after the last pair. Corrected:
{ "name": "Ada", "age": 30 }
Recognise it: a 400 Bad Request with a message like "Unexpected token."
Mistake 4 — assuming a 2xx means your data was correct.
A server can return 200 OK while quietly ignoring a field it didn't recognise, or 201 while silently dropping your extra keys. Always read the echoed response body and confirm it contains what you expect, rather than trusting the status code alone.
Mistake 5 — confusing 401 and 403. Treating them as interchangeable hides real bugs. 401 = "you are not authenticated (log in)." 403 = "you are authenticated but not permitted." Retrying a 403 with the same credentials will never succeed; retrying a 401 after logging in might.
Common errors and how to chase them:
400 Bad Request — almost always malformed JSON or a missing required field. Paste the body into a JSON validator (or run it through python3 -m json.tool) to find the exact syntax error.401 Unauthorized — you sent no token, an expired token, or a malformed Authorization header. Check the header is spelled Authorization: Bearer <token> exactly.403 Forbidden — the token is valid but your account lacks permission. Do not keep retrying; the request itself is fine.404 Not Found — either the URL is misspelled or the resource ID doesn't exist. Confirm the path with a known-good GET on the collection first.405 Method Not Allowed — right URL, wrong verb. The endpoint exists but doesn't accept, say, DELETE. Check the API docs for allowed methods.415 Unsupported Media Type — you forgot Content-Type: application/json.A repeatable debugging routine:
-i (or -v for full detail) to curl so you can see the status line and headers, not just the body.GET on the same resource to confirm the URL and auth are correct, then change one thing at a time.Questions to ask when it doesn't work: Is the method right for the action? Is the URL pointing at a real resource? Did I announce and send valid JSON? Do I have a valid token, and does my account have permission?
This is a concept lesson, so the concern is robustness and safe handling of untrusted input rather than C memory bugs — but the mindset carries straight into the parsing exercises attached to this lesson.
Never trust the client. Every byte of a request — method, URL, headers, and JSON body — is fully attacker-controlled. Defensive habits:
"role":"admin" or "isVerified":true to a field the form never offered.http_parse_request_line, parse_request_line), the request line is raw untrusted bytes. Bounds-check every copy into a fixed buffer — a missing length check on a copied path is a classic buffer-overflow CVE. Always pass and honour the destination buffer size.These three flaws — verb tampering, mass assignment, IDOR — are defensive lab concepts here; the offensive testing is covered later in the API Security track against intentionally vulnerable practice targets only.
Where REST + JSON shows up. Nearly everywhere a program talks to another program over the internet: a mobile app fetching your feed, a single-page web app calling its backend, a payment integration (Stripe, PayPal), maps and weather services, cloud provider control planes (AWS, GCP), and AI model APIs. When you 'log in with Google' or your phone shows the latest messages, REST requests carrying JSON are doing the work underneath.
Professional best-practice habits:
Content-Type: application/json with a body. Read the status code before the body. Validate your JSON before sending. Keep secrets (tokens) out of URLs — put them in the Authorization header, never in query strings, which get logged./v1/...) so you can evolve it without breaking clients. Return consistent, machine-readable error bodies. Use correct status codes (201 for create, 204 for delete, 422 for validation errors). Paginate large collections instead of returning everything. Enforce authorization on every resource ID. Rate-limit and cap request body size. Document each endpoint's allowed methods and required fields.Readability and naming. Predictable, plural, lowercase resource names (/api/orders, not /API/getOrderList) make an API self-documenting. Consistent field naming in JSON (pick snake_case or camelCase and stick to it) saves every future client a lookup.
Beginner 1 — Classify the requests. Given these five requests, state for each one (a) the resource, (b) the action in plain English, and (c) whether it is safe and/or idempotent:
GET /api/books
GET /api/books/17
POST /api/books
PUT /api/books/17
DELETE /api/books/17
Concepts: methods as verbs, safe vs idempotent. Hint: build a small table with one row per request.
Beginner 2 — Validate JSON. Here are three candidate bodies. For each, decide whether it is valid JSON, and if not, name every error:
A) { "id": 5, "tags": ["new", "sale"] }
B) { 'id': 5 }
C) { "id": 5, "active": true, }
Concepts: JSON syntax rules. Hint: check quoting and trailing commas.
Intermediate 1 — Compose curl commands. Using the jsonplaceholder.typicode.com sandbox, write the exact curl command to (a) read comment 4, (b) create a new post with a title and body, and (c) update only the completed field of todo 1 to true. Include the correct method flag and, where needed, the Content-Type header. Concepts: method selection, request bodies, headers. Hint: creation uses POST; a partial update uses PATCH.
Intermediate 2 — Map flaws to request parts. For each of the three flaws below, name which part of a REST request it targets (method / JSON body / URL ID) and write one sentence describing a defensive check that prevents it: verb tampering, mass assignment, IDOR. Concepts: the three attack hooks, defensive validation. Hint: one flaw per request part.
Challenge — Design a mini REST API. On paper, design the endpoints for a simple 'library' service that manages books and each book's reviews. Specify: the URL and method for listing books, reading one book, creating a book, updating a book, deleting a book, listing a book's reviews, and adding a review. For the 'create book' endpoint, list exactly which JSON fields you will accept (an allow-list) and which you will reject to avoid mass assignment, and pick the correct success status code for each write operation. Concepts: resource modelling, method selection, status codes, allow-list validation. Hint: reviews are a nested collection under a book; do not accept an id or owner field from the client on create.
/api/users/42) and uses the HTTP method as the verb: GET reads, POST creates, PUT/PATCH update, DELETE removes. GET is safe; GET, PUT, and DELETE are idempotent.{}, arrays [], scalars (string/number/boolean), and null. Double-quote every key and string; no trailing commas, no comments.Content-Type: application/json, and invalid JSON (single quotes, trailing commas).