API Design
What Makes a Good API: Resources, URLs and the Contract
What an API contract is, REST in plain words, naming resources and URLs, and the rules that make an API predictable.
On this page
- Words used in this lesson
- What an API really is: a contract
- The bad API: verbs in the URL, always 200
- The good API: nouns in the URL, verbs from HTTP
- REST in plain words
- Resources and URLs: the naming rules
- The URL cheat sheet for a small shop
- Where does the version go, and what format?
- What good looks like: four small habits
- Try it
- Interview questions
- Key takeaways
- What's next
In one line: an API is a promise between two programs. A good API keeps that promise simple and predictable: things (resources) have names, a small set of verbs act on them, and the answer always says clearly what happened. If a stranger can guess your next URL and be right, you designed it well.
Almost every app you will ever build talks to another program over an API. Your React page asks a server for data. Your server asks a payment company to charge a card. A phone app asks for a list of messages. Each time, two programs must agree on the rules. Who may ask? What do they send? What do they get back? What happens when something goes wrong?
Those rules are the API. Most developers do not design an API on purpose. They add one endpoint at a time, and after a year there are 90 endpoints with 90 different styles. This series teaches the opposite: a few simple rules, applied the same way every time. We will use REST first (the style most web APIs use), then look at GraphQL and gRPC in the last lessons so you know when not to use REST.
This is lesson 0 of a ten-lesson series. You only need basic JavaScript and a rough idea of what a web server is. In this lesson we build two tiny servers that do the same job, one with a bad design and one with a good design, and call them with curl. The next lesson, HTTP Methods, Status Codes and Idempotency in Practice, goes deep on the verbs and the numbers.
If you want to see how this fits a bigger backend, Backend Lesson 2 (REST API design) is a short companion. For the HTTP basics under all of this, Browser Lesson 6 (HTTP semantics) is useful.
What I ran / checked. Everything below ran on Linux with Node.js v22.22.0, using only the built-in node:http module (no framework) and curl. I pasted real output; header lines like date and content-length are removed to keep it short. Sources I read (all in my own words): the Microsoft Azure REST API Guidelines (Guidelines.md in the microsoft/api-guidelines repo, vNext branch), the Zalando RESTful API Guidelines (chapters on HTTP requests, status codes, JSON, pagination and compatibility, from the zalando/restful-api-guidelines repo), and Google's API Improvement Proposal AIP-122 (resource names) and AIP-131/132 (get and list methods) from the aip-dev/google.aip.dev repo. Could not reach: the RFC Editor and the IETF Datatracker were blocked from my machine, so I did not read RFC 9110 (HTTP semantics) or RFC 9457 (problem details) directly. Where I mention what those RFCs say, it comes from the guidelines above, which cite them, and from my own knowledge. Check the RFC text before you quote it in a design review. I did not check GitHub star counts, so I give none.
Words used in this lesson
- API (Application Programming Interface) is a set of rules that lets one program talk to another. In this series it means an API you call over HTTP.
- Endpoint is one address plus one method you can call, like
GET /users/1. - Client is the program that asks (a browser, a mobile app, another server). Server is the program that answers.
- Contract is the written or unwritten agreement of what you send and what you get back. If the server changes the contract without warning, clients break.
- Resource is a "thing" the API is about, such as a user, an order or an invoice.
- URL (or URI) is the address of a resource, like
/users/1. - HTTP method is the verb of the request: GET, POST, PUT, PATCH, DELETE.
- Status code is the number at the top of a response.
200means OK,404means not found. - JSON is the text format most web APIs use for data:
{"name":"Ada"}. - REST is a style for designing web APIs around resources, URLs, the standard HTTP methods and standard status codes. We explain it below.
- Collection is a list of resources of one kind, such as
/users. - Stateless means each request carries everything the server needs to answer it. The server does not remember the last request.
- Consumer is anyone who calls your API. Sometimes you do not even know who they are.
What an API really is: a contract
Think about a restaurant menu. The menu lists what you can order and how it is described. You do not go into the kitchen. If the restaurant changes "soup of the day" to "please ask the waiter", you can still order. But if the restaurant suddenly requires you to order in a new language, you are stuck.
An API is a menu for programs. The contract has four parts:
- What you can ask for. The URLs and methods.
- What you must send. The headers, query parameters and the JSON body.
- What you get back. The status code, headers and the JSON body.
- What happens when it goes wrong. The error format and the codes.
Here is the key idea of the whole series. The contract matters more than the code behind it. You can rewrite your server in another language and nobody notices, if the contract stays the same. But if you change one field name, every client may stop working, and you may not even know who they are. So we design the contract with care, and we change it with even more care (lesson 4).
An API is not "the functions I wrote". It is what other people depend on. The moment someone else calls an endpoint, it is part of your promise. Treat it like a public road: easy to enter, hard to move.
Who is the customer of an API?
The customer is another developer, often in a hurry, reading your docs at 5 pm. A good API for that person is:
- Predictable. If
/usersis a list of users,/ordersis a list of orders in the same shape. - Consistent. The same name means the same thing everywhere. Not
user_idin one place anduserIdin another anduidin a third. - Hard to misuse. The easy way is the right way.
- Clear when it fails. The error tells you what to fix.
- Stable. It does not change under your feet.
Microsoft's Azure REST guidelines say the URL is how a developer builds a mental model of your service. I like this sentence because it is true: people learn your API by looking at the URLs first.
The bad API: verbs in the URL, always 200
Many first APIs look like this. Each action is a function name in the URL. Let us build one and see what is wrong. This is a real server, with only node:http:
// bad.mjs (run: node bad.mjs)import http from "node:http";const users = new Map([[1,{id:1,name:"Ada"}],[2,{id:2,name:"Bob"}]]);let next = 3;http.createServer((req,res)=>{ const u = new URL(req.url,"http://x"); let body=""; req.on("data",c=>body+=c); req.on("end",()=>{ const send=(o)=>{res.writeHead(200,{"content-type":"application/json"});res.end(JSON.stringify(o));}; if (u.pathname==="/getUser") { const x=users.get(+u.searchParams.get("id")); return send(x?{ok:true,data:x}:{ok:false,msg:"no user"}); } if (u.pathname==="/createUser") { const d=JSON.parse(body); const x={id:next++,name:d.name}; users.set(x.id,x); return send({ok:true,data:x}); } if (u.pathname==="/deleteUser") { users.delete(+u.searchParams.get("id")); return send({ok:true}); } if (u.pathname==="/getAllUsers") return send({ok:true,data:[...users.values()]}); send({ok:false,msg:"unknown"}); });}).listen(4100);Now call it:
$ curl -i "localhost:4100/getUser?id=99"HTTP/1.1 200 OKcontent-type: application/json{"ok":false,"msg":"no user"}$ curl -i -X POST localhost:4100/createUser -d '{"name":"Cy"}'HTTP/1.1 200 OKcontent-type: application/json{"ok":true,"data":{"id":3,"name":"Cy"}}$ curl -i "localhost:4100/deleteUser?id=1"HTTP/1.1 200 OKcontent-type: application/json{"ok":true}$ curl -i "localhost:4100/getUser?id=1"HTTP/1.1 200 OKcontent-type: application/json{"ok":false,"msg":"no user"}Let us list what is wrong. There are at least seven problems, and each one will hurt later.
1. Every answer is 200 OK, even for "no user". The status code is the first thing every tool reads. Browsers, proxies, monitoring dashboards, retry libraries and API clients all use it. When "not found" says 200, your monitoring shows 100% success while users see errors. A client library cannot tell success from failure without opening the body and guessing your private ok field.
2. /getUser, /createUser, /deleteUser: the verb is in the URL. HTTP already has verbs (GET, POST, DELETE). Putting a second verb in the URL means you need a new name for every action, and every team invents different ones: /fetchUser, /removeUser, /user/delete. The consumer has to read the docs for each one.
3. GET /deleteUser?id=1 deleted data. This is the worst one. A GET request is supposed to be safe, which means it only reads. Browsers, search engine crawlers, link prefetchers and caches assume that GET is safe. They will call it freely. A browser extension that "pre-loads" links can delete your users. We cover safe methods in lesson 1.
4. The body and the query mix two styles. One endpoint takes id in the query, another takes name in a JSON body. There is no pattern to learn.
5. Success and error share one shape that hides the real status. The {"ok": false, "msg": ...} wrapper is a private convention. Nobody else's tools understand it.
6. Nothing says what was created. After createUser you get the user in the body, but there is no address (no Location header) for the new user.
7. Unknown calls also return 200. The last line of the server sends {"ok":false,"msg":"unknown"} with status 200. A typo in a URL looks like a working server.
The good API: nouns in the URL, verbs from HTTP
Now the same job in the REST style. The idea: the URL names a thing (a noun). The HTTP method says what to do with it. The status code says what happened.
// good.mjs (run: node good.mjs)import http from "node:http";const users = new Map([[1,{id:1,name:"Ada"}],[2,{id:2,name:"Bob"}]]);let next = 3;const json=(res,code,o,h={})=>{res.writeHead(code,{"content-type":"application/json",...h});res.end(JSON.stringify(o));};http.createServer((req,res)=>{ const u = new URL(req.url,"http://x"); let body=""; req.on("data",c=>body+=c); req.on("end",()=>{ const parts=u.pathname.split("/").filter(Boolean); // "/users/1" -> ["users","1"] if (parts[0]!=="users"||parts.length>2) return json(res,404,{error:"not found"}); const id=parts[1]?+parts[1]:null; if (!id) { if (req.method==="GET") return json(res,200,{items:[...users.values()]}); if (req.method==="POST") { const d=JSON.parse(body); const x={id:next++,name:d.name}; users.set(x.id,x); return json(res,201,x,{location:`/users/${x.id}`}); } return json(res,405,{error:"method not allowed"},{allow:"GET, POST"}); } const x=users.get(id); if (req.method==="GET") return x?json(res,200,x):json(res,404,{error:"user not found"}); if (req.method==="DELETE") { users.delete(id); res.writeHead(204); return res.end(); } json(res,405,{error:"method not allowed"},{allow:"GET, DELETE"}); });}).listen(4101);And the calls:
$ curl -i localhost:4101/users/99HTTP/1.1 404 Not Foundcontent-type: application/json{"error":"user not found"}$ curl -i -X POST localhost:4101/users -d '{"name":"Cy"}'HTTP/1.1 201 Createdcontent-type: application/jsonlocation: /users/3{"id":3,"name":"Cy"}$ curl -i -X DELETE localhost:4101/users/3HTTP/1.1 204 No Content$ curl -i -X PUT localhost:4101/usersHTTP/1.1 405 Method Not Allowedcontent-type: application/jsonallow: GET, POST{"error":"method not allowed"}Read what changed:
404for a missing user. Any HTTP tool understands this.POST /usersreturns201 Createdand aLocationheader with the address of the new user.DELETE /users/3returns204 No Content: it worked, and there is nothing to send back.PUT /usersreturns405 Method Not Allowedwith anAllowheader that lists what you can do. The server is teaching the client.
The code is about the same size. The difference is that the second server speaks the language the whole web already knows.
I left a bug in on purpose (it is a real one)
I wrote const id=parts[1]?+parts[1]:null; and did not think about letters. Look at what a wrong URL does:
$ curl -i localhost:4101/users/abcHTTP/1.1 200 OKcontent-type: application/json{"items":[{"id":1,"name":"Ada"},{"id":2,"name":"Bob"}]}+"abc" is NaN, and NaN is "falsy" in JavaScript, so my code thought "no id" and treated the call like GET /users. A typo returned the whole list with 200. This is exactly the kind of quiet bug that APIs grow. The fix is to check the id and say 400 Bad Request:
const id = parts[1] === undefined ? null : Number(parts[1]);if (id !== null && !Number.isInteger(id)) return json(res, 400, { error: "id must be a whole number" });$ curl -i localhost:4102/users/abcHTTP/1.1 400 Bad Requestcontent-type: application/json{"error":"id must be a whole number"}Two lessons from one small bug. First, validate the input and answer with the right status (lesson 2 does this properly). Second, the good design is not "good" because it is REST. It is good only if you also handle the edges.
REST in plain words
You will hear "REST" in every API interview, and also a lot of confusion. Let us keep it simple and honest.
REST means "Representational State Transfer". It was described by Roy Fielding in his PhD thesis in 2000 as a style for networked systems. The "web API" meaning most people use today is a looser version: resources with URLs, standard HTTP methods, standard status codes, and a stateless server.
Here are the main ideas, in plain English:
- Resources. Everything is a thing with an address. A user, an order, a photo.
- Representations. You never get "the user itself". You get a representation of it, usually JSON. You may ask for a different one (JSON, CSV) with the
Acceptheader. - Uniform interface. Every resource is used with the same small set of methods. You do not need a special verb for each thing.
- Stateless. Every request carries what the server needs (for example the login token). The server does not keep "conversation memory" between two requests. This is why you can run ten copies of the server behind a load balancer.
- Cacheable. A response says whether it can be reused.
GETresponses can be cached by browsers and CDNs. - Client and server are separate. They agree on the contract and nothing else.
There is also an idea called HATEOAS (links inside responses tell the client what it can do next). Fielding counts it as a core part of REST. In practice, most APIs that people call "REST" do not use it fully, and that is fine. In an interview you can say: "I design resource-oriented HTTP APIs. I use links for pagination and for the location of created things, but I do not build a fully hypermedia-driven client."
People argue about whether an API is "truly RESTful". In real work, nobody pays you for purity. Aim for resource-oriented, consistent and boring. If an interviewer asks, show that you know the textbook meaning, and that you know what teams really ship.
Resources and URLs: the naming rules
The URL names the thing. This is where most API design happens, so let us take it slowly.
Rule 1: use nouns, not verbs
| Bad | Good |
|---|---|
GET /getUsers | GET /users |
POST /createOrder | POST /orders |
POST /deleteOrder?id=7 | DELETE /orders/7 |
GET /searchProducts?q=shoe | GET /products?q=shoe |
The method is the verb. The URL is the noun.
Rule 2: use plural collection names
/users is the collection. /users/42 is one member. Use the plural everywhere, even for the single item, so people do not have to remember when to switch. Microsoft's guidelines say the collection name should be unabbreviated and pluralised. Google's AIP-122 says the same: collection names are plural, and an individual resource is named by its collection plus an id.
Rule 3: nest only to show ownership, and not deeper than needed
If a thing only exists inside another thing, nesting helps:
GET /users/42/orders the orders of user 42GET /orders/9001 one order, directDo not nest more than two levels. /users/42/orders/9001/items/3/refunds/8 is a mess. Once an order has its own id, give it its own top-level address. Google's guidance on resource names shows nesting of parent and child collections, and also keeps it as a clean path of collection-id pairs, but you do not need to copy that large scheme for a small API.
Rule 4: pick one case style and keep it
Microsoft prefers kebab-case for URL path segments (/purchase-orders). Zalando requires snake_case for JSON field names (customer_number). Others use camelCase for fields. There is no standard that settles this. The only real rule is to choose one and use it everywhere. The Zalando text says plainly that the goal is that the JSON "looks as if it came from the same hand".
In this series I will use: lowercase plural nouns with hyphens in URLs (/purchase-orders) and snake_case for JSON fields (created_at). You may choose camelCase for JSON if your team writes JavaScript everywhere. Just never mix.
Rule 5: the URL does not carry the action, but you may need an action
Sometimes there is a real action that is not "create, read, update, delete": cancel an order, send an email, restart a server. Two honest choices:
- Model it as a resource.
POST /orders/9001/cancellationcreates a "cancellation" for that order. Or change a field:PATCH /orders/9001with{"status":"cancelled"}. - Allow a clearly marked action.
POST /orders/9001:cancel(Google and Microsoft both describe a colon-and-verb form for custom actions).
Either is fine. Pick a pattern and use it for all actions. What is not fine is GET /cancelOrder?id=9001.
Rule 6: keep the URL for identity and the query string for options
Use the path to say which thing. Use the query string for how to show it: filters, sorting, page size, field selection.
GET /orders/9001 which orderGET /orders?status=paid&sort=-created_at filter and sort a listGET /orders?limit=20 page size(Lesson 3 covers pagination, filtering and sorting in depth.)
Rule 7: do not leak internals
A URL is part of the contract. /api/v1/mysql_users_table_2 tells the client about your database. If you rename the table, you must not rename the URL. Name things by what they mean to the user, not by how you stored them.
Rule 8: ids
Use an id that is hard to guess and never reused when it can be sensitive, such as a random UUID or a long random string. A counter like /orders/1, /orders/2 shows your business size and invites people to try /orders/3 (we saw this "change the number" attack in Security Lesson 4). Whatever the id looks like, the server must still check that this user is allowed to see that order.
Microsoft also advises to keep URLs readable and to avoid UUIDs when you can, because humans read URLs in logs. That is a trade-off, not a contradiction: readable ids are nicer to debug, random ids are safer to expose. For private data, safety wins.
The URL cheat sheet for a small shop
Here is a plan you could write down before you code:
| Action | Method and URL | Success | Notes |
|---|---|---|---|
| List products | GET /products | 200 | paginated (lesson 3) |
| Get one product | GET /products/{id} | 200 | 404 if missing |
| Create a product | POST /products | 201 + Location | body has the fields |
| Replace a product | PUT /products/{id} | 200 or 204 | whole object |
| Change a few fields | PATCH /products/{id} | 200 | only changed fields |
| Delete a product | DELETE /products/{id} | 204 | safe to repeat |
| A user's orders | GET /users/{id}/orders | 200 | nesting shows ownership |
| Cancel an order | POST /orders/{id}:cancel | 200 | a real action |
You can already see the pattern. A new developer who learned products can guess how orders work.
Where does the version go, and what format?
Two more small decisions you should make on day one:
- Format. Use JSON for requests and responses and say so with
Content-Type: application/json. Zalando's rules say to always return a JSON object at the top level and not a bare array, so you can add fields later without breaking anyone. Look at our good server: the list returns{"items":[...]}, not[...]. That choice makes the next change cheap. - Version. Lesson 4 is about this in full. For now: do not rush to add
/v1/just to feel safe. Think about how you will change the API without a new version. Zalando even says to avoid versioning if you can, and to prefer compatible changes. Microsoft takes the opposite route and puts a date in a query parameter. We compare them later.
Let us show why the top-level object matters with a tiny test. Imagine v1 returned a bare array:
// v1: [] -> a client does: const users = await res.json(); users.map(...)// you want to add "next_cursor" later. Where does it go?There is no place for it. A bare array has no room for extra fields. A top-level object {"items":[...]} can grow to {"items":[...],"next_cursor":"abc"} and old clients still read items. That is a one-line decision on day one that saves a breaking change in month six.
What good looks like: four small habits
- Write the contract first. Before you write code, write a list like the table above. Show it to the person who will call it. Lesson 7 teaches OpenAPI, which is a standard file format for this.
- Be boring. Use the standard method and the standard status. The best API is the one you can use without reading the docs.
- Make the failure path as nice as the success path. Lesson 2 gives you one error format for the whole API.
- Assume you are wrong. You will not predict every use. Leave room: objects instead of arrays, optional fields, a stable error code.
Try it
- Take this list of endpoints from a made-up app and rewrite each as a REST-style method and URL:
GET /getAllInvoicesPOST /invoice/createGET /invoice/delete?id=5POST /updateInvoiceStatus?id=5&status=paidGET /searchInvoicesByCustomer?customer=12
- For each of your rewritten endpoints, write the success status code.
- Run
good.mjsfrom this lesson (copy it from above). CallGET /users/1/orders. What status do you get? Is it the right one? - Add a
PUT /users/{id}handler to the good server that replaces the user. What status will you return when the id does not exist? Think about it, then read lesson 1 to compare. - Pick any public API you use (GitHub, Stripe, your payment provider). List five of its URLs. Do they follow the nouns-plural-nesting pattern? Where do they break it?
Answers
GET /invoices,POST /invoices,DELETE /invoices/5,PATCH /invoices/5with{"status":"paid"}, andGET /invoices?customer_id=12.200,201(withLocation),204,200(with the updated invoice) or204,200.- The good server answers
404with{"error":"not found"}, becauseparts.length>2makes the route unknown. That is the right status for "no such route". If you wanted to support it, you would add a handler for/users/{id}/ordersand return the list or an empty list. - The choice is between
404(it is a replace, and the thing is not there) and creating it with201. Both are valid designs, and lesson 1 explains whyPUTmay create. What matters is that you document it and do it the same way for every resource. - Almost every large public API breaks a rule somewhere, usually because of age. That is normal and useful to notice. It shows you what is a rule and what is a habit.
Interview questions
What is a REST API? (Must know)
It is a web API designed around resources. Each resource has a URL, I act on it with the standard HTTP methods (GET to read, POST to create, PUT or PATCH to change, DELETE to remove), and the answer uses standard status codes. The server is stateless, so every request carries what it needs, and responses say whether they can be cached. In practice I add that most APIs people call REST do not use hypermedia fully, and I aim for consistent and predictable rather than pure.
What is wrong with POST /getUser and GET /deleteUser?id=1? (Trap)
The first puts a verb in the URL and uses POST for a read, so caches and tools cannot treat it as a safe read. The second is worse: GET must be safe, meaning it only reads. Crawlers, browser prefetching and caches call GET freely, so a link could delete data. The fix is GET /users/1 and DELETE /users/1.
Why should an API use real status codes instead of 200 with {"ok":false}? (Must know)
Because the whole HTTP ecosystem reads the status code: browsers, proxies, retry libraries, monitors and alerting. If every answer is 200, dashboards show 100% success while users get errors, a client cannot tell what to retry, and caches may store an error. The body can carry detail (lesson 2), but the status code must be honest.
Singular or plural resource names? (Must know)
Plural, for the collection and for the member: /users and /users/42. It is one rule to remember. Microsoft's and Google's guidelines both use plural collection names. The important point is to pick one and be consistent.
How would you model an action like "cancel an order" in a REST API? (Senior signal)
I would first try to model it as a resource or a state change: PATCH /orders/9001 with {"status":"cancelled"}, or POST /orders/9001/cancellation, which creates a cancellation record and also gives me a place to store the reason and the time. If it is a real action with side effects, a custom action like POST /orders/9001:cancel is acceptable. What I never do is use GET, and I use the same pattern for all actions in the API.
Why return {"items":[...]} instead of a bare array? (Senior signal)
A bare array has no place for extra fields. When I later want to add a next_cursor, a total or a warning, I can add it to an object without breaking any existing client, because old clients only read items. Zalando's guidelines require a top-level object for this reason. It is a one-line choice on day one that avoids a breaking change later.
How deep should URL nesting go? (Must know)
I nest to show ownership, such as /users/42/orders, and I stop at about two levels. If a child has its own id, I also expose it directly at /orders/9001. Deep nesting makes URLs long, makes clients carry many ids, and couples the URL to the data model.
What is the difference between the path and the query string in an API URL? (Must know)
The path identifies which resource. The query string says how I want to see it: filters, sorting, page size, fields. /orders/9001 is one order. /orders?status=paid&sort=-created_at is a view of the collection. A useful test: if two URLs with different query strings are the same resource, the query is options, not identity.
Are sequential ids like /orders/1 a security problem? (Trap)
By themselves they are not a vulnerability. The vulnerability is a missing ownership check on the server (the "change the number" attack, called IDOR). Sequential ids make it easy to try, and they leak how many orders you have. So I check access on every request, and for private data I prefer random ids. I never rely on "nobody will guess the URL".
What is "the contract" of an API, and why does it matter more than the code? (Senior signal)
The contract is everything a caller can rely on: URLs, methods, request and response shapes, status codes, error format and behaviour such as ordering and limits. I can rewrite the code behind it freely as long as the contract holds. But a changed field name or status can break callers I do not control and cannot see. So I design the contract on purpose, write it down (OpenAPI), and change it only in compatible ways.
What does "stateless" mean, and what does it buy you? (Must know)
Each request carries everything the server needs, such as the auth token and all parameters, so the server does not depend on memory of earlier requests. That lets me run many identical server copies behind a load balancer and send any request to any of them, and it makes failures and retries simpler. State still exists, but it lives in a database or cache, or in the token, not in the server's memory for one client.
Key takeaways
- An API is a contract. Design the contract, then the code. Anyone who depends on it makes it hard to change.
- Nouns in the URL, verbs from HTTP, numbers from HTTP status codes. A wrapper like
{"ok":false}hides the truth from every tool. GETmust only read. A delete behind a GET is a real bug waiting for a crawler.- Use plural collection names, nest only for ownership and stop at two levels, and keep one naming style.
- Return a JSON object at the top level, not a bare array, so you can add fields later.
- Validate ids and inputs and answer with the right status. My own good server returned a list for
/users/abcuntil I checked. - REST is a style, not a certificate. Aim for consistent, predictable and boring.
What's next
Next: HTTP Methods, Status Codes and Idempotency in Practice, Lesson 1. You will see what happens when a client retries POST and PUT after a network failure, why one creates a duplicate and the other does not, and which status codes you really need. Practise now: rewrite three endpoints from a project you know into nouns and methods, before you read on.
Published by Jainik · Last updated · How these lessons are made
Keep learning
Related lessons
- Clean Code for Backends: Naming, Small Functions and Boundaries · Backend in Depth
- REST API Design Best Practices: Resources, Versioning, Status Codes · Backend in Depth
- REST vs GraphQL (vs Hasura): How to Choose · Backend in Depth
Part of the study guide
Related series