HTTP becomes easier when I stop seeing it as fetch(url) and start seeing a request and response contract travelling through caches, proxies, gateways, servers and browsers.
Before we begin: let us read the message, not just call fetch
These are my self-notes, but I am writing them as if you and I are reviewing an API together. You may know how to call an endpoint and still feel unsure about why one request uses PUT, why a retry duplicates data, or why a response says 200 when the operation failed. Those are not silly questions. They are the questions that separate a working demo from a dependable contract.
For every topic, we will ask a learner's question first. I will answer in plain language, then we will read an actual HTTP exchange and discuss the failure case. Please do not memorize status codes as disconnected numbers. Ask what story the method, target, headers, status and representation tell together.
Our recurring mentoring questions are: What does this message promise? What may an intermediary safely do? What happens if it arrives twice, arrives late, or its response is lost?
Once those answers are clear, HTTP stops feeling like plumbing. It becomes a precise language for cooperation across an unreliable network.
1. “What should I understand about a request and a response?”
Begin with a conversation, not a library. The client sends one complete request describing a target and intention. The server sends one response describing the outcome. fetch, Axios, Express and ASP.NET wrap that conversation, but they do not replace it. When debugging, peel the wrapper away and read the actual method, URL, headers, status and body.
GET /api/lessons/42 HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer <token>
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, max-age=60
{"data":{"id":"42","title":"HTTP"}}
A request has a method, target, headers and optional body. A response has a status, headers and optional body. HTTP is stateless at the protocol level; cookies, tokens and server stores create continuity.
2. “Can we unpack methods communicate intent?”
GET: retrieve a representation; safe and idempotent.POST: submit or create; not inherently idempotent.PUT: replace a resource at a known target; idempotent.PATCH: partially update; semantics depend on the patch format.DELETE: request removal; idempotent in intended effect.
PUT /api/learners/7/preferences
Content-Type: application/json
{"theme":"dark"}
3. “Why does it matter to understand status codes are part of the API?”
200 OK: successful response with content.201 Created: resource created; often includeLocation.204 No Content: success with no body.400 Bad Request: malformed request.401 Unauthorized: authentication required/invalid.403 Forbidden: identity known but action denied.404 Not Found: resource unavailable or intentionally concealed.409 Conflict: state conflict.422 Unprocessable Content: syntactically valid but semantically invalid.429 Too Many Requests: rate limit.500/503: server failure or temporary unavailability.
200 with { success:false } for every failure. Clients, caches and observability depend on HTTP semantics.
4. “How should I think about design resource contracts?”
GET /api/courses?cursor=abc&limit=20
GET /api/courses/42
POST /api/courses
PATCH /api/courses/42
DELETE /api/courses/42
POST /api/courses/42/publication
Use nouns for resources and explicit sub-resources or commands for meaningful domain operations. Do not expose database tables blindly.
{
"data": [{ "id": "42", "title": "HTTP" }],
"page": { "nextCursor": "eyJpZCI6IjQyIn0" }
}
Cursor pagination is stable under concurrent inserts when based on a deterministic ordered key. Bound limit on the server.
5. “Can you explain headers carry representation and policy?”
Content-Type describes the sent body. Accept describes desired response formats. Authorization carries credentials. Cache-Control, ETag, Last-Modified and Vary guide caching. Correlation headers help tracing.
ETag: "lesson-42-v7"
GET /api/lessons/42
If-None-Match: "lesson-42-v7"
The server can return 304 Not Modified without resending the representation.
6. “What should I understand about caching is correctness?”
Name the cache: browser, service worker, CDN, reverse proxy, server data cache or database buffer. Specify key, privacy, lifetime and invalidation.
Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=30
Never mark personalised responses public. Include relevant negotiation dimensions in Vary. Content-hashed static assets can use long immutable caching; HTML usually needs revalidation.
7. “Can we unpack authentication, cookies and CSRF?”
Cookies are automatically attached to matching requests, which makes them convenient and creates CSRF considerations. SameSite, anti-CSRF tokens and origin checks form layered protection. Bearer tokens must be protected from theft and should be short-lived and scoped appropriately.
Authentication says who. Authorization must still check whether that identity may access this exact resource.
8. “Why does it matter to understand cORS is permission to read?”
Browsers restrict cross-origin reads. A server opts into selected origins, methods and headers. Credentialed requests cannot use wildcard origins.
app.use(cors({
origin: "https://app.example.com",
credentials: true,
}));
CORS does not stop curl, another server or a malicious client from sending requests. Protect the endpoint with authentication, authorization and validation.
9. “How should I think about resilient clients?”
async function requestJson<T>(url: string, schema: z.ZodType<T>, signal: AbortSignal): Promise<T> {
const response = await fetch(url, { signal, headers: { Accept: "application/json" } });
if (!response.ok) throw new HttpError(response.status, await response.text());
return schema.parse(await response.json());
}
Use timeouts/cancellation. Retry only transient failures and idempotent operations unless protected by an idempotency key. Add exponential backoff and jitter. Respect Retry-After.
Idempotency-Key: 7cfa1c4b-...
The server stores the key and prior outcome so a repeated payment request cannot charge twice.
10. “Can you explain aPI errors and observability?”
{
"error": {
"code": "LESSON_ALREADY_COMPLETE",
"message": "This lesson is already complete.",
"requestId": "req_123",
"fields": {}
}
}
Return safe stable codes. Log richer server context with the same request/trace ID. Do not expose stack traces, SQL or secrets.
11. “What should I understand about versioning and compatibility?”
Prefer additive evolution: add optional response fields and new endpoints before removing old contracts. Consumers should tolerate unknown response fields. Use URL, header or media-type versioning when breaking change is unavoidable, and publish a deprecation timeline.
12. “Can we unpack practice project?”
Build a course API with cursor pagination, validation, ETags, role-based authorization, consistent errors, request IDs and an idempotent enrolment operation. Test 401, 403, 404, 409, 422, 429 and 503, not only 200.
Mastery statement: HTTP is an application protocol with meaningful methods, status codes, headers, representations and cache/security rules. A good API uses those semantics to make behavior predictable.
13. “Why does it matter to understand mentoring analogy: the postal service?”
An HTTP message resembles registered post. The address identifies a destination, the envelope carries metadata, the body carries content and a receipt reports an outcome. The analogy is incomplete but useful.
method → requested kind of operation
URL → target resource
headers → delivery and representation instructions
body → submitted representation or command data
status → outcome category
The postal worker does not understand the business meaning of a signed contract. Similarly, HTTP transports representations; the application interprets domain rules.
14. “How should I think about uRLs are identifiers?”
https://api.example.com:443/courses/42/lessons?status=published#overview
- scheme:
https - authority/host:
api.example.com - port:
443 - path:
/courses/42/lessons - query:
status=published - fragment:
overview
const url = new URL("/api/search", window.location.origin);
url.searchParams.set("q", userQuery);
url.searchParams.set("page", "1");
Do not concatenate unescaped user input into URLs.
15. “Can you explain safe, idempotent and cacheable are different?”
These words predict what clients, browsers and intermediaries may do. Safe means the client asks to observe rather than change state. Idempotent means repeating a request preserves the same intended effect. Cacheable means a stored response may answer a later equivalent request under cache rules. One method can have one property without another, so do not use the words as synonyms.
A safe method intends read-only semantics. An idempotent method has the same intended effect when repeated. Cacheability is governed by method and response rules.
A GET endpoint that sends email is badly designed because crawlers, prefetchers and retries may activate it. A DELETE may return 204 the first time and 404 later while remaining idempotent in effect: the resource stays absent.
16. “What should I understand about representation is not the resource itself?”
A course is a domain concept. JSON is one representation.
GET /courses/42
Accept: application/json
{
"id": "42",
"title": "HTTP from First Principles",
"links": {
"self": "/courses/42",
"lessons": "/courses/42/lessons"
}
}
Do not expose every database column. Design stable client contracts. Dates, money, decimals and identifiers need explicit serialisation choices.
{
"price": { "amountMinor": 1999, "currency": "GBP" },
"publishedAt": "2026-07-28T14:00:00Z"
}
17. “Can we unpack content negotiation?”
Content-Type describes the body being sent. Accept describes what the client can receive.
POST /api/courses
Content-Type: application/json
Accept: application/json
Return 415 Unsupported Media Type for a body format the endpoint does not support and 406 Not Acceptable when supported response negotiation cannot satisfy the request, where appropriate.
18. “Why does it matter to understand conditional updates prevent lost writes?”
Imagine you and I edit the same document. You save version seven as version eight. I still hold version seven. If my later request blindly replaces the document, your work disappears. If-Match lets my request say, “Apply this only if the resource is still the version I read.” A failed precondition is the server protecting us from silent data loss.
Two editors can load version seven of a course. If both save, the later request may overwrite the earlier one.
GET /api/courses/42
ETag: "course-42-v7"
PATCH /api/courses/42
If-Match: "course-42-v7"
Content-Type: application/json
{"title":"Updated title"}
If the resource changed to version eight, the server can reject the stale update with 412 Precondition Failed. This is optimistic concurrency using HTTP validators.
19. “How should I think about pUT versus PATCH workshop?”
PUT commonly represents replacement of the target representation:
PUT /api/learners/7/preferences
{"theme":"dark","density":"comfortable","emailUpdates":false}
PATCH describes a partial modification. A simple merge-style patch and JSON Patch have different rules.
PATCH /api/learners/7/preferences
Content-Type: application/merge-patch+json
{"theme":"light"}
Document whether null clears a field, is a value or means “not provided.” Ambiguity causes data loss.
20. “Can you explain pagination deeply understood?”
Offset pagination is easy:
GET /api/lessons?offset=40&limit=20
But inserts can shift later rows, causing duplicates or omissions. Cursor pagination anchors the next request to the last ordered item.
SELECT id, title, created_at
FROM lessons
WHERE (created_at, id) < ($1, $2)
ORDER BY created_at DESC, id DESC
LIMIT 21;
Use a stable unique tie-breaker such as id. Encode cursors opaquely so clients do not depend on internal format. Return a bounded limit.
21. “What should I understand about filtering, sorting and search?”
GET /api/lessons?status=published&sort=-publishedAt,title&q=closure
Validate allowed fields and operators. Do not interpolate arbitrary sort names into SQL. Map public names to known columns.
const sortColumns = {
title: lessons.title,
publishedAt: lessons.publishedAt,
} as const;
Search semantics should be documented: exact, prefix, full-text or semantic.
22. “Can we unpack rate limits and quotas?”
Rate limiting protects availability and abuse-sensitive operations.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Keys may include account, token, IP, route and risk signals. IP-only limits can punish shared networks and be bypassed. Apply tighter controls to authentication, password recovery and expensive searches.
23. “Why does it matter to understand retry correctly?”
The first question is not “How many retries?” It is “Can repeating this operation cause another effect?” A dropped response creates uncertainty: the server may have completed the work. Safe retry therefore comes from method semantics, idempotency keys and domain constraints—not from a generic loop around every fetch.
Retries multiply traffic during outages. Retry only likely transient conditions, cap attempts, add exponential backoff and jitter, and respect the operation's idempotency.
async function retry<T>(operation: () => Promise<T>, attempts = 3): Promise<T> {
let lastError: unknown;
for (let attempt = 0; attempt < attempts; attempt += 1) {
try {
return await operation();
} catch (error) {
lastError = error;
if (attempt === attempts - 1) break;
const delay = 200 * 2 ** attempt + Math.random() * 100;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
This educational helper still needs cancellation, retry classification and server guidance in production.
24. “How should I think about idempotent creation?”
Payment and enrolment clients may not know whether a timed-out request succeeded.
POST /api/enrolments
Idempotency-Key: 4f66c9f1-...
The server atomically associates the authenticated caller, route, request fingerprint and key with the outcome. Reuse with different input should be rejected. Store keys for an appropriate lifetime.
25. “Can you explain cookies and sessions?”
Set-Cookie: session=opaque-value; Path=/; HttpOnly; Secure; SameSite=Lax
An opaque cookie can reference server-side session state. Signed or encrypted cookies have different revocation and size trade-offs. Rotate identifiers after login or privilege elevation. Do not put sensitive readable data in an unsigned cookie.
26. “What should I understand about cORS conversation?”
Junior: My API rejects my browser request. Should I set Access-Control-Allow-Origin: *?
Mentor: First identify which origin should read the response and whether credentials are used. CORS is a browser read permission, not a general API firewall. A wildcard is inappropriate for credentialed private data.
const allowedOrigins = new Set([
"https://app.studydesk.example",
"https://admin.studydesk.example",
]);
Return the exact validated origin and Vary: Origin. Handle preflight requests consistently. Keep server authorization regardless of CORS.
27. “Can we unpack problem Details-style errors?”
{
"type": "https://studydesk.example/problems/course-title-invalid",
"title": "Course title is invalid",
"status": 422,
"detail": "Use between 3 and 120 characters.",
"instance": "/api/courses/request-123",
"errors": { "title": ["Use between 3 and 120 characters."] }
}
Keep machine-readable codes stable and human messages safe. Localise at a deliberate layer. A request ID lets support find trusted logs.
28. “Why does it matter to understand aPI security checklist?”
- authenticate where required;
- authorise the exact resource and tenant;
- validate path, query, headers and body;
- bound body, page and file sizes;
- use parameterised database operations;
- rate-limit sensitive work;
- prevent mass assignment;
- return safe errors;
- use TLS;
- audit important actions;
- avoid private shared-cache responses.
29. “How should I think about testing an HTTP contract?”
it("returns a conflict when a stale version is updated", async () => {
const response = await request(app)
.patch("/api/courses/42")
.set("If-Match", '"course-42-v6"')
.send({ title: "Stale change" });
expect(response.status).toBe(412);
});
Test status, headers and body. Test unauthorized, forbidden, absent, malformed, conflicting, rate-limited and unavailable cases.
30. “Can you explain seven-day API study plan?”
- Day 1: inspect raw requests and responses.
- Day 2: design resources, methods and statuses.
- Day 3: implement runtime validation and consistent errors.
- Day 4: add cache validators and conditional updates.
- Day 5: add authentication, authorization and CORS.
- Day 6: implement pagination, rate limits and idempotency.
- Day 7: contract-test failures and explain each semantic choice.
31. “What should I understand about let us rebuild the mental model slowly?”
Imagine ordering a book from a library desk. You identify the book, state what you want to do, provide any membership proof and wait for the librarian's answer. The librarian gives you a result plus useful instructions: when the book is due, whether another edition exists or why the request failed.
HTTP provides a general message system with the same shape:
- a target identifies a resource;
- a method states the requested action's semantics;
- headers carry metadata and policy;
- an optional content body carries a representation or command data;
- a status code reports the outcome category;
- response headers and content explain what comes next.
32. “Can we unpack hTTP semantics versus protocol version?”
HTTP/1.1, HTTP/2 and HTTP/3 use different framing and transport techniques, but share core semantics: methods, status codes, fields and resources.
HTTP/1.1 examples are often written as readable text:
GET /courses/42 HTTP/1.1
Host: api.studydesk.example
Accept: application/json
HTTP/2 uses binary framing, multiplexes concurrent streams over a connection and compresses fields. HTTP/3 maps HTTP semantics over QUIC and avoids some TCP-level head-of-line effects between streams. Application code should not invent different meanings for GET merely because transport changes.
This distinction protects your learning. Understand what a message means first. Inspect the negotiated protocol when performance or infrastructure requires it.
33. “Why does it matter to understand the path from client to origin?”
A request rarely travels through an empty pipe. It may pass through:
Browser → local/network cache → CDN → load balancer
→ reverse proxy/API gateway → application → database
On the response journey, any permitted cache may shorten future trips. A gateway may authenticate, rate-limit or route. A reverse proxy may terminate TLS or compress responses. Each intermediary needs trustworthy metadata.
Use standard headers and semantics so generic infrastructure can help. A CDN understands Cache-Control; it cannot infer that your custom X-Reuse-For: maybe means freshness.
At the same time, remember the security boundary: never assume a header from the public internet was added by your trusted proxy. Strip or overwrite trusted-context headers at the edge.
34. “How should I think about anatomy of a request?”
A request contains a method, target, fields and possibly content.
POST /api/courses HTTP/1.1
Host: api.studydesk.example
Content-Type: application/json
Accept: application/json
Authorization: Bearer ey...
Idempotency-Key: 8b56c7...
{"title":"HTTP from First Principles","visibility":"private"}
Read this aloud:
“Create or process something at the courses collection. I am sending JSON, I prefer JSON back, here is my credential, and this logical operation has a stable retry key.”
Content-Type describes the sent representation. Accept describes what response media types the client can process. They answer different questions.
Content length and framing
The receiver must know where content ends. HTTP versions provide framing mechanisms. Application developers should normally let mature servers and clients manage this. Incorrect framing at intermediaries can create serious request-smuggling vulnerabilities, so avoid hand-parsing HTTP messages in application code.
35. “Can you explain anatomy of a response?”
HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/courses/crs_82
Cache-Control: no-store
Request-Id: req_9a7
{"id":"crs_82","title":"HTTP from First Principles","visibility":"private"}
Read this aloud:
“The request created a resource. Here is its representation, here is its location, do not store this response, and here is a correlation identifier.”The status and headers are part of the contract, not decoration around JSON. A client that ignores them loses caching, redirection, authentication challenges, retry guidance and content information.
36. “What should I understand about resource and representation?”
A resource is the thing identified by a URI. A representation is data describing its current or intended state.
The course /courses/42 might be represented as JSON, HTML or another media type:
GET /courses/42
Accept: application/json
{"id":"42","title":"HTTP","published":true}
The JSON is not the course itself. It is one representation at one time. This distinction explains content negotiation, caching validators and update conflicts.
Do not design routes as remote procedure names by habit:
/api/getCourse
/api/createCourse
/api/deleteCourse
A resource-oriented alternative uses the method for intent:
GET /api/courses/42
POST /api/courses
DELETE /api/courses/42
Sometimes an operation is naturally a resource too:
POST /api/courses/42/publication
This is clearer than pretending every domain action is CRUD. REST is not a ban on verbs in human language; it is an architectural style involving resources, representations, a uniform interface, stateless messages and other constraints.
37. “Can we unpack designing useful URLs?”
URLs are identifiers and should be stable, understandable and safely encoded.
/api/courses/42/lessons/7
/api/courses?status=published&page=2
Use path segments for identity or hierarchy and query parameters for filtering, sorting, paging or optional views. This is a convention, not a law of physics.
Never concatenate unencoded user text:
const params = new URLSearchParams({
q: searchTerm,
sort: "publishedAt:desc",
});
const response = await fetch(`/api/courses?${params}`);
The URL API handles encoding. On the server, parse and validate every value. A numeric-looking ID is still untrusted text until validated.
URI identity and trailing slashes
/courses and /courses/ are different identifiers at protocol level even if your server redirects or normalizes them. Choose a policy and make redirects explicit. Be equally deliberate about case sensitivity and percent encoding.
38. “Why does it matter to understand method semantics in plain language?”
GET
Retrieve a current representation. It is safe: the client does not request a state change. Logging and usage counters may occur, but the requested semantics remain read-only.
GET /api/courses/42
Do not place destructive behavior behind GET. Browsers, crawlers and prefetchers may follow links.
HEAD
Ask for the headers that a corresponding GET would return, without response content. Useful for metadata checks, but not automatically cheaper if the server must calculate the same representation.
POST
Submit content for target-specific processing. Commonly creates a subordinate resource, starts an operation or submits a command. It is not inherently idempotent.
POST /api/enrollments
Content-Type: application/json
{"courseId":"42"}
PUT
Create or replace the state of the target resource with the supplied representation. It is idempotent by semantics.
PUT /api/users/7/preferences
Content-Type: application/json
{"theme":"dark","density":"comfortable"}
Sending the same complete desired state repeatedly should have the same intended effect.
PATCH
Apply a partial modification described by a defined patch media type or contract. Idempotency depends on the patch document.
PATCH /api/courses/42
Content-Type: application/merge-patch+json
{"title":"A Better Title"}
“Increment progress by one” is not idempotent; “set title to this value” can be.
DELETE
Request removal of the association represented by the target. Repeating DELETE is idempotent in intended effect, even if the first response is 204 and the next is 404.
OPTIONS
Describes communication options and is also used by browser CORS preflight. Do not require ordinary application authentication in a way that prevents valid preflight policy responses.
39. “How should I think about safe, idempotent and cacheable?”
These properties are independent.
- Safe: the requested semantics are read-only.
- Idempotent: repeating an identical request has the same intended effect as sending it once.
- Cacheable: a response may be stored and reused under HTTP cache rules.
GET is safe, idempotent and cacheable in principle. PUT and DELETE are idempotent but unsafe because they request state changes. POST is not inherently idempotent, though its response can be cacheable under explicit conditions.
Idempotency does not mean the response is byte-for-byte identical. Timestamps, request IDs or status can differ. It means retries do not multiply the intended effect.
This matters after ambiguity. If the connection closes after a server processes a request but before the client sees the response, the client does not know what happened. Idempotent design makes retry safer.
40. “Can you explain status codes as shared vocabulary?”
Status codes are grouped by first digit:
1xx: interim information;2xx: successful handling;3xx: redirection or cache-related outcomes;4xx: the request cannot be fulfilled as sent/currently authorized;5xx: the server failed to fulfil an apparently valid request.
Important success codes
200 OK: successful response with a representation or result.201 Created: a resource was created; often includeLocation.202 Accepted: accepted for asynchronous processing, not yet completed.204 No Content: successful with no response content.
202 and then imply the operation completed. Provide an operation resource:
HTTP/1.1 202 Accepted
Location: /api/operations/op_92
Retry-After: 3
{"id":"op_92","status":"pending"}
Redirects
301and308indicate permanent redirection.302and307indicate temporary redirection.303 See Otherdirects retrieval of another resource, often with GET after a POST.304 Not Modifiedis a conditional cache result, not a general redirect and normally has no representation content.
307 and 308 preserve method and content. Historical client behavior around 301/302 can change POST to GET, which is why choosing deliberately matters.
Client-error distinctions
400 Bad Request: malformed or generally invalid request syntax/shape.401 Unauthorized: authentication is missing or invalid; despite the name, it is about authentication.403 Forbidden: identity may be known but action is not permitted.404 Not Found: target not found or intentionally concealed.405 Method Not Allowed: method unsupported for this resource; includeAllow.409 Conflict: current resource state conflicts with the request.412 Precondition Failed: a conditional request precondition did not hold.415 Unsupported Media Type: request content type is unsupported.422 Unprocessable Content: syntax understood, but content instructions/validation cannot be processed.429 Too Many Requests: rate limit exceeded;Retry-Aftermay guide the client.
Server errors
500 Internal Server Error: unexpected origin failure.502 Bad Gateway: intermediary received an invalid upstream response.503 Service Unavailable: temporary unavailability; may includeRetry-After.504 Gateway Timeout: intermediary timed out waiting upstream.
200 with { "success": false } for every failure. Generic HTTP tooling then believes the operation succeeded.
41. “What should I understand about headers: typed envelopes of metadata?”
Headers communicate representation metadata, caching, authentication, preferences, conditions and policy.
Frequently useful request fields include:
Accept: application/json
Accept-Language: en-GB
Authorization: Bearer <token>
If-None-Match: "course-v4"
Origin: https://app.example.com
Frequently useful response fields include:
Content-Type: application/json; charset=utf-8
Cache-Control: private, max-age=60
ETag: "course-v4"
Location: /api/courses/42
Retry-After: 30
Vary: Accept-Encoding
Header names are case-insensitive. HTTP/2 and later representations commonly display lower-case names in tools. Do not parse comma-separated fields with naive split(',') unless that field's grammar allows it; field syntax varies.
42. “Can we unpack content types and honest bodies?”
If you send JSON, say so:
Content-Type: application/json
If the client only accepts selected types, state them:
Accept: application/json, application/problem+json
Validate the media type before parsing. Returning HTML from an API error path while claiming JSON creates confusing failures:
const response = await fetch(url);
const type = response.headers.get("content-type") ?? "";
if (!type.includes("application/json")) {
throw new Error(`Expected JSON but received ${type || "unknown content"}`);
}
Media types can include parameters such as charset. Use well-tested parsers rather than brittle equality when appropriate.
43. “Why does it matter to understand content negotiation?”
Negotiation means one resource can have more than one representation, and the client can express preferences. The cache must know which request fields changed the selection. That is why Vary belongs to correctness: without it, a shared cache may reuse the English or compressed representation for a request that asked for something else.
In proactive negotiation, the client sends preferences and the server selects a representation.
GET /courses/42
Accept: application/json
Accept-Language: cy, en;q=0.8
The server might respond:
Content-Type: application/json
Content-Language: cy
Vary: Accept, Accept-Language
Vary tells caches that these request fields affect representation selection. Forgetting it can serve the wrong language or encoding from a shared cache. Varying on too many high-cardinality headers destroys cache efficiency.
Many APIs sensibly support one media type and choose locale through explicit user settings. Negotiation is a capability, not a commandment.
44. “How should I think about compression and transfer size?”
Clients advertise acceptable content codings:
Accept-Encoding: gzip, br
The response declares the selected coding:
Content-Encoding: br
Vary: Accept-Encoding
Compression reduces transferred bytes but consumes CPU and may be ineffective for already compressed formats. Inspect transferred size and decoded resource size in DevTools. Do not confuse content encoding with media type.
45. “Can you explain caching begins with freshness?”
A cache stores a response associated with a request and may reuse it for later equivalent requests. This reduces latency, bandwidth and origin work.
Cache-Control: public, max-age=3600
Date: Tue, 28 Jul 2026 12:00:00 GMT
max-age=3600 gives a one-hour freshness lifetime relative to the response's date/age calculations. A fresh response can normally be reused without contacting the origin.
private permits private caches, such as a browser cache, but prevents storage by shared caches. public explicitly permits shared caching where other rules might restrict it. s-maxage targets shared caches.
Personalized responses require care:
Cache-Control: private, no-cache
This allows private storage but requires validation before reuse. Highly sensitive responses may use no-store, recognizing that cache directives are only part of an overall data-handling policy.
46. “What should I understand about no-cache is not no-store?”
The naming is unfortunate:
no-cachemeans “do not reuse without successful validation.” Storage is allowed.no-storemeans “do not store this response.”max-age=0, must-revalidateis often used with similar effect to immediate staleness and required validation.
For content-hashed static assets:
Cache-Control: public, max-age=31536000, immutable
/assets/app.4f92a1.js
When content changes, the URL changes. The old URL can stay cached for a long time. HTML that points to those hashes generally needs a shorter or revalidation-based policy.
47. “Can we unpack validators and conditional GET?”
An entity tag identifies a selected representation version:
ETag: "course-42-v7"
Later the client validates:
GET /api/courses/42
If-None-Match: "course-42-v7"
If unchanged:
HTTP/1.1 304 Not Modified
ETag: "course-42-v7"
Cache-Control: private, max-age=60
The cached content is reused with updated metadata. A Last-Modified/If-Modified-Since pair provides time-based validation, usually with less precision. Prefer entity tags when you can generate reliable representation validators.
48. “Why does it matter to understand preventing lost updates with conditions?”
Two instructors open course version 7. Both edit. Instructor A saves version 8. Instructor B then saves an old copy and accidentally overwrites A.
Send the version you edited:
PATCH /api/courses/42
If-Match: "course-42-v7"
Content-Type: application/merge-patch+json
{"title":"New title"}
The server applies only if the current validator matches. Otherwise:
HTTP/1.1 412 Precondition Failed
Content-Type: application/problem+json
Now the client can fetch current state and offer conflict resolution. This is optimistic concurrency: proceed without a long-held lock, but verify assumptions at the write boundary.
49. “How should I think about cookies and server sessions?”
A server can create a cookie:
Set-Cookie: session=opaque-random-id; Path=/; Secure; HttpOnly; SameSite=Lax
The browser decides when to attach it:
Cookie: session=opaque-random-id
The opaque identifier can point to server-side session state. Secure limits it to secure transport; HttpOnly prevents normal page JavaScript reading it; SameSite affects cross-site requests.
Cookie-based continuity does not make HTTP itself stateful. Each request still carries enough information—directly or through its session identifier—for the server to process it.
Cookies are automatically attached in matching contexts, which creates CSRF considerations. Use appropriate SameSite policy plus anti-CSRF measures for relevant state-changing requests. Never use GET for destructive operations.
50. “Can you explain authentication is not authorization?”
Authentication establishes an identity or credential context. Authorization decides whether that identity may perform this action on this resource.
app.patch("/api/courses/:id", requireSession, async (req, res) => {
const course = await repository.find(req.params.id);
if (!course) return res.sendStatus(404);
if (course.organisationId !== req.user.organisationId ||
!req.user.permissions.includes("course:update")) {
return res.sendStatus(403);
}
// validate and update
});
A bearer token is called “bearer” because possession grants its authority. Protect it in transport, storage and logs. Validate signature or introspection result, issuer, audience, expiry and expected scopes according to its token system.
Do not rely on a frontend hiding a button. The API enforces authorization at the resource boundary.
51. “What should I understand about cORS without panic?”
CORS has one narrow job: a server tells the browser which other origins may let their JavaScript read a response. It is not login, role permission or a firewall. A command-line attacker is not blocked by browser CORS. Keep this sentence nearby whenever a CORS error tempts you to add a wildcard.
The same-origin policy prevents browser JavaScript from freely reading data from other origins. An origin is scheme, host and port. CORS is the HTTP-header mechanism through which a server grants selected cross-origin reading permission.
Suppose code at https://app.studydesk.example calls https://api.studydesk.example. The hosts differ, so this is cross-origin.
const response = await fetch("https://api.studydesk.example/courses");
The API can permit that frontend:
Access-Control-Allow-Origin: https://app.studydesk.example
Vary: Origin
CORS is enforced by browsers. curl, server-to-server clients and attackers are not transformed into authorized users by the absence of this header. Your API still needs authentication, authorization, validation and rate controls.
Preflight
A browser may first ask permission with OPTIONS:
OPTIONS /courses/42 HTTP/1.1
Origin: https://app.studydesk.example
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: authorization, content-type
The server responds with policy:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.studydesk.example
Access-Control-Allow-Methods: GET, PATCH
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 600
Vary: Origin
The actual request follows only if browser checks pass. Configure gateways to answer preflight consistently. Redirects, missing headers on error responses and authentication middleware placed before OPTIONS handling are common causes of confusion.
Credentials
await fetch("https://api.studydesk.example/profile", {
credentials: "include",
});
For credentialed CORS, the server must explicitly allow credentials and a specific permitted origin. Do not use a wildcard with credentials. Validate the incoming origin against an allowlist before echoing it.
What the error means
When the console says CORS blocked the response, the server may have processed the request. The browser is refusing to expose the response to JavaScript. Inspect the Network entry, preflight, redirect chain and response headers. Do not “fix” it with mode: "no-cors"; an opaque response is not readable JSON.
52. “Can we unpack the Fetch API response contract?”
fetch resolves when an HTTP response is available—even when the status is 404 or 500. It normally rejects for network-level failures or aborts.
const response = await fetch("/api/courses/42");
if (!response.ok) {
throw new Error(`Request failed with ${response.status}`);
}
const course = await response.json();
response.ok covers status 200 through 299. A robust client considers status, content type and expected schema.
async function readJson<T>(response: Response, schema: Schema<T>): Promise<T> {
const type = response.headers.get("content-type") ?? "";
if (!type.includes("application/json") &&
!type.includes("application/problem+json")) {
throw new ProtocolError(`Unexpected content type: ${type}`);
}
const value: unknown = await response.json();
return schema.parse(value);
}
TypeScript annotations do not validate network data:
// This assertion makes a promise to the compiler; it proves nothing at runtime.
const course = await response.json() as Course;
External data remains untrusted until runtime validation.
53. “Why does it matter to understand build a small resilient client?”
Keep transport mechanics in one deliberate boundary:
type ApiOptions = RequestInit & {
timeoutMs?: number;
};
async function apiFetch(path: string, options: ApiOptions = {}): Promise<Response> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 8_000);
try {
const response = await fetch(new URL(path, API_BASE_URL), {
...options,
signal: AbortSignal.any([
controller.signal,
...(options.signal ? [options.signal] : []),
]),
headers: {
Accept: "application/json, application/problem+json",
...options.headers,
},
});
return response;
} finally {
clearTimeout(timeout);
}
}
The caller's cancellation and the timeout both matter. If AbortSignal.any is outside your support target, combine signals with a small tested helper.
Do not automatically add Content-Type: application/json to GET or bodyless requests. Add it when you actually send JSON.
async function createCourse(input: CreateCourseInput): Promise<Course> {
const response = await apiFetch("/courses", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(input),
});
if (response.status !== 201) throw await toApiError(response);
return readJson(response, CourseSchema);
}
54. “How should I think about timeouts and cancellation?”
Without a deadline, a caller may wait forever from its perspective. But “timeout” is not a complete strategy. Ask which interval is bounded: connection establishment, response headers, body download or the whole operation.
const controller = new AbortController();
searchInput.addEventListener("input", () => {
controller.abort("superseded by a newer search");
});
Cancellation saves client work, but the server may already be processing the request. Do not assume aborting fetch rolls back a payment or database transaction. Server operations need their own cancellation and idempotency semantics.
Choose timeouts from user expectations and observed latency, not an arbitrary universal five seconds. A report export and type-ahead search have different budgets.
55. “Can you explain retries: repeat only when it is safe?”
A retry can turn a brief outage into a storm. Backoff gives the service room; jitter prevents synchronized waves; a total deadline stops endless patience. But none of those makes a non-idempotent payment safe to repeat. Reliability begins with semantics.
A retry is appropriate for some temporary failures, not every unsuccessful response.
Possible retry candidates include selected network errors, 429, 502, 503 and 504, depending on operation semantics. Do not retry validation 422, permission 403 or an ordinary not-found response hoping the same request will change.
function backoff(attempt: number): number {
const base = Math.min(500 * 2 ** attempt, 10_000);
return Math.random() * base; // full jitter
}
async function fetchWithRetry(url: string, init: RequestInit = {}) {
const maxAttempts = 4;
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
try {
const response = await fetch(url, init);
if (![429, 502, 503, 504].includes(response.status)) return response;
if (attempt === maxAttempts - 1) return response;
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
await delay(retryAfter ?? backoff(attempt));
} catch (error) {
if (attempt === maxAttempts - 1 || init.signal?.aborted) throw error;
await delay(backoff(attempt));
}
}
throw new Error("unreachable");
}
Exponential backoff reduces repeated pressure. Jitter prevents synchronized clients retrying together. Bound total attempts and total elapsed time. Respect Retry-After when valid.
Most importantly, verify that the operation is idempotent or protected by an idempotency mechanism before retrying.
56. “What should I understand about idempotency keys for ambiguous POST operations?”
Imagine a user submits payment. The server commits it, but the response connection fails. A blind second POST could charge twice.
The client assigns one key to one logical attempt:
POST /api/payments
Idempotency-Key: 51ccd2a2-3a0c-4f9b-b22f-8fca31e0b311
Content-Type: application/json
{"invoiceId":"inv_7","amount":4900,"currency":"GBP"}
The server stores key, authenticated owner, request fingerprint, processing state and eventual result atomically. A repeat with the same key and same content returns the recorded outcome. Same key with different content is rejected.
type IdempotencyRecord = {
key: string;
principalId: string;
requestHash: string;
status: "processing" | "complete";
responseStatus?: number;
responseBody?: unknown;
expiresAt: Date;
};
Define concurrency behavior when two copies arrive together. Idempotency keys need a scope, retention period and abuse limits. They complement transactional domain constraints; they do not replace them.
57. “Can we unpack rate limits and quotas?”
Rate limiting protects capacity and fairness. A quota may govern a longer business allowance, such as monthly exports.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/problem+json
{"type":"https://api.example.com/problems/rate-limit","title":"Too many requests","status":429}
Choose a key based on the protected resource: authenticated account, API key, organization, IP range or combination. IP-only limits can punish shared networks and are easier to evade. Different endpoints deserve different costs.
Common algorithms include fixed windows, sliding windows, token buckets and leaky buckets. The correct choice depends on allowed bursts and distributed consistency needs.
Clients should surface rate-limit state gently, respect server guidance and avoid retry storms. Servers should not reveal sensitive account information in rate-limit errors.
58. “Why does it matter to understand pagination is a consistency decision?”
Returning every record harms latency, memory and reliability. Pagination bounds work.
Offset pagination
GET /api/courses?limit=25&offset=50
Simple and supports direct page numbers. But large offsets can be expensive, and insertions/deletions between requests can create duplicates or omissions.
Cursor pagination
GET /api/courses?limit=25&after=eyJwdWJsaXNoZWRBdCI6Ii4uLiIsImlkIjoiLi4uIn0
The opaque cursor represents a stable position in a deterministic ordering. Include a unique tie-breaker:
ORDER BY published_at DESC, id DESC
WHERE (published_at, id) < ($cursorTime, $cursorId)
ORDER BY published_at DESC, id DESC
LIMIT 26
Fetch one extra row to determine hasNextPage, then return at most the requested limit.
{
"items": [{"id":"42","title":"HTTP"}],
"page": {"nextCursor":"opaque-value","hasNextPage":true}
}
Sign or validate cursors if tampering matters. Treat them as opaque in clients so internal schema can evolve.
59. “How should I think about filtering, sorting and search contracts?”
An API should document accepted filters and operators:
GET /api/courses?status=published&authorId=7&sort=publishedAt:desc
Allowlist sort fields and map them to known database expressions:
const sortColumns = {
publishedAt: courses.publishedAt,
title: courses.title,
} as const;
const column = sortColumns[input.sortField];
if (!column) throw new ValidationError("Unsupported sort field");
Never concatenate arbitrary column or direction values into SQL. Parameterization protects values; identifiers usually require allowlisting.
Full-text search has language, ranking and indexing semantics. Define whether results use prefix, stemming, fuzzy matching or exact phrases. “Search” is not merely a filter with %term% once data grows.
60. “Can you explain partial responses and ranges?”
Sometimes clients need only selected fields:
GET /api/courses/42?fields=id,title,publishedAt
This can reduce payload but increases cache variants and authorization complexity. Never let field selection bypass rules for sensitive fields.
HTTP Range requests retrieve portions of a representation and are useful for large files or media:
Range: bytes=0-999999
A successful partial response uses 206 Partial Content and Content-Range. Use established server support; byte ranges are not the same feature as paginating database records.
61. “What should I understand about asynchronous operations?”
Some work exceeds a normal request budget. Accept the command and expose an operation resource:
POST /api/reports
Content-Type: application/json
{"courseId":"42","format":"pdf"}
HTTP/1.1 202 Accepted
Location: /api/operations/op_123
Retry-After: 3
{"id":"op_123","status":"queued","progress":0}
Clients can poll with backoff, subscribe through an appropriate real-time mechanism or receive a webhook in server integrations. Define completion, failure, cancellation, expiry and authorization for the operation resource.
Do not hold an HTTP request open for minutes merely because asynchronous architecture has not been designed.
62. “Can we unpack error responses that teach clients?”
A good error serves two readers. Generic HTTP software reads the status. Application code reads a stable problem type and structured fields. A human may read the title and detail. Do not force code to parse an English sentence, and do not expose internal stack traces merely because a developer needs diagnostics. Link the public problem to protected evidence with a request identifier.
RFC 9457 defines Problem Details for HTTP APIs with media type application/problem+json.
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
Request-Id: req_f73
{
"type": "https://api.studydesk.example/problems/validation",
"title": "The request did not pass validation",
"status": 422,
"detail": "Correct the highlighted fields and try again.",
"instance": "/problems/req_f73",
"errors": {
"title": ["Enter a title between 1 and 120 characters."]
}
}
The type identifies the problem kind; title is a stable human summary; detail describes this occurrence; instance identifies the occurrence. Extension members such as errors carry machine-readable domain details.
Clients should branch on stable status/type/code, not parse English detail. Servers must not expose stack traces, SQL, secrets or private resource existence unnecessarily.
63. “Why does it matter to understand mapping errors without lying?”
Create a small, explicit taxonomy:
function toHttp(error: unknown): HttpProblem {
if (error instanceof ValidationError) return problem(422, "validation");
if (error instanceof AuthenticationError) return problem(401, "authentication-required");
if (error instanceof ForbiddenError) return problem(403, "forbidden");
if (error instanceof NotFoundError) return problem(404, "not-found");
if (error instanceof VersionConflictError) return problem(412, "version-conflict");
return problem(500, "internal-error");
}
Log an unexpected exception with a correlation ID, then return a safe generic problem. Do not convert every exception into 400; server defects are not client mistakes. Do not return 500 for expected validation; that invites useless retries and false alerts.
64. “How should I think about aPI versioning and compatible evolution?”
The easiest version to operate is the one you do not need. Prefer additive, compatible changes:
- add optional response fields;
- let clients ignore fields they do not understand;
- use tolerant readers but strict validation for dangerous input;
- avoid changing the meaning or type of an existing field;
- give deprecation notice and usage evidence.
/api/v2/courses/42
No strategy removes migration work. Versioning is a promise to run multiple contracts or coordinate transitions.
For removal, publish a timeline, identify consumers, provide migration examples, measure old-version traffic and retain rollback capacity. A response header can communicate deprecation metadata, but direct consumer communication is still valuable.
65. “Can you explain rEST maturity without ceremony?”
REST emphasizes resources, representations, stateless messages, a uniform interface, cacheability and layered systems. Hypermedia can let responses include links to available transitions:
{
"id": "42",
"title": "HTTP",
"links": {
"self": "/api/courses/42",
"lessons": "/api/courses/42/lessons",
"publish": "/api/courses/42/publication"
}
}
Do not argue about a “RESTful” label while returning wrong statuses, unsafe GET operations and uncacheable static data. Use the constraints that improve interoperability and explain intentional departures.
GraphQL, RPC and event APIs solve different interface problems. They still run over transport with authentication, deadlines, errors and observability. Choose based on client query needs, ownership and operational tradeoffs, not fashion.
66. “What should I understand about webhooks are incoming HTTP APIs?”
A webhook sender calls your endpoint when an event occurs. Treat it as an untrusted, retried, out-of-order message source.
app.post("/webhooks/payments", rawBodyMiddleware, async (req, res) => {
verifySignature(req.rawBody, req.headers);
if (await events.exists(req.body.id)) return res.sendStatus(200);
await transaction(async tx => {
await tx.events.record(req.body.id);
await tx.payments.apply(req.body);
});
res.sendStatus(200);
});
Verify signatures using the exact raw bytes and provider rules. Protect against replay with timestamps/nonces where specified. Deduplicate event IDs. Acknowledge promptly and move slow work to a durable queue. Design for retries and reordered events.
67. “Can we unpack observability across a request?”
Assign or propagate a request/trace identifier at a trusted boundary:
app.use((req, res, next) => {
req.id = validateRequestId(req.get("request-id")) ?? crypto.randomUUID();
res.set("Request-Id", req.id);
next();
});
Structured logs should answer: what route, status, duration, authenticated principal category and failure type? Do not log passwords, tokens, full cookies or unnecessary bodies.
logger.info({
event: "http.request.completed",
requestId: req.id,
method: req.method,
route: req.route?.path,
status: res.statusCode,
durationMs,
});
Metrics reveal rates and distributions: throughput, errors, latency percentiles and saturation. Traces connect time across gateways, services and databases. High-cardinality identifiers belong in logs/traces rather than metric labels.
68. “Why does it matter to understand security checklist for every endpoint?”
Ask, in order:
- Is TLS enforced at the correct boundary?
- Who is authenticated, and how is the credential validated?
- May that identity perform this action on this exact tenant/resource?
- Are path, query, headers and content validated with limits?
- Is content type supported before parsing?
- Are database and command interpreters safely parameterized?
- Could the response disclose another user's information?
- Are cache directives safe for personalized data?
- Can retries duplicate effects?
- Are rate limits and resource budgets appropriate?
- Do errors and logs avoid secrets?
- Are audit events recorded for high-value changes?
69. “How should I think about contract testing?”
Test the externally observable message, not only controller methods:
it("creates a course and advertises its location", async () => {
const response = await request(app)
.post("/api/courses")
.set("Authorization", instructorToken)
.set("Content-Type", "application/json")
.send({ title: "HTTP" });
expect(response.status).toBe(201);
expect(response.headers.location).toMatch(/^\/api\/courses\//);
expect(response.headers["content-type"]).toMatch(/application\/json/);
expect(response.body).toMatchObject({ title: "HTTP" });
});
Test malformed JSON, unsupported content type, invalid fields, anonymous, wrong role, wrong tenant, missing record, stale ETag, duplicate idempotency key, rate limit and unexpected server failure.
Use the real database when constraints and transactions are part of correctness. Consumer-driven contracts can coordinate independently deployed systems, but retain end-to-end checks for critical journeys.
70. “Can you explain a complete API endpoint workshop?”
We will design “publish a course.” Requirements:
- only an instructor in the course organization may publish;
- title and at least one lesson are required;
- publishing the same version twice should not duplicate notifications;
- an instructor must not overwrite a newer edit;
- the client needs a useful conflict response.
PUT /api/courses/42/publication
Authorization: Bearer <token>
If-Match: "course-42-v7"
Content-Type: application/json
Idempotency-Key: publish-course-42-v7
{"published":true}
Application flow:
async function publishCourse(command: PublishCommand): Promise<Course> {
return database.transaction(async tx => {
const course = await tx.courses.findForUpdate(command.courseId);
if (!course) throw new NotFoundError();
authorize(command.actor, "course:publish", course);
requireVersion(course, command.ifMatch);
ensurePublishable(course);
if (course.published) return course;
const published = await tx.courses.publish(course.id);
await tx.outbox.add({
type: "course.published",
aggregateId: course.id,
deduplicationKey: `course-published:${course.id}:${course.version}`,
});
return published;
});
}
Success:
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "course-42-v8"
Cache-Control: private, no-cache
Stale version:
HTTP/1.1 412 Precondition Failed
Content-Type: application/problem+json
This one endpoint connects resource design, idempotency, authorization, validation, transactions, conditional requests, events, caching and useful errors.
71. Debugging conversation: “The API is broken”
Junior: “Fetch says failed. The API is broken.”
Mentor: “Let us narrow it. Did the browser create a request?”
Junior: “Yes. There is an OPTIONS row and no PATCH.”
Mentor: “Then application PATCH code has not run. Inspect the preflight response.”
Junior: “It returns 401 without CORS headers.”
Mentor: “Good evidence. Our authentication middleware is challenging preflight before CORS policy is answered. Repair middleware ordering and ensure error responses include the applicable CORS policy.”
Another day:
Junior: “A POST timed out, so I retried. Now there are two enrollments.”
Mentor: “A timeout gave us an unknown outcome, not proof of failure. What uniqueness constraint or idempotency key protects the logical operation?”
The calm habit is always the same: state what is known, distinguish transport outcome from business outcome and inspect the message chain.
72. “What should I understand about devTools and command-line laboratory?”
In browser DevTools Network panel, inspect:
- method, URL, status and protocol;
- initiator and request priority;
- request/response headers;
- query, content and parsed payload;
- cookie inclusion/blocking reasons;
- CORS preflight;
- cache/service-worker source;
- DNS, connection, waiting and download timing.
curl to make protocol details visible:
curl -i https://api.example.com/courses/42
Conditional request:
curl -i \
-H 'If-None-Match: "course-42-v7"' \
https://api.example.com/courses/42
Preflight simulation:
curl -i -X OPTIONS \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: PATCH' \
-H 'Access-Control-Request-Headers: authorization,content-type' \
https://api.example.com/courses/42
curl is not subject to browser CORS enforcement, but it can show whether the server returns policy headers. Compare it with the browser, not as a replacement for browser testing.
73. “Can we unpack build the StudyDesk practice API?”
Create these resources:
GET /api/courses
POST /api/courses
GET /api/courses/{courseId}
PATCH /api/courses/{courseId}
DELETE /api/courses/{courseId}
GET /api/courses/{courseId}/lessons
POST /api/enrollments
GET /api/operations/{operationId}
Add features in this order:
- Define request and response examples before implementation.
- Validate path, query and JSON at the edge.
- Add authentication and resource-level authorization.
- Use correct status,
Locationand media type. - Add ETag validation to course reads and writes.
- Paginate the collection using a deterministic cursor.
- Add idempotency to enrollment creation.
- Return Problem Details for stable error types.
- Add rate limiting and
Retry-After. - Add request IDs, logs, metrics and traces.
- Write positive and negative contract tests.
- Test from browser, curl and a production-like proxy path.
74. “Why does it matter to understand thirty-five revision questions?”
Answer these without looking, then verify uncertain answers.
- What are the main parts of a request?
- What are the main parts of a response?
- How is HTTP semantics distinct from HTTP version framing?
- What is a resource?
- What is a representation?
- Why are
Content-TypeandAcceptdifferent? - What does GET promise?
- When is PUT suitable?
- Why is PATCH not automatically idempotent?
- Why is repeated DELETE still idempotent if statuses differ?
- How are safe and idempotent different?
- When should an API return
201? - What does
202not promise? - Why are
401and403different? - When are
409and412useful? - Why should errors not all return
200? - What is a fresh cached response?
- How do
no-cacheandno-storediffer? - What does an ETag validate?
- Why is
304not a normal redirect? - How can
If-Matchprevent lost updates? - What forms a browser origin?
- What permission does CORS grant?
- When does preflight happen conceptually?
- Why does CORS not secure server-to-server access?
- Why does fetch resolve for
404? - Which failures may justify a retry?
- Why add jitter to backoff?
- How does an idempotency key prevent duplicate effects?
- When is cursor pagination better than offset?
- What makes ordering deterministic?
- What is Problem Details designed to express?
- How can APIs evolve compatibly?
- What evidence belongs in logs, metrics and traces?
- Which exact contract tests protect your highest-risk endpoint?
75. “How should I think about four-week mentoring plan?”
Week 1: messages and meaning
- Day 1: write raw requests and responses by hand.
- Day 2: design resources and stable URLs.
- Day 3: practise method safety and idempotency.
- Day 4: classify status codes with real scenarios.
- Day 5: inspect headers and content types.
- Day 6: compare HTTP/1.1, HTTP/2 and HTTP/3 conceptually.
- Day 7: explain one complete request through intermediaries.
Week 2: correctness and caching
- Day 8: set freshness for HTML, assets and private JSON.
- Day 9: test
no-cacheversusno-store. - Day 10: implement ETag validation.
- Day 11: prevent a lost update with
If-Match. - Day 12: design cookies and server sessions.
- Day 13: test authentication and resource authorization.
- Day 14: teach why cache policy is application correctness.
Week 3: clients and resilience
- Day 15: reproduce a simple and preflighted CORS request.
- Day 16: build a typed client with runtime validation.
- Day 17: add deadlines and caller cancellation.
- Day 18: add bounded retries, backoff and jitter.
- Day 19: implement an idempotency-key store.
- Day 20: compare offset and cursor pagination.
- Day 21: implement rate-limit handling.
Week 4: production contracts
- Day 22: return consistent Problem Details.
- Day 23: design an asynchronous operation resource.
- Day 24: secure and deduplicate a webhook.
- Day 25: add request correlation and structured logs.
- Day 26: write authorization and concurrency contract tests.
- Day 27: inspect everything through DevTools and curl.
- Day 28: complete and explain the StudyDesk API aloud.
76. “Can you explain final mentoring conversation?”
Junior: “Should I memorize every status and header?”
Mentor: “No. Memorize the vocabulary you use every week and know how to consult the standard for the rest. More importantly, learn to make the method, status, headers and representation tell one consistent story.”
Junior: “What makes an API feel reliable?”
Mentor: “Clear contracts under both success and failure. Bounded work. Safe retries. Stable pagination. Correct cache policy. Authorization at every resource. Errors that clients can act on. Evidence when something fails.”
Junior: “What should I ask in a review?”
Mentor: “What happens if the request arrives twice? What if the response is lost? What if two people edit together? What if the caller belongs to another tenant? What may be cached? What will the client do with each failure?”
If you can answer those questions calmly, HTTP stops being a collection of magic numbers. It becomes a precise language for cooperation across unreliable networks.
77. “What should I understand about a day in the life of one request?”
Let us connect everything through one ordinary action: a learner clicks Mark lesson complete.
The UI first decides whether this is a command with a server effect. It is. The client creates a stable operation identifier and sends:
PUT /api/enrollments/enr_8/lessons/lesson_4/completion
Authorization: Bearer <access-token>
Content-Type: application/json
If-Match: "progress-v11"
Idempotency-Key: enr_8-lesson_4-complete-v11
{"completed":true}
Why PUT? The target identifies the completion state and the body states the desired result. Repeating it should keep that state complete. If-Match ensures the client updates the progress version it saw. The idempotency key provides additional operation deduplication across uncertain delivery.
At the gateway, request size and rate are checked. The application authenticates the token, loads the enrollment inside its tenant boundary, authorizes this learner, validates content and applies the change transactionally. An outbox event records that progress changed.
The success response:
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "progress-v12"
Cache-Control: private, no-cache
Request-Id: req_77
{"lessonId":"lesson_4","completed":true,"coursePercentage":36}
If the response is lost, the client may repeat safely. If another device changed progress first, 412 invites reconciliation. If the session expired, 401 starts reauthentication. If permission was removed, 403 prevents the action. If rate-limited, 429 plus Retry-After controls retry. One well-designed exchange contains the recovery story.
78. “Can we unpack optimistic UI and server truth?”
An optimistic interface shows the expected result before the response arrives. This can feel immediate, but it creates a temporary prediction.
async function markComplete(lessonId: string) {
const previous = progressStore.snapshot();
progressStore.completeOptimistically(lessonId);
try {
const current = await progressApi.complete(lessonId, previous.etag);
progressStore.replace(current);
} catch (error) {
progressStore.replace(previous);
showError(messageFor(error));
}
}
Define rollback before enabling optimism. If a command cannot be safely reversed in the interface—payment is a good example—show a pending state rather than pretending success.
Prevent double submission visually, but do not rely on a disabled button for correctness. Network retries, multiple tabs and impatient automation still exist. Server idempotency and domain constraints remain authoritative.
79. “Why does it matter to understand authentication refresh without a request storm?”
Some clients use short-lived access tokens plus a controlled refresh mechanism. When many requests receive 401 together, naive code may launch many refreshes.
let refreshInFlight: Promise<void> | null = null;
async function refreshOnce(): Promise<void> {
refreshInFlight ??= refreshSession().finally(() => {
refreshInFlight = null;
});
return refreshInFlight;
}
Retry an original request only when the authentication contract says refresh is appropriate and the request body can be replayed safely. Streams may not be reusable. Bound refresh attempts; a rejected refresh should lead to a clear signed-out state rather than an infinite loop.
For cookie-based sessions, a 401 might lead to navigation to login. For OAuth clients, use a proven library and flow. A generic HTTP wrapper should not quietly invent identity policy.
80. “How should I think about request deduplication and cancellation in screens?”
Suppose a component asks for the same course three times during rapid rerenders. A server-state library can share the in-flight Promise and cache result under a stable key:
const courseQuery = {
queryKey: ["course", courseId],
queryFn: ({ signal }: { signal: AbortSignal }) =>
coursesApi.get(courseId, { signal }),
staleTime: 60_000,
};
Deduplication is not HTTP caching, although both reuse work. Application query caches understand domain keys and UI freshness. HTTP caches understand request/response semantics. They can cooperate.
When navigation makes a result irrelevant, cancel consumption through an AbortSignal. Still handle late server effects correctly. A cancelled GET can simply be ignored; a cancelled mutation may already have committed.
81. “Can you explain streaming responses?”
HTTP content can arrive incrementally. The Fetch response body is a readable stream:
const response = await fetch("/api/reports/large.csv");
if (!response.ok || !response.body) throw new Error("Export failed");
const reader = response.body.getReader();
let received = 0;
while (true) {
const { value, done } = await reader.read();
if (done) break;
received += value.byteLength;
updateProgress(received);
}
Streaming reduces time to first useful content and peak memory when processing incrementally. It complicates mid-stream error handling: headers and success status may already have been sent. Formats such as newline-delimited JSON can provide record boundaries, but require explicit contracts.
Server-Sent Events provide a text event stream from server to browser. WebSockets provide a different full-duplex protocol after an HTTP-based handshake. Do not choose them merely to avoid polling; compare direction, reconnection, proxy compatibility, ordering and operational needs.
82. “What should I understand about uploads and multipart requests?”
Forms and file uploads commonly use multipart/form-data. Let the browser set the boundary when using FormData:
const data = new FormData();
data.set("title", "Course thumbnail");
data.set("image", fileInput.files[0]);
await fetch("/api/uploads", {
method: "POST",
body: data,
// Do not manually set Content-Type; fetch adds the multipart boundary.
});
The server must stream or bound the body, validate size and content, generate safe storage names and authorize association with the course. Client-declared filename and media type are untrusted.
For very large uploads, direct-to-object-storage flows can reduce application-server bandwidth. The API issues a short-lived narrowly scoped upload authorization, then the client confirms completion. Validate the final object before making it public.
83. “Can we unpack performance without premature cleverness?”
Measure the request path:
- queueing and connection time;
- time to first byte;
- content download;
- server routing, authentication and database time;
- payload sizes and compression;
- cache hits and misses;
- client parse and rendering time.
load user → load organizations → load course → load lessons
If dependencies are known, fetch independent resources concurrently or create a screen-oriented representation. Avoid building one enormous endpoint that couples every widget forever.
HTTP/2 multiplexing does not make unlimited tiny requests free. Each request has headers, scheduling, server work and client processing. Use evidence to choose granularity.
84. “Why does it matter to understand aPI anti-pattern clinic?”
Everything returns 200
{"status":"error","code":404}
This hides failure from caches, monitoring and client libraries. Use the HTTP status plus structured content.
GET changes state
GET /api/courses/42/delete
Crawlers, previews and prefetchers may follow it. Use a state-changing method with authorization and CSRF protection where applicable.
Unbounded collection
GET /api/all-events
It works in development and collapses with years of data. Bound result size and paginate from the first public contract.
Blind retries
Retrying every 500 five times multiplies load during an outage and may duplicate effects. Classify failure, verify operation safety, back off with jitter and cap attempts.
Leaking persistence models
Returning every database column couples clients to internal names and risks exposing secrets. Map to an intentional representation.
function toCourseResponse(row: CourseRow): CourseResponse {
return { id: row.id, title: row.title, publishedAt: row.publishedAt };
}
Trusting TypeScript at the network
Types do not cross HTTP. Validate received and sent data at runtime, and test malformed versions.
85. “How should I think about review template for a new endpoint?”
Write this note before implementation:
Resource:
Method and why:
Request media type/schema/limits:
Authentication:
Resource-level authorization:
Success status, headers and representation:
Validation and conflict statuses:
Idempotency/retry behavior:
Cache policy and validators:
Pagination/order if collection:
Rate/resource limits:
Observability and audit events:
Compatibility plan:
Contract tests:
If the team cannot fill a line yet, that line is a design question—not paperwork. This short template prevents many production surprises.
86. “Can you explain your final first-principles explanation?”
Practise saying this in your own words:
HTTP is a stateless application protocol for exchanging messages about resources. A request combines a target, method, fields and optional content. A response combines a status, fields and optional content. Method semantics help clients and intermediaries reason about safety and retries. Headers carry content, cache, condition, authentication and policy metadata. Status codes make outcomes interoperable. Reliable APIs add explicit authorization, validation, idempotency, concurrency control, pagination, deadlines, structured errors and observability.Do not worry if your wording differs. The test is whether you can use the model to design and debug an unfamiliar exchange.
87. “What should I understand about five small scenarios to check your judgment?”
Scenario 1: the user double-clicks Create
Disabling the button improves experience but does not provide server correctness. Give one logical creation attempt an idempotency key, enforce domain uniqueness where applicable and return the first outcome for a repeated matching key.
Scenario 2: a profile appears stale after saving
Inspect which cache owns the stale value. It may be an application query cache, browser HTTP cache, service worker, CDN or server cache. Check mutation response, invalidation, validators and Cache-Control. Do not solve an unidentified cache with a global page reload.
Scenario 3: a request works in Postman but not the browser
Compare origins and inspect OPTIONS. Postman is not constrained by browser CORS policy. Verify the actual response and every error response include the correct allowlisted origin policy. Also inspect cookie SameSite, Secure and credentials settings.
Scenario 4: page two repeats records
Confirm the ordering is deterministic and unique. Offset pagination over changing data can shift. For a moving feed, use a cursor based on the ordered fields plus a unique tie-breaker. Apply exactly the same direction and comparison in query and cursor.
Scenario 5: a retry made an outage worse
Count retries across every layer. A browser client, gateway and service SDK each retrying three times can multiply one operation dramatically. Give retry ownership to a clear layer, use deadlines, backoff and jitter, and stop when the remaining time cannot support another meaningful attempt.
These scenarios reveal an important habit: never name a fix before naming the failure mechanism. “Add retry,” “clear cache” and “enable CORS” are actions. Diagnosis explains why that action matches the evidence.
