Post

HTTP & REST Fundamentals

HTTP Concepts Cheatsheet: methods, status codes, headers, caching, CORS, URL encoding, and REST API design conventions.

Overview

HTTP (HyperText Transfer Protocol) is a stateless request-response protocol. A client sends a request, a server responds. Most of the web runs on this. It is defined by the IETF through RFC documents, e.g. HTTP/2 is RFC 9113.

There are three versions in use today:

  • HTTP/1.1 handles one request at a time per connection. Most older or simpler servers use this.
  • HTTP/2 sends multiple requests over the same connection at once. Most modern web apps and APIs use this.
  • HTTP/3 uses QUIC instead of TCP, making it faster and more resilient on unstable connections like mobile networks.

HTTP Methods

MethodPurposeRequest Body
GETRetrieve a resourceNo
HEADSame as GET but no response bodyNo
POSTCreate a resource or trigger an actionYes
PUTReplace an entire resourceYes
PATCHPartially update a resourceYes
DELETERemove a resourceNo
OPTIONSDescribe allowed optionsNo

PUT vs PATCH

PUT replaces the entire resource. Sending only { "price": 999 } via PUT wipes every other field. PATCH only updates what is sent.

Notes

  • Don’t put actions in URLs. POST /api/products/456/publish is fine for an action, but GET /api/deleteProduct/456 is not. Use DELETE /api/products/456.
  • POST is not a catch-all. If the operation is a retrieval, update, or deletion, use the appropriate method.
  • PUT and PATCH are not interchangeable. If the client only has a partial payload, use PATCH.

Status Codes

2xx – Success

CodeNameWhen to Use
200OKStandard success. Return with response body.
201CreatedResource was successfully created. Include Location header pointing to new resource.
202AcceptedRequest accepted but processing is async. Used for long-running operations.
204No ContentSuccess but nothing to return. Typical for DELETE or PATCH with no response body.

200 vs 204: if there is nothing to return (delete succeeded, update acknowledged), use 204. Returning 200 with an empty body is technically wrong.

3xx – Redirects

CodeNameWhen to Use
301Moved PermanentlyResource has permanently moved. Browsers cache this aggressively, so avoid it for anything that might change.
302FoundTemporary redirect. Browser does not cache.
303See OtherRedirect after a POST (POST-Redirect-GET pattern). Forces GET on the new URL.
304Not ModifiedResource has not changed. Used with ETag/If-None-Match, no body sent, client uses its cache.
307Temporary RedirectLike 302 but preserves the original HTTP method. POST stays POST after redirect.
308Permanent RedirectLike 301 but preserves the original HTTP method.

4xx – Client Errors

CodeNameWhen to Use
400Bad RequestMalformed request: invalid JSON, missing required fields, bad syntax.
401UnauthorizedMissing or invalid authentication credentials.
403ForbiddenAuthenticated but not authorized to access this resource.
404Not FoundResource does not exist.
405Method Not AllowedHTTP method not supported for this endpoint.
409ConflictRequest conflicts with current state: duplicate resource, version mismatch.
422Unprocessable EntityRequest is structurally valid but semantically wrong (e.g., business rule violation).
429Too Many RequestsRate limit hit. Include Retry-After header.

401 vs 403: 401 means the server does not know who you are (missing or invalid credentials). 403 means it does know who you are, but you do not have permission. A logged-in user hitting an admin-only endpoint gets 403, not 401.

5xx – Server Errors

CodeNameWhen to Use
500Internal Server ErrorUnexpected server-side failure. Log it, do not expose internals to the client.
502Bad GatewayServer acting as a proxy received an invalid response from upstream. Usually an infrastructure issue.
503Service UnavailableServer is down or overloaded. Include Retry-After if recovery time is known.
504Gateway TimeoutUpstream server did not respond in time.

HTTP Headers

Common Request Headers

