Restful
Rest Constraints
Uniform interface
As the constraint name itself applies, you MUST decide APIs interface for resources inside the system which are exposed to API consumers and follow religiously. A resource in the system should have only one logical URI, and that should provide a way to fetch related or additional data. It’s always better to synonymize a resource with a web page.
Any single resource should not be too large and contain each and everything in its representation. Whenever relevant, a resource should contain links (HATEOAS) pointing to relative URIs to fetch related information.
Also, the resource representations across the system should follow specific guidelines such as naming conventions, link formats, or data format (XML or/and JSON).
All resources should be accessible through a common approach such as HTTP GET and similarly modified using a consistent approach.
Once a developer becomes familiar with one of your APIs, he should be able to follow a similar approach for other APIs.
Summary: API and consumers share one single, technical interface: URI, Method, Media Type
Client–server
This constraint essentially means that client application and server application MUST be able to evolve separately without any dependency on each other. A client should know only resource URIs, and that’s all. Today, this is standard practice in web development, so nothing fancy is required from your side. Keep it simple.
Servers and clients may also be replaced and developed independently, as long as the interface between them is not altered.
Summary: Client and server are separated: (Client and Server can evolve separately)
Stateless
Roy fielding got inspiration from HTTP, so it reflects in this constraint. Make all client-server interactions stateless. The server will not store anything about the latest HTTP request the client made. It will treat every request as new. No session, no history.
If the client application needs to be a stateful application for the end-user, where user logs in once and do other authorized operations after that, then each request from the client should contain all the information necessary to service the request – including authentication and authorization details.
No client context shall be stored on the server between requests. The client is responsible for managing the state of the application.
Summary: State is contained within the request
Cacheable
In today’s world, the caching of data and responses is of utmost importance wherever they are applicable/possible. The webpage you are reading here is also a cached version of the HTML page. Caching brings performance improvement for the client-side and better scope for scalability for a server because the load has reduced.
In REST, caching shall be applied to resources when applicable, and then these resources MUST declare themselves cacheable. Caching can be implemented on the server or client-side.
Well-managed caching partially or completely eliminates some client-server interactions, further improving scalability and performance.
Summary: Each response message must explicitly state if it can be cached or not
Layered system
REST allows you to use a layered system architecture where you deploy the APIs on server A, and store data on server B and authenticate requests in Server C, for example. A client cannot ordinarily tell whether it is connected directly to the end server or an intermediary along the way.
Summary: Client cannot tell what layer it's connected to
Code on demand (optional)
Well, this constraint is optional. Most of the time, you will be sending the static representations of resources in the form of XML or JSON. But when you need to, you are free to return executable code to support a part of your application, e.g., clients may call your API to get a UI widget rendering code. It is permitted.
All the above constraints help you build a truly RESTful API, and you should follow them. Still, at times, you may find yourself violating one or two constraints. Do not worry; you are still making a RESTful API – but not “truly RESTful.” Notice that all the above constraints are most closely related to WWW (the web). Using RESTful APIs, you can do the same thing with your web services what you do to web pages.
Summary: Server can extend client functionality
Richardson Maturity Model
Richardson Maturity Model. A model (developed by Leonard Richardson) that breaks down the principal elements of a REST approach into three steps. These introduce resources, http verbs, and hypermedia controls.
Level Zero
Level zero of maturity does not make use of any of URI, HTTP Methods, and HATEOAS capabilities.
These services have a single URI and use a single HTTP method (typically POST). For example, most Web Services (WS-*)-based services use a single URI to identify an endpoint, and HTTP POST to transfer SOAP-based payloads, effectively ignoring the rest of the HTTP verbs.
Similarly, XML-RPC based services send data as Plain Old XML (POX). These are the most primitive way of building SOA applications with a single POST method and using XML to communicate between services.
Level One
Level one of maturity makes use of URIs out of URI, HTTP Methods, and HATEOAS.
These services employ many URIs but only a single HTTP verb – generally HTTP POST. They give each resource in their universe a URI. A unique URI separately identifies one unique resource – and that makes them better than level zero.
Level Two
Level two of maturity makes use of URIs and HTTP out of URI, HTTP Methods, and HATEOAS.
Level two services host numerous URI-addressable resources. Such services support several of the HTTP verbs on each exposed resource – Create, Read, Update and Delete (CRUD) services. Here the state of resources, typically representing business entities, can be manipulated over the network.
Here service designer expects people to put some effort into mastering the APIs – generally by reading the supplied documentation.
Level 2 is the excellent use-case of REST principles, which advocate using different verbs based on the HTTP request methods, and the system can have multiple resources.
Level Three
Level three of maturity makes use of all three, i.e. URIs and HTTP and HATEOAS.
This level is the most mature level of Richardson’s model, which encourages easy discoverability. This level makes it easy for the responses to be self-explanatory by using HATEOAS.
The service leads consumers through a trail of resources, causing application state transitions as a result.
Status Codes
Intro
This is a list of Hypertext Transfer Protocol (HTTP) response status codes. Status codes are issued by a server in response to a client's request made to the server. It includes codes from IETF Request for Comments (RFCs), other specifications, and some additional codes used in some common applications of the HTTP. The first digit of the status code specifies one of five standard classes of responses. The message phrases shown are typical, but any human-readable alternative may be provided. Unless otherwise stated, the status code is part of the HTTP/1.1 standard (RFC 7231).[1]
The Internet Assigned Numbers Authority (IANA) maintains the official registry of HTTP status codes.[2]
All HTTP response status codes are separated into five classes or categories. The first digit of the status code defines the class of response, while the last two digits do not have any classifying or categorisation role. There are five classes defined by the standard:
- 1xx informational response – the request was received, continuing process
- 2xx successful – the request was successfully received, understood, and accepted
- 3xx redirection – further action needs to be taken in order to complete the request
- 4xx client error – the request contains bad syntax or cannot be fulfilled
- 5xx server error – the server failed to fulfil an apparently valid request
200 (OK)
It indicates that the REST API successfully carried out whatever action the client requested and that no more specific code in the 2xx series is appropriate.
Unlike the 204 status code, a 200 response should include a response body. The information returned with the response is dependent on the method used in the request, for example:
GET an entity corresponding to the requested resource is sent in the response; HEAD the entity-header fields corresponding to the requested resource are sent in the response without any message-body; POST an entity describing or containing the result of the action; TRACE an entity containing the request message as received by the end server.
201 (Created)
A REST API responds with the 201 status code whenever a resource is created inside a collection. There may also be times when a new resource is created as a result of some controller action, in which case 201 would also be an appropriate response.
The newly created resource can be referenced by the URI(s) returned in the entity of the response, with the most specific URI for the resource given by a Location header field.
The origin server MUST create the resource before returning the 201 status code. If the action cannot be carried out immediately, the server SHOULD respond with a 202 (Accepted) response instead.
202 (Accepted)
A 202 response is typically used for actions that take a long while to process. It indicates that the request has been accepted for processing, but the processing has not been completed. The request might or might not be eventually acted upon, or even maybe disallowed when processing occurs.
Its purpose is to allow a server to accept a request for some other process (perhaps a batch-oriented process that is only run once per day) without requiring that the user agent’s connection to the server persist until the process is completed.
The entity returned with this response SHOULD include an indication of the request’s current status and either a pointer to a status monitor (job queue location) or some estimate of when the user can expect the request to be fulfilled.
204 (No Content)
The 204 status code is usually sent out in response to a PUT, POST, or DELETE request when the REST API declines to send back any status message or representation in the response message’s body.
An API may also send 204 in conjunction with a GET request to indicate that the requested resource exists, but has no state representation to include in the body.
If the client is a user agent, it SHOULD NOT change its document view from that which caused the request to be sent. This response is primarily intended to allow input for actions to take place without causing a change to the user agent’s active document view. However, any new or updated metainformation SHOULD be applied to the document currently in the user agent’s dynamic view.
The 204 response MUST NOT include a message-body and thus is always terminated by the first empty line after the header fields.
301 (Moved Permanently)
The 301 status code indicates that the REST API’s resource model has been significantly redesigned, and a new permanent URI has been assigned to the client’s requested resource. The REST API should specify the new URI in the response’s Location header, and all future requests should be directed to the given URI.
You will hardly use this response code in your API as you can always use the API versioning for new API while retaining the old one.
302 (Found)
The HTTP response status code 302 Found is a common way of performing URL redirection. An HTTP response with this status code will additionally provide a URL in the Location header field. The user agent (e.g., a web browser) is invited by a response with this code to make a second. Otherwise identical, request to the new URL specified in the location field.
Many web browsers implemented this code in a manner that violated this standard, changing the request type of the new request to GET, regardless of the type employed in the original request (e.g., POST). RFC 1945 and RFC 2068 specify that the client is not allowed to change the method on the redirected request. The status codes 303 and 307 have been added for servers that wish to make unambiguously clear which kind of reaction is expected of the client.
303 (See Other)
A 303 response indicates that a controller resource has finished its work, but instead of sending a potentially unwanted response body, it sends the client the URI of a response resource. The response can be the URI of the temporary status message, or the URI to some already existing, more permanent, resource.
Generally speaking, the 303 status code allows a REST API to send a reference to a resource without forcing the client to download its state. Instead, the client may send a GET request to the value of the Location header.
The 303 response MUST NOT be cached, but the response to the second (redirected) request might be cacheable.
304 (Not Modified)
This status code is similar to 204 (“No Content”) in that the response body must be empty. The critical distinction is that 204 is used when there is nothing to send in the body, whereas 304 is used when the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.
In such a case, there is no need to retransmit the resource since the client still has a previously-downloaded copy.
Using this saves bandwidth and reprocessing on both the server and client, as only the header data must be sent and received in comparison to the entirety of the page being re-processed by the server, then sent again using more bandwidth of the server and client.
307 (Temporary Redirect)
A 307 response indicates that the REST API is not going to process the client’s request. Instead, the client should resubmit the request to the URI specified by the response message’s Location header. However, future requests should still use the original URI.
A REST API can use this status code to assign a temporary URI to the client’s requested resource. For example, a 307 response can be used to shift a client request over to another host.
The temporary URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s). If the 307 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.
400 (Bad Request)
400 is the generic client-side error status, used when no other 4xx error code is appropriate. Errors can be like malformed request syntax, invalid request message parameters, or deceptive request routing etc.
The client SHOULD NOT repeat the request without modifications.
401 (Unauthorized)
A 401 error response indicates that the client tried to operate on a protected resource without providing the proper authorization. It may have provided the wrong credentials or none at all. The response must include a WWW-Authenticate header field containing a challenge applicable to the requested resource.
The client MAY repeat the request with a suitable Authorization header field. If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials. If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity might include relevant diagnostic information.
403 (Forbidden)
A 403 error response indicates that the client’s request is formed correctly, but the REST API refuses to honor it, i.e., the user does not have the necessary permissions for the resource. A 403 response is not a case of insufficient client credentials; that would be 401 (“Unauthorized”).
Authentication will not help, and the request SHOULD NOT be repeated. Unlike a 401 Unauthorized response, authenticating will make no difference.
404 (Not Found)
The 404 error status code indicates that the REST API can’t map the client’s URI to a resource but may be available in the future. Subsequent requests by the client are permissible.
No indication is given of whether the condition is temporary or permanent. The 410 (Gone) status code SHOULD be used if the server knows, through some internally configurable mechanism, that an old resource is permanently unavailable and has no forwarding address. This status code is commonly used when the server does not wish to reveal exactly why the request has been refused, or when no other response is applicable.
405 (Method Not Allowed)
The API responds with a 405 error to indicate that the client tried to use an HTTP method that the resource does not allow. For instance, a read-only resource could support only GET and HEAD, while a controller resource might allow GET and POST, but not PUT or DELETE.
A 405 response must include the Allow header, which lists the HTTP methods that the resource supports. For example:
Allow: GET, POST
406 (Not Acceptable) The 406 error response indicates that the API is not able to generate any of the client’s preferred media types, as indicated by the Accept request header. For example, a client request for data formatted as application/xml will receive a 406 response if the API is only willing to format data as application/json.
If the response could be unacceptable, a user agent SHOULD temporarily stop receipt of more data and query the user for a decision on further actions.
412 (Precondition Failed)
The 412 error response indicates that the client specified one or more preconditions in its request headers, effectively telling the REST API to carry out its request only if certain conditions were met. A 412 response indicates that those conditions were not met, so instead of carrying out the request, the API sends this status code.
415 (Unsupported Media Type)
The 415 error response indicates that the API is not able to process the client’s supplied media type, as indicated by the Content-Type request header. For example, a client request including data formatted as application/xml will receive a 415 response if the API is only willing to process data formatted as application/json.
For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format.
500 (Internal Server Error)
500 is the generic REST API error response. Most web frameworks automatically respond with this response status code whenever they execute some request handler code that raises an exception.
A 500 error is never the client’s fault, and therefore, it is reasonable for the client to retry the same request that triggered this response and hope to get a different response.
The API response is the generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
501 (Not Implemented)
The server either does not recognize the request method, or it cannot fulfill the request. Usually, this implies future availability (e.g., a new feature of a web-service API).
Cache-Control Directives
Cacheability
A response is normally cached by the browser if:
- it has a status code of 301, 302, 307, 308, or 410 and
- Cache-Control does not have no-store, or if proxy, does not have private and
- Authorization is unset either
- has a status code of 301, 302, 307, 308, or 410 or
- has public, max-age or s-maxage in Cache-Control or
- has Expires set
public
The response may be stored by any cache, even if the response is normally non-cacheable.
private
The response may be stored only by a browser's cache, even if the response is normally non-cacheable. If you mean to not store the response in any cache, use no-store instead. This directive is not effective in preventing caches from storing your response.
no-cache
The response may be stored by any cache, even if the response is normally non-cacheable. However, the stored response MUST always go through validation with the origin server first before using it, therefore, you cannot use no-cache in-conjunction with immutable. If you mean to not store the response in any cache, use no-store instead. This directive is not effective in preventing caches from storing your response.
no-store
The response may not be stored in any cache. Although other directives may be set, this alone is the only directive you need in preventing cached responses on modern browsers. max-age=0 is already implied. Setting must-revalidate does not make sense because in order to go through revalidation you need the response to be stored in a cache, which no-store prevents.
Expiration
max-age=<seconds>
The maximum amount of time a resource is considered fresh. Unlike Expires, this directive is relative to the time of the request.
s-maxage=<seconds>
Overrides max-age or the Expires header, but only for shared caches (e.g., proxies). Ignored by private caches.
max-stale[=<seconds>]
Indicates the client will accept a stale response. An optional value in seconds indicates the upper limit of staleness the client will accept.
min-fresh=<seconds>
Indicates the client wants a response that will still be fresh for at least the specified number of seconds.
stale-while-revalidate=<seconds>
Indicates the client will accept a stale response, while asynchronously checking in the background for a fresh one. The seconds value indicates how long the client will accept a stale response. See "Keeping things fresh with stale-while-revalidate" for more information.
stale-if-error=<seconds>
Indicates the client will accept a stale response if the check for a fresh one fails. The seconds value indicates how long the client will accept the stale response after the initial expiration.
Revalidation and reloading
must-revalidate
Indicates that once a resource becomes stale, caches must not use their stale copy without successful validation on the origin server.
proxy-revalidate
Like must-revalidate, but only for shared caches (e.g., proxies). Ignored by private caches.
immutable
Indicates that the response body will not change over time. The resource, if unexpired, is unchanged on the server and therefore the client should not send a conditional revalidation for it (e.g. If-None-Match or If-Modified-Since) to check for updates, even when the user explicitly refreshes the page. Clients that aren't aware of this extension must ignore them as per the HTTP specification. In Firefox, immutable is only honored on https:// transactions. For more information, see also this blog post.
Other
no-transform
An intermediate cache or proxy cannot edit the response body, Content-Encoding, Content-Range, or Content-Type. It therefore forbids a proxy or browser feature, such as Google’s Web Light, from converting images to minimize data for a cache store or slow connection.
only-if-cached
Set by the client to indicate "do not use the network" for the response. The cache should either respond using a stored response, or respond with a 504 status code. Conditional headers such as If-None-Match should not be set. There is no effect if only-if-cached is set by a server as part of a response.
SQL vs Rest
SELECT * FROM users https://<db-name>.restdb.io /rest/users?q={}
SELECT id, user_id, status FROM users /rest/users?q={}&h={"$fields": {"user_id": 1, "status": 1} }
SELECT * FROM users WHERE status = "A" /rest/users?q={ "status": "A" }
SELECT * FROM users WHERE status != "A" /rest/users?q={"status":{"$not":"A"}}
SELECT * FROM users WHERE status = "A" AND age = 50 /rest/users?q={ "status": "A", "age": 50 }
SELECT * FROM users WHERE status = "A" OR age = 50 /rest/users?q={ "$or": [ { "status": "A" } ,{ "age": 50 } ] }
SELECT * FROM users WHERE age > 25 /rest/users?q={ "age": { "$gt": 25 } }
SELECT * FROM users WHERE age < 25 /rest/users?q={ "age": { "$lt": 25 } }
SELECT * FROM users WHERE age > 25 AND age <= 50 /rest/users?q={ "age": { "$gt": 25, "$lte": 50 } }
SELECT * FROM users WHERE user_id like "%bc%" /rest/users?q={ "user_id": {"$regex" :"bc"}}
SELECT * FROM users WHERE user_id like "bc%" /rest/users?q={ "user_id": {"$regex" :"^bc"}}
SELECT * FROM users WHERE status = "A" ORDER BY user_id ASC /rest/users?q={ "status": "A" }&sort=user_id&dir=1
SELECT * FROM users WHERE status = "A" ORDER BY user_id DESC /rest/users?q={ "status": "A" }&sort=user_id&dir=-1
SELECT COUNT(*) FROM users /rest/users?q={}&h={"$aggregate":["COUNT:"]}
SELECT COUNT(*) FROM users WHERE age > 30 /rest/users?q={"age":{"$gt": 30}}&h={"$aggregate":["COUNT:"]}
SELECT * FROM users LIMIT 1 /rest/users?q={}&max=1
SELECT * FROM users LIMIT 5 SKIP 10 /rest/users?q={}&max=5&skip=10
Set https://restdb.io/docs/querying-with-the-api for more details
Quick Cheat Sheet
Principles
Code-on-Demand
REST allows servers to send executable code (usually JavaScript) to clients, enabling dynamic behavior beyond static data. This principle is optional and often skipped for simplicity or security. When used, it empowers clients to adapt without requiring full updates. Had to get an example to understand
// server.js
const express = require('express');
const app = express();
const port = 3000;
app.get('/product/:id/review-form', (req, res) => {
const productId = req.params.id;
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Product Review</title>
</head>
<body>
<h1>Review Product ${productId}</h1>
<form id="reviewForm">
<label for="rating">Rating:</label>
<input type="number" id="rating" name="rating" min="1" max="5" required>
<br>
<label for="comment">Comment:</label>
<textarea id="comment" name="comment"></textarea>
<br>
<button type="submit">Submit Review</button>
</form>
<script>
// Code on Demand: Client-side validation logic
document.getElementById('reviewForm').addEventListener('submit', function(event) {
const rating = document.getElementById('rating').value;
if (rating < 1 || rating > 5) {
alert('Rating must be between 1 and 5.');
event.preventDefault(); // Prevent form submission
}
// More complex validation logic could be included here
});
</script>
</body>
</html>
`);
});
Cacheable
Responses must indicate whether they can be cached. This enables clients and intermediaries to reuse data, reducing latency and server load. Use HTTP headers like Cache-Control, ETag, and Expires to guide caching behavior. Proper caching improves scalability and responsiveness.
Stateless
Each client request must contain all necessary information for the server to process it. The server does not retain session state between requests. This simplifies server design and improves reliability. Authentication tokens (e.g., JWTs) must be included with each request to maintain context.
Uniform Interface
The Uniform Interface principle ensures that all interactions between client and server follow a consistent, predictable structure. This simplifies integration and decouples client and server implementations.
Key elements include:
- Resource identification via URIs (e.g.,
/users/123) - Standard methods like GET, POST, PUT, DELETE
- Self-descriptive messages using HTTP headers and status codes
- Hypermedia as the engine of application state (HATEOAS) — clients discover actions via links in responses
By enforcing a uniform interface, REST promotes scalability, evolvability, and clarity across distributed systems.
Client-Server
The Client-Server principle separates concerns between the user interface (client) and data storage or logic (server). This modularity allows each side to evolve independently.
Benefits include:
- Simplified development — frontend and backend teams can work in parallel
- Improved scalability — servers can be optimized without affecting clients
- Security and abstraction — clients don’t need to know how data is stored or processed
This separation is foundational to REST and supports the layered architecture of modern web services.
Self-descriptive Messages
In REST, each message — whether a request or response — must contain all the information needed to understand and process it. This makes interactions predictable and decouples client and server logic.
Key elements include:
- HTTP methods (e.g., GET, POST) that describe the intended action
- Headers that convey metadata (e.g.,
Content-Type,Authorization) - Status codes that indicate outcome (e.g., 200 OK, 404 Not Found)
- Body content that represents the resource or error details
This principle ensures that intermediaries (like proxies or caches) can inspect and act on messages without needing external context. It also improves debuggability and supports statelessness by making each message complete in itself.
Resource-Based
REST treats everything as a resource — a conceptual entity that can be accessed or manipulated via a URI. Resources are typically nouns (e.g., /users, /orders/123) and represent data objects or services.
Key ideas:
- Each resource has a unique URI — this is its address in the system
- Resources are manipulated via representations — usually JSON or XML
- Actions are defined by HTTP methods — not by verbs in the URI
This approach encourages clean, predictable API design and aligns with REST’s emphasis on statelessness and uniform interfaces. It also makes APIs easier to document, test, and evolve over time.
Manipulation of Resources Through Representations
In REST, clients interact with resources by sending and receiving representations — typically in formats like JSON or XML. These representations contain the data and structure needed to modify or understand the resource, without exposing its internal implementation.
For example:
- A client sends a JSON payload to update a user's email:
{
"email": "new@example.com"
}
This representation is sent via a PUT or PATCH request to /users/123. The server uses it to update the resource.
- A client receives a JSON response:
{
"id": 123,
"name": "Iain",
"email": "new@example.com"
}
This is a representation of the user resource. The client can display it, store it, or use it in further requests.
This principle ensures that clients never manipulate resources directly — they work through structured representations, which keeps the system modular, secure, and stateless.
Hypermedia as the Engine of Application State (HATEOAS)
In REST, hypermedia means that responses include links that guide the client through available actions. Instead of hardcoding endpoint paths, clients discover what they can do next by following links embedded in the response.
For example, a response might look like:
{
"id": 123,
"name": "Iain",
"links": {
"self": "/users/123",
"edit": "/users/123/edit",
"delete": "/users/123/delete"
}
}
This allows the client to navigate the API dynamically, based on the current state of the resource. It’s like receiving a map with each response — showing where you are and what paths are available.
HATEOAS supports decoupling, discoverability, and adaptability. While not always implemented in full, it embodies REST’s vision of self-navigating systems.
Layered System
The layered system principle allows a REST architecture to be composed of hierarchical layers, each with its own responsibility. Clients cannot ordinarily tell whether they are connected directly to the origin server or through intermediaries like proxies, gateways, or load balancers.
This is how it is normally layered
Client → Load Balancer → Reverse Proxy → Application Server → Database
This was the best picture I could find - but least I get the picture

Benefits include:
- Scalability — intermediaries can handle caching, security, or load distribution
- Encapsulation — each layer can evolve independently
- Security — firewalls and authentication layers can be added without changing core logic
This abstraction supports modularity and resilience in distributed systems.
Rest
Simple and Fine-grained
REST encourages APIs to be simple in structure and fine-grained in behavior. This means each endpoint should do one thing well, and expose resources in a way that’s easy to understand and compose.
For example:
- Instead of
/updateUserAndSendEmail, use:
*PUT /users/123to update the user *POST /emailsto send an email
Benefits:
- Clarity — each endpoint has a clear purpose
- Composability — clients can combine calls to achieve complex workflows
- Maintainability — changes to one resource don’t ripple across unrelated actions
This principle supports modularity and makes APIs easier to document, test, and evolve over time.
Pagination
Breaks large collections into manageable chunks. Common patterns include first, last, next, and prev links. Improves performance and user experience.
Filtering/Ordering
Allows clients to narrow or sort results using query parameters (e.g., ?status=active&sort=created). Supports flexible data access without overfetching.
Resource Naming
Use clear, consistent, and plural nouns for endpoints (e.g., /users, /orders). Avoid verbs — actions are expressed via HTTP methods.
Versioning
Supports API evolution without breaking clients. Common strategies include URI-based (/v1/users) or header-based (Accept: application/vnd.api+json;version=1).
Caching
Improves performance by storing responses. Use headers like Cache-Control, ETag, and Expires to guide behavior.
Security
Protects data and access. Use TLS for transport, authentication (e.g., OAuth, JWT), and input validation to prevent injection and abuse.
CORS
Cross-Origin Resource Sharing (CORS) controls how resources are shared across different domains. It prevents unauthorized scripts from accessing sensitive data by enforcing origin-based access rules.
Use headers like Access-Control-Allow-Origin to specify which domains are permitted. Proper CORS configuration is essential for browser-based clients and protects against cross-site attacks.
Idempotence
An operation is idempotent if repeating it has the same effect as doing it once. In REST, methods like GET, PUT, and DELETE should be idempotent. This improves reliability, especially in retry scenarios.
Auth
Authentication ensures that only authorized users can access protected resources. Common methods include API keys, OAuth tokens, and JWTs. Always use HTTPS to protect credentials in transit.
Input Sanitization
Sanitize all incoming data to prevent injection attacks and malformed requests. Validate types, lengths, and formats. Never trust user input — especially in query parameters, headers, or request bodies.
Iain's Bonus Bits
ID Generation Approach
Most APIs use server-generated IDs for consistency and control. These are typically auto-incremented integers or UUIDs. Client-generated IDs are rare and usually reserved for offline-first or distributed systems.
Avoid exposing internal IDs unless necessary. If exposed, make them opaque and non-sequential to prevent enumeration.
Slugs Approach
Slugs are human-readable identifiers used in URLs (e.g., /articles/why-rest-matters). They improve SEO, usability, and shareability. Slugs are derived from titles or names, sanitized, and stored alongside primary IDs.
They’re not primary keys — instead, they map to internal IDs server-side. Ensure uniqueness and resolve slugs to IDs for data integrity.
SQL Injection Approach
Use parameterized queries or ORM methods. Never concatenate raw input into SQL strings. Validate and sanitize all inputs — especially filters, search terms, and dynamic queries.
This protects against injection attacks and ensures query integrity.
Timeouts Approach
Set timeouts for API calls, database queries, and external services to avoid hanging requests. Use shorter timeouts for user-facing endpoints and longer ones for background tasks.
Timeouts improve resilience and help detect bottlenecks early.
Retry Approach
Implement retries with exponential backoff for transient failures (e.g., network errors, 503 responses). Avoid retrying on client-side errors (e.g., 400 Bad Request). Add jitter to prevent retry storms under load.
Retries should be bounded and logged for observability.
Rate Limiting Approach
Protect endpoints with rate limits to prevent abuse and ensure fair usage. Use headers like X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After to inform clients.
Rate limits can be based on IP, user ID, or API token. Consider burst limits and sliding windows for flexibility.
Error Handling Approach
Return clear, consistent error responses with appropriate HTTP status codes. Include helpful messages and error codes in the body.
Do not expose internal error details or stack traces — this can be a security risk. Log full errors server-side and return generic messages to clients.
Logging Approach
Log structured data with context — request IDs, user IDs, timestamps. Avoid logging sensitive information. Use consistent log levels (INFO, WARN, ERROR) and tag logs for traceability across services.
Logs are essential for debugging, monitoring, and auditing.
Content Negotiation Approach
Support Accept headers to serve different formats (e.g., JSON, XML). Default to JSON if unspecified. This improves flexibility and interoperability across clients.
Dependency Management Approach
Pin versions of libraries and APIs to avoid unexpected changes. Use semantic versioning and changelogs. Regularly audit dependencies for security and compatibility.
Payload Size Limiting Approach
Restrict the size of incoming requests and outgoing responses to prevent abuse and protect system resources. Use headers like Content-Length and enforce limits at the server level.
This helps prevent denial-of-service attacks and ensures predictable performance.
Software Tooling by Language
REST API Language Popularity (2025 Snapshot)
I let the robot do this one for me as I cannot do everything. For me I like C# as it is a typed language, with good supporting libraries.
| Language | Popularity Tier | Common Use Cases | Notes |
|---|---|---|---|
| JavaScript (Node.js) | 🥇 Most Popular | Web/mobile APIs, microservices, serverless | Ubiquitous in full-stack setups; Express and NestJS dominate |
| Python | 🥈 Very Popular | ML endpoints, automation, internal APIs | FastAPI and Flask are favorites for simplicity and speed |
| Go | 🥉 Popular | High-performance APIs, cloud-native services | Loved for concurrency and speed; used by Google, Uber |
| C# (.NET) | 🥉 Popular | Enterprise APIs, Windows services | Strong in regulated and enterprise environments |
| Java | 🥉 Popular | Large-scale distributed systems | Still dominant in legacy and enterprise stacks |
| Rust | 🚀 Emerging | Secure, high-performance APIs | Gaining traction in security-sensitive domains |
| Scala | 🚀 Niche | Functional APIs, data pipelines | Used in data-heavy systems; less common for REST-first apps |
Go
| Feature | Library |
|---|---|
| Routing & Middleware | Gorilla Mux / Chi |
| Validation | go-playground/validator |
| Rate Limiting | uber-go/ratelimit / tollbooth |
| Versioning | Custom routing or URL prefixing |
| Auth (JWT) | golang-jwt/jwt |
| Input Sanitization | Custom middleware / validator tags |
| Monitoring | Prometheus + Grafana / OpenTelemetry |
C# (.NET)
| Feature | Library |
|---|---|
| Routing & Middleware | ASP.NET Core |
| Validation | DataAnnotations / FluentValidation |
| Rate Limiting | AspNetCoreRateLimit |
| Versioning | Microsoft.AspNetCore.Mvc.Versioning |
| Auth (JWT) | Microsoft.AspNetCore.Authentication.JwtBearer |
| Input Sanitization | Model binding + custom filters |
| Monitoring | Application Insights / Serilog |
TypeScript (Node.js)
| Feature | Library |
|---|---|
| Routing & Middleware | Express / Fastify / NestJS |
| Validation | class-validator / Joi / Zod |
| Rate Limiting | express-rate-limit / rate-limiter-flexible |
| Versioning | URL prefixing / NestJS modules |
| Auth (JWT) | passport-jwt / jsonwebtoken |
| Input Sanitization | DOMPurify / validator.js |
| Monitoring | Prometheus / OpenTelemetry / Winston |
Rust
| Feature | Library |
|---|---|
| Routing & Middleware | Actix-web / Axum |
| Validation | validator crate |
| Rate Limiting | tower-governor / actix-limitation |
| Versioning | Route prefixing / Axum layers |
| Auth (JWT) | jsonwebtoken crate |
| Input Sanitization | Custom filters / validator crate |
| Monitoring | tracing / metrics / Prometheus |
Scala
| Feature | Library |
|---|---|
| Routing & Middleware | Akka HTTP / http4s / Play Framework |
| Validation | Accord / Play JSON validation |
| Rate Limiting | Kamon / custom Akka streams |
| Versioning | Route prefixing / Play modules |
| Auth (JWT) | Silhouette / Play Auth |
| Input Sanitization | OWASP Java HTML Sanitizer / custom filters |
| Monitoring | Kamon / Prometheus / Lightbend Telemetry |
Python
| Feature | Library |
|---|---|
| Routing & Middleware | Flask / FastAPI / Django REST Framework |
| Validation | Pydantic / Marshmallow |
| Rate Limiting | Flask-Limiter / django-ratelimit |
| Versioning | URL prefixing / DRF versioning |
| Auth (JWT) | PyJWT / Django REST Knox |
| Input Sanitization | Bleach / WTForms validators |
| Monitoring | Prometheus / OpenTelemetry / Sentry |
Testing Tools by Language
C# (.NET)
| Category | Tool |
|---|---|
| Unit Testing | xUnit / NUnit / MSTest |
| E2E Testing | Selenium / Playwright / SpecFlow |
| Mocking | Moq / NSubstitute |
| Test Runner | Visual Studio Test Explorer / dotnet CLI |
Go
| Category | Tool |
|---|---|
| Unit Testing | Go’s built-in testing package
|
| E2E Testing | godog / chromedp / Playwright |
| Mocking | gomock / testify/mock |
| Test Runner | go test |
TypeScript (Node.js)
| Category | Tool |
|---|---|
| Unit Testing | Jest / Mocha / Vitest |
| E2E Testing | Cypress / Playwright / Puppeteer |
| Mocking | ts-mockito / sinon / jest.mock |
| Test Runner | Jest CLI / Mocha CLI / Vitest |
Python
| Category | Tool |
|---|---|
| Unit Testing | unittest / pytest |
| E2E Testing | Selenium / Playwright / behave |
| Mocking | unittest.mock / pytest-mock |
| Test Runner | pytest / unittest CLI |
Rust
| Category | Tool |
|---|---|
| Unit Testing | Rust’s built-in #[test] framework
|
| E2E Testing | cucumber-rust / Playwright / reqwest + assert |
| Mocking | mockall / double |
| Test Runner | cargo test |
Scala
| Category | Tool |
|---|---|
| Unit Testing | ScalaTest / Specs2 |
| E2E Testing | Selenium / Gatling / Playwright |
| Mocking | Mockito-scala / ScalaMock |
| Test Runner | sbt / ScalaTest CLI |


