Open the IRCTC app and tap a train number to check seat availability. Within a second or two, a seat count appears on your screen. Your phone did not calculate that number — it has no idea how many berths are free on the Rajdhani Express this morning. Instead, your phone sent a short, precisely worded request across the internet to a server sitting in a data centre somewhere, and the server sent back exactly the information that was asked for, nothing more, nothing less. That exchange — a precise question and a precise answer, repeated by millions of apps billions of times a day — is what an API makes possible. This chapter is about how to design that exchange well, using a set of rules called REST.
From asking a person to asking a server
Imagine two students, Priya and Rohan, working on a class project. Rohan keeps a notebook of data. Priya cannot see the notebook directly, so every time she needs something, she asks Rohan a clear question: "What is the value on page 4?" Rohan answers with exactly that value. Priya never needs to know how Rohan organised his notebook, whether he keeps it in a spiral binder or a diary — she only needs to know what question to ask and what answer to expect. This is exactly what an API (Application Programming Interface) is: an agreed set of questions one piece of software can ask another, and the answers it can expect back, without either side needing to know how the other one works internally.
A web API is the same idea, except Priya is your phone app and Rohan is a server on the internet. The "questions" are HTTP requests — small messages sent over the network — and the "answers" are HTTP responses, usually carrying data in a format called JSON (JavaScript Object Notation), which is just text organised as key-value pairs, easy for both humans and programs to read. REST (Representational State Transfer) is not a programming language or a tool — it is a set of design conventions for how those questions and answers should be structured so that any two systems, built by different teams, in different companies, in different countries, can talk to each other predictably. When people say an API is "RESTful," they mean it follows these conventions.
The core idea: everything is a resource
The single biggest shift in thinking that REST asks you to make is this: stop thinking about actions and start thinking about things. A poorly designed API is organised around verbs — "getBook," "deleteBook," "searchBooks" — as if each one were a separate remote function you are calling, like a menu of commands. A REST API is organised around nouns instead. Every piece of data your system manages — a book, a student, a train, a loan record — is a resource, and every resource gets its own unique address on the web, called a URL (Uniform Resource Locator). For a school library's system, the book with ID 102 might live permanently at:
https://library.school.edu/api/books/102
That address does not say "get" or "fetch" anywhere in it — it simply names the thing: book number 102. The action you want to perform on that thing — read it, change it, delete it — is expressed separately, using something called an HTTP method. This separation of "which thing" (the URL) from "what to do to it" (the method) is the foundation everything else in this chapter builds on.
The four verbs that do almost everything: CRUD and HTTP methods
Almost every operation any app performs on data falls into one of four categories, often remembered by the acronym CRUD: Create, Read, Update, Delete. Add a new student record — that's a Create. Look up a book's details — that's a Read. Mark a loan as returned — that's an Update. Remove a book from the catalogue — that's a Delete. HTTP already provides a matching method for each of these, and a well-designed REST API uses them exactly as intended rather than inventing its own vocabulary:
GET— read a resource. Never changes anything on the server.POST— create a new resource.PUT— replace a resource completely with a new version.PATCH— update part of a resource, leaving the rest untouched.DELETE— remove a resource.
Let's make this concrete with a system a Grade 9 student can picture clearly: a REST API for a school library's book-circulation system, with three resource types — books, students, and loans (borrow records). Here is how a librarian's app and a student's app would use each method:
GET /books/102— fetch the details of book 102.POST /books— add a brand-new book to the catalogue (the server assigns it a new ID, say 103).PUT /books/102— replace the entire record for book 102 with a fresh version supplied in the request.PATCH /books/102— change just one field of book 102, for example marking it as damaged, without touching its title or author.DELETE /books/102— remove book 102 from the catalogue entirely.
Notice something important: the word "book" or "loan" never changes across these five lines — only the method and, sometimes, the ID change. That consistency is exactly what makes REST predictable. A developer who has never seen this particular API can still correctly guess that DELETE /students/45 will remove student 45, because the pattern is uniform across every resource in the system, not just books.
Watching a full request-response cycle
Let's trace what actually happens on the wire when a student borrows a book. The student's app sends a POST request to the /loans resource, with a JSON body describing what is being created:
POST /loans
Content-Type: application/json
{
"studentId": "S2027045",
"bookId": 102
}
The server checks that book 102 exists and is available, creates a new loan record, and sends back a response. Two things matter enormously in that response: the status code and the body.
HTTP/1.1 201 Created
{
"loanId": 5001,
"studentId": "S2027045",
"bookId": 102,
"borrowedDate": "2026-08-14",
"dueDate": "2026-08-28",
"returned": false
}
The status code 201 Created is a promise, expressed as a number, that anyone building on top of this API can trust without reading the body first: "something new now exists." Notice the arithmetic hiding in that response too — the library's loan period is 14 days, so a book borrowed on 14 August has a due date of 28 August (14 + 14 = 28). A well-designed API computes and returns that due date itself, rather than leaving every client app to calculate it independently and risk getting it wrong.
What if the book was already borrowed by someone else? The server should not pretend to succeed. It should refuse clearly, with a status code that says so:
HTTP/1.1 409 Conflict
{
"error": "Book 102 is currently unavailable",
"availableOn": "2026-08-28"
}
This is a core best practice: the status code and the JSON body should always agree with each other, and the body of an error response should follow the same consistent shape every time — here, an error key with a human-readable message — rather than a different structure for every kind of failure.
A visual map of the cycle
Notice the last row. DELETE returns 204 No Content — a success code that deliberately carries no body, because once something is deleted there is nothing left to describe. Sending back an empty body on purpose, with a status code that says so, is itself good design: it tells the client app "do not bother parsing anything here."
A misconception to correct: verbs do not belong in the URL
A very common mistake — one you will see in real student projects and even some poorly designed commercial APIs — is writing URLs like:
GET /getBookById?id=102
POST /deleteBook?id=102
This looks reasonable at first glance, but it is not REST — it is what is called an RPC (Remote Procedure Call) style, where every URL is really a disguised function name. It has two concrete problems. First, it is redundant: the word "get" in getBookById repeats information already carried by the HTTP method GET, and worse, the second example uses POST — the "create" method — to perform a delete, which actively lies about what the request does. Second, it does not scale predictably: every new operation needs a brand-new, differently named endpoint (getBookById, getAllBooks, searchBooksByGenre...), and a developer has to read documentation for each one individually, because nothing about the naming is uniform. The correct REST versions are:
GET /books/102
DELETE /books/102
Here, the URL is purely a noun — "book 102" — and the HTTP method alone carries the verb. This is called the uniform interface principle, and it is the single most important habit to build: whenever you catch yourself typing a verb into a URL path, stop and ask whether an HTTP method already says that verb for you.
A second, smaller misconception worth naming: many students think "REST" simply means "an API that sends JSON over HTTP." It doesn't. You can send JSON over HTTP using verb-stuffed URLs like the ones above and still not be RESTful, because REST is about the design conventions — resource-based URLs, correct method usage, correct status codes, statelessness (explained next) — not about the data format. JSON is simply the most common convention today because it is compact and easy to parse; REST itself does not require it.
Statelessness: why the server forgets you between requests
Picture the counter at your school canteen during lunch break. Each time you step up, you tell the person behind the counter your full order — "one samosa, one lassi" — and hand over your ID card if needed. The person does not remember what you ordered yesterday, or even five minutes ago; every single request is self-contained. Now compare this to a phone call with a friend, where context carries forward automatically — you don't have to reintroduce yourself in every sentence.
A REST API is designed to behave like the canteen counter, not the phone call. This property is called statelessness: the server does not remember anything about a client between one request and the next. Every request must carry everything the server needs to process it — including, typically, an authentication token proving who is asking. If the student's app wants to check its loan history, it cannot say "show me my loans" and rely on the server remembering who "me" is from an earlier login screen — it must include an identifying token in every single request, every time.
This might sound like extra, wasteful work, but it is a deliberate trade-off with a real payoff: because no server needs to remember any particular student, a library system serving five thousand schools can spread incoming requests across hundreds of interchangeable servers, and any one of them can handle any request, because none of them are holding private memories of past conversations. If a server crashes and restarts, no student's session is lost, because nothing was ever stored there in the first place. Statelessness is what lets REST APIs scale to enormous numbers of simultaneous users — a property that matters enormously for systems like IRCTC or UPI, which must handle millions of requests within the same few seconds during peak booking windows.
Status codes: numbers that tell the truth
Every HTTP response carries a three-digit status code, and a well-designed API chooses these carefully rather than always returning 200 OK and burying the real result inside the JSON body. The codes fall into clear families, and Grade 9 students should be comfortable with the ones used constantly in practice:
200 OK— the request succeeded (typical forGET,PUT,PATCH).201 Created— a new resource was successfully created (typical forPOST).204 No Content— the request succeeded and there is nothing to send back (typical forDELETE).400 Bad Request— the request itself was malformed, for example a required field likebookIdwas missing from the JSON body.401 Unauthorized— no valid identity was provided at all; the request must first prove who is asking.403 Forbidden— the identity is known, but that person is not allowed to do this — for example, a student trying toDELETEa book record, an action reserved for librarians.404 Not Found— the requested resource, such as/books/999, does not exist.409 Conflict— the request is valid but clashes with the current state, as in the already-borrowed book example above.500 Internal Server Error— something broke on the server's side, unrelated to anything the client did wrong.
The discipline here matters because these codes are machine-readable before they are human-readable. A well-written client app can check "was the status code in the 200s?" as a single, reliable test for success, without needing to parse and interpret the body's wording every time — and can react differently and correctly to a 401 (send the user to a login screen) versus a 404 (show "book not found") versus a 500 (show "try again later"), three situations that call for three completely different responses from the app.
Idempotency: what happens if a request is sent twice
Trains on shaky mobile networks sometimes cause an app to send the same request twice without the user noticing — a tap that seems to fail, followed by a retry. This is where a subtle but important REST property called idempotency matters: an idempotent operation produces the same end result no matter how many times it is repeated.
GET, PUT, and DELETE are all idempotent. Calling DELETE /books/102 once removes book 102 and returns 204. Calling it a second time finds that book 102 no longer exists, so it returns 404 Not Found — a different status code, but the same real-world end state: book 102 does not exist, whether you deleted it once or attempted it five times. PUT /books/102 with the same body sent three times in a row leaves book 102 in exactly the same final state as sending it once.
POST, by contrast, is deliberately not idempotent — because its entire job is to create something new. Sending POST /loans with the same body twice creates two separate loan records, with two different IDs, 5001 and 5002, because each call is a request to make a new thing exist. This is precisely why the earlier borrow example used POST and the return-a-book example used PATCH: creating a loan should only ever happen once per borrow, but if a "mark as returned" request accidentally fires twice, the end state — returned: true — stays correct either way.
Filtering, pagination, and nested resources
A real library catalogue does not hold five books — it might hold 4,700. Returning all 4,700 records every time a student opens the app would be wasteful and slow. REST handles this with query parameters, extra information attached after a ? in the URL, used for narrowing down or paging through a collection rather than identifying a single resource:
GET /books?genre=fiction&available=true&page=2&limit=10
Read this piece by piece: genre=fiction and available=true filter the collection down to only fiction books currently on the shelf; page=2 and limit=10 control pagination — "give me 10 results per page, and show me page 2." If a filtered search matches 47 books, page 1 covers books 1 through 10, page 2 covers books 11 through 20, and so on, with the final page — page 5 — covering only 41 through 47, since 47 divided into groups of 10 leaves a partial last group of 7. Note carefully that query parameters are used for filtering, sorting, and paging a collection, never for identifying a specific resource — /books/102 uses a path segment for that, not /books?id=102, precisely because 102 identifies one particular book, not a search condition.
Some resources naturally belong inside another, and REST expresses that with nested paths. A student's list of currently borrowed books is naturally a sub-collection of that student, so it is addressed as:
GET /students/S2027045/loans
rather than as a separate, disconnected endpoint. This nesting should typically go only one level deep in practice — a path like /students/S2027045/loans/5001/book/pages/12 becomes hard to read and hard to reason about, and it is usually clearer to give deeply related resources their own top-level addresses, such as /loans/5001, and let the response body reference the related IDs.
Consistency and versioning
Two final habits separate a well-designed REST API from a merely functional one. First, resource names in URLs should be plural nouns, consistently — /books, not /book, so that the same pattern (GET /books for the collection, GET /books/102 for one member) applies everywhere without exceptions to memorise. Second, every error response across the entire API should use the same JSON shape, so a client app can write one piece of code to handle all errors rather than special-casing each endpoint.
Real systems also evolve. Suppose the library API later needs to change how a book's dueDate is calculated, in a way that would break older versions of the student app still installed on some phones. Rather than changing /books and silently breaking everyone, APIs are commonly given a version number directly in the path:
https://library.school.edu/api/v1/books/102
https://library.school.edu/api/v2/books/102
Older apps keep calling v1 and keep working exactly as before; new apps call v2 and get the improved behaviour. This is the same reasoning that lets a large system like IRCTC roll out a redesigned booking flow to new app updates while older phones that haven't updated yet continue to function without suddenly breaking.
Try it yourself
- The library API needs an endpoint to let a librarian correct a typo in a single book's title, without touching any other field. Which HTTP method should this use, and why is it the wrong choice to use
PUThere?
Answer:PATCH, because only one field changes; usingPUTwould require sending the entire book record, and if any field were accidentally left out of that request,PUT's "replace completely" behaviour could wipe it out. - A poorly designed API has an endpoint
POST /markBookDamaged?id=102. Rewrite this as a proper REST request (method + URL).
Answer:PATCH /books/102with a body such as{"damaged": true}— the URL names the resource, the method carries the verb. - A student calls
GET /books/9999for a book that does not exist. What status code should the server return, and what family of codes does it belong to?
Answer:404 Not Found, part of the 4xx family, meaning the client's request itself pointed at something invalid. - Why is
GETconsidered both "safe" and "idempotent," whilePOSTis neither?
Answer:GETonly reads data and never changes server state, so calling it any number of times is harmless and always produces the same result;POSTis explicitly meant to create a new resource each time it is called, so repeating it creates duplicates. - A catalogue holds 133 books, and a client requests
GET /books?limit=25&page=6. How many books should this page return, and why?
Answer: 8 books. Pages 1–5 cover books 1–125 (5 × 25), leaving books 126–133, which is 8 books, on page 6.
Summary
REST is a design discipline built on a single core shift: model your data as addressable resources (nouns), and let the HTTP method — GET, POST, PUT, PATCH, DELETE — carry the action (the verb), rather than inventing verb-shaped URLs. Every response should carry a status code that honestly matches what happened, from 200 and 201 for success through 404 and 409 for client-side problems to 500 for server failures. Each request must be self-contained and statelessness must never be broken by having the server "remember" a client between calls, which is what allows REST systems to scale across many interchangeable servers. Idempotent methods (GET, PUT, DELETE) must behave safely even if a flaky network causes a retry, while POST intentionally does not share that guarantee. Collections are filtered and paged through query parameters, never through the resource-identifying path segment, and closely related resources may be nested one level deep. Finally, consistent plural naming and explicit versioning (/api/v1/...) are what let a REST API keep serving old clients correctly while it grows to serve new ones — the same discipline that lets systems like IRCTC's booking service stay reliable for millions of simultaneous users every single day.