HeaderExamplePurpose
AuthorizationBearer eyJhbGc...Sends auth token.
Content-Typeapplication/jsonFormat of the request body.
Acceptapplication/jsonFormats the client expects in the response.
Hostapi.example.comTarget server hostname. Required in HTTP/1.1.
Cookiesession_id=abc123Sends stored cookies to the server.
If-None-Match"v1abc123"Sends stored ETag for conditional requests.
If-Modified-SinceWed, 21 Oct 2024 07:28:00 GMTSends last-known modified date for conditional requests.
Originhttps://app.example.comIndicates where the request originated (used in CORS).

Common Response Headers

HeaderExamplePurpose
Content-Typeapplication/json; charset=utf-8Format of the response body.
Set-Cookiesession_id=xyz; Path=/; Secure; HttpOnlyInstructs the client to store a cookie.
Locationhttps://api.example.com/products/456Redirect target or URL of newly created resource.
Cache-Controlmax-age=3600, publicCaching instructions.
ETag"v1abc123"Version identifier for the resource. Used for conditional requests.
Last-ModifiedWed, 21 Oct 2024 07:28:00 GMTWhen the resource was last changed.
Access-Control-Allow-Originhttps://app.example.comCORS: which origin can access this.
Retry-After60Seconds to wait before retrying (used with 429, 503).

Caching

Cache-Control

The primary header for controlling cache behaviour.

Response directives (server → client):

DirectiveMeaning
max-age=NCache is valid for N seconds.
publicAny cache (browser, CDN, proxy) can store this.
privateOnly the browser can cache. Proxies cannot. Use for user-specific data.
no-cacheCache can store it, but must revalidate with the server before each use.
no-storeDon’t cache at all. Use for sensitive data.
must-revalidateOnce stale, must revalidate before serving.

Conditional Requests (ETag / Last-Modified)

Avoids sending a full response body when nothing has changed.

  1. Server sends ETag (resource version hash) and/or Last-Modified in the response.
  2. Client stores both. On next request, sends If-None-Match: <etag> and/or If-Modified-Since: <date>.
  3. Server checks if the resource has changed:
    • No change → 304 Not Modified (empty body, client uses its cache).
    • Changed → 200 OK with updated resource.
1
2
3
4
5
6
7
8
9
10
11
12
# Initial response
200 OK
ETag: "v2abc"
Cache-Control: max-age=3600

# Client re-requests after cache expires
GET /api/resource
If-None-Match: "v2abc"

# If unchanged
304 Not Modified
(no body)

CORS

CORS (Cross-Origin Resource Sharing) is a browser mechanism that controls which cross-origin requests are allowed. Browsers enforce the same-origin policy: a page at https://app.example.com cannot make requests to https://api.example.com without explicit server permission.

Two URLs share the same origin only if protocol, domain, and port all match.

Simple Request

Applies to GET/HEAD/POST with standard headers. Browser adds the Origin header automatically.

1
2
3
4
5
GET /api/products
Origin: https://shop.example.com

-> 200 OK
Access-Control-Allow-Origin: https://shop.example.com

Browser checks if the response origin matches. If not, it blocks the response from client-side code (the request still hit the server).

Preflight Request

Applies to PUT, PATCH, DELETE, or requests with custom headers. Browser sends an OPTIONS request first.

1
2
3
4
5
6
7
8
9
10
OPTIONS /api/products/456
Origin: https://shop.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type

-> 200 OK
Access-Control-Allow-Origin: https://shop.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400

If the preflight passes, the browser sends the actual request.

CORS Response Headers

HeaderPurpose
Access-Control-Allow-OriginWhich origins are allowed. * = any, but incompatible with credentials.
Access-Control-Allow-MethodsAllowed HTTP methods.
Access-Control-Allow-HeadersAllowed request headers.
Access-Control-Allow-CredentialsWhether cookies/auth headers can be sent. Requires a specific origin, not *.
Access-Control-Max-AgeSeconds to cache the preflight response, reducing round trips.

Notes

  • Setting Access-Control-Allow-Origin: * alongside Access-Control-Allow-Credentials: true does not work. Browsers reject this combination.
  • Any custom request header (e.g. X-Request-ID) must be listed in Access-Control-Allow-Headers, otherwise the preflight will fail.
  • Some frameworks do not automatically handle OPTIONS requests. This needs to be configured explicitly on the server.

URL Structure & Encoding

A URL has several distinct parts:

1
2
3
https://shop.example.com:443/api/products?category=phones&sort=price#reviews
\_____/ \______________/ \_/ \__________/ \________________________/ \_____/
scheme       host       port    path             query string        fragment
PartDescription
SchemeProtocol: http or https.
HostDomain or IP address of the server.
PortOptional. Defaults to 80 for HTTP, 443 for HTTPS.
PathIdentifies the resource on the server.
Query stringKey-value pairs after ?, separated by &. Used for filters, pagination, etc.
FragmentStarts with #. Processed entirely by the browser, never sent to the server. Used to jump to a section of a page.

URL Encoding

URLs can only contain a limited set of characters. Anything outside that set must be percent-encoded: the character is replaced with % followed by its hex value.

CharacterEncodedCommon Scenario
Space%20 or + (in query strings)Search terms, names with spaces
#%23When # is meant as data, not a fragment
&%26When & appears inside a query param value
=%3DWhen = appears inside a query param value
/%2FWhen / is part of a value, not a path separator
+%2BWhen a literal + is needed vs space
@%40Emails in query params
1
2
3
4
5
# Raw
GET /api/products?name=iPhone 16&tag=new&improved

# Encoded
GET /api/products?name=iPhone%2016&tag=new%26improved

Fragment (#)

The fragment is stripped by the browser before the request is sent. The server never sees it. It is purely client-side, used for anchor links within a page or in single-page apps for client-side routing. E.g.

1
2
3
https://ryo-wijaya.me/#experience
# Server receives: GET /
# Browser scrolls to: <section id="experience">

REST API Design

REST is an architectural style, not a standard. These are the conventions I see most applications follow.

Resources, Not Actions

URLs should represent resources (nouns). The HTTP method already expresses the action, so the URL does not need to repeat it.

1
2
3
4
5
6
7
8
9
10
11
# Product catalog example
GET    /api/products               # list all products
GET    /api/products/iphone-16     # get a specific product
POST   /api/products               # create a product
PUT    /api/products/iphone-16     # replace a product
PATCH  /api/products/iphone-16     # update fields on a product
DELETE /api/products/iphone-16     # delete a product

# Nested resource
GET    /api/products/iphone-16/reviews    # reviews for a product
POST   /api/products/iphone-16/reviews   # add a review

Avoid putting verbs in paths like /api/getProducts or /api/deleteProduct/iphone-16.

Query Parameters

Use query params for filtering, sorting, and pagination, not path segments.

1
2
3
GET /api/products?category=phones&in_stock=true
GET /api/products?sort=price&order=asc
GET /api/products?page=2&limit=20

Versioning

URL versioning (most common):

1
2
GET /api/v1/products
GET /api/v2/products

Consistent Error Format

1
2
3
4
5
6
7
8
{
  "status": 400,
  "error": "VALIDATION_ERROR",
  "message": "Price must be a positive number",
  "details": [
    { "field": "price", "reason": "must be > 0" }
  ]
}

Common Flows

Login (Token-Based)

1
2
3
4
5
6
7
8
9
10
POST /api/auth/login
Content-Type: application/json
{ "email": "john@example.com", "password": "secret" }

-> 200 OK
{ "token": "eyJhbGc..." }

# Subsequent request
GET /api/account/me
Authorization: Bearer eyJhbGc...

File Upload

1
2
3
4
5
6
7
8
9
10
11
POST /api/products/iphone-16/images
Content-Type: multipart/form-data; boundary=----Boundary

------Boundary
Content-Disposition: form-data; name="file"; filename="front.jpg"
Content-Type: image/jpeg
[binary data]
------Boundary--

-> 201 Created
Location: /api/products/iphone-16/images/front.jpg

Rate Limiting

1
2
3
4
5
6
7
-> 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 42

# After limit exceeded
-> 429 Too Many Requests
Retry-After: 60

Comments powered by Disqus.