REST (Representation State Transfer) is an architectural style for designing scalable and maintainable networked applications – most commonly used for building APIs over HTTP.
It is not protocol and not tied to JSON. REST is a set of architectural constraints. When followed correctly, these constraints produce systems that are:
- Scalable
- Cache-friendly
- Loosely coupled
- Fault tolerant
- Evolvable over time
REST Is Not What Most People Think:
REST Is:
- An architectural style
- A set of constraints
- A system-level design philosophy
Rest Is NOT:
| Myth | Reality |
| REST = JSON | REST can use JSON, XML, protobuf, binary |
| REST = HTTP | REST is an architecture; HTTP is a transport |
| REST = CRUD | CRUD fits REST well, but REST is about resources and state transitions, not databases. |
REST = Representational State Transfer
Let's break into this into parts:
1 Representational
A representation is the format of the data you send or receive.
A resource (like a user, product, order, or photo) is stored on the server, but the server does not send the actual database object. Instead, it sends a representation of it.
Below are the formats in which data can be represented:
- JSON
- XML
- HTML
- CSV
- Protobuf
- Even binary
Example Resource: User
Representation in JSON:
{
"id": 7,
"name": "TheJat",
"email": "...."
}The same resource could be represented as XML:
<user>
<id>7</id>
<name>TheJat</name>
</users>Representation = formatted snapshot of a resource.
2 State
State means the current data of a resource.
When you request something, you want the state of that resource at that moment.
Example:
Product 300 State:
name: "MacBook"
price: 130000
stock: 15
colour: "silver"This is the state of the product.
But you don't get the database object – you get a representation of that state (JSON/XML/etc.).
State = the real data stored on the server.
3 Transfer
Transfer means sending the representation of the state between client and server over HTTP.
- Client: sends request
- Server: transfers a representation of the resource state back
- Client: updates its UI using that representation
Example:
Client:
GET /products/300Server transfers representation:
{
"id": 300,
"name": "MacBook",
"price": 130000,
"stock": 15
}
This is the transfer part of REST.
Transfer = sending the resource's state (in some representation) over the network.
REST Core Constraints (The Laws of REST)
To call an API a true REST API, it must follow all six constraints:
1 Client-Server Separation
The client and server must be completely independent.
- The client handles UI/UX
- The server handles data and business logic
- They evolve independently
They communicate only through requests and responses, not shared memory.
Benefits:
- Independent deployments
- Multiple client types (web, mobile, IoT)
- Better testability
- Cleaner architecture
2 Statelessness (The Backbone of Scalability)
Every request must be completely self-contained.
- The server stores no client session
- Each request includes:
- Authentication
- Authorization
- Context
- No memory of previous requests
- The server treats every request as brand new
This enables:
- Horizontal scaling
- Easy load balancing
- Easier caching
- Simpler failover
Bad:
“Server remembers your session”
Good:
“Every request is self-contained”
3 Cacheability
Responses must explicitly define whether they can be cached.
Common HTTP headers:
Cache-ControlETagLast-Modified
What caching gives us:
- Massive reduction in server load
- Faster responses
- CDN integration
Incorrect caching breaks correctness. Correct caching saves millions of dollars at scale.
4 Uniform Interface (The Heart of REST)
This is the most important REST constraint.
It consists of four sub-rules:
(a) Resource Identification via URIs
Each resource has a unique URL
/users
/orders
/productsA URI identifies a thing, not an action.
(b) Manipulation via Standard HTTP Methods
We use:
GET -> Read
POST -> Create
PUT -> Replace
PATCH -> Modify
DELETE -> RemoveNo custom verbs. No RPC-style endpoints.
(c) Self-Descriptive Messages
Every request and response must fully describe:
- What the data is
- How to parse it
- How to cache it
- What authentication applies
(d) Hypermedia (HATEOAS)
The server tells the client:
- What can be done next
- What actions are valid
- What transitions are allowed
This turns APIs into discoverable systems, not rigid contracts.
5 Layered System
A REST client must not know whether it is talking to:
- A load balancer
- A CDN
- A reverse proxy
- A cache
- A firewall
- The actual application server
This enables:
- Security layers
- CDN layers
- API gateways
- Zero-trust networks
6 Code-on-Demand (Optional)
The server can send executable code to the client (usually JavaScript)
This is optional and rarely used today in APIs.
Resources – The Heart of REST
Everything in REST is a resource. A REST API is built around resources.
A resource is:
- A real-world entity
- A concept with identity
- A thing that can change state
Examples:
- Users
- Products
- Orders
- Payments
- Books
- Tickers
A REST API is simply a network of linked resources that evolve through state transitions.
Professional REST API Design Process
Let's now design a REST API the correct professional way.
Step 1 – Identify the Resources
Ask these two questions:
- What real-world things exist in the system?
- What data do we permanently store?
Examples of an E-commerce system:
- user
- product
- category
- cart
- order
- review
- payment
These are all candidates for resources.
Step 2 – Convert Entities Into REST Resources
Follow this rule:
Objects -> Nouns -> Plural -> Resources
Example:
| Object | Resource |
| User | /users |
| Product | /products |
| Order | /orders |
| Payment | /payments |
Never user verbs.
Bad:
/getUser
/createOrder
/cancelPaymentGood:
/users
/orders
/paymentsUse plural names:
users
products
categories
carts
orders
reviews
paymentsStep 3 – Identify Resource Relationships
REST is not flat. Resources represent real-world relationships.
Types of relationships:
1 One-to-Many Relationships
- A user has many orders
- A book has many reviews
REST representation:
GET /users/{id}/orders
GET /books/{id}/reviews2 Many-to-Many Relationships
- Products belong to multiple categories
- Books has many authors
REST Representation:
GET /products/{id}/categories
GET /categories/{id}/products3 One-to-One Relationships
- A user has one profile
- An order has one payment
REST Representation:
GET /users/{id}/profile
GET /orders/{id}/paymentStep 4 – Avoid Over-Nesting
Bad:
/users/10/orders/5/items/2/payments/99/address/3Good:
/items/2/payments
/payments/99/addressRule: Never go more than 2 levels deep.
Step 5 – Define Resource Attributes (Fields)
Resources return representations, not database rows.
Each resource has fields:
Example: User Resource
id
name
email
phone
created_at
updated_atExample: Order Resource
id
user_id
order_items
total_amount
status
created_atThis becomes your JSON structure.
Rules:
- Field names must never change
- You may add new fields
- Never remove or rename existing fields
- Hide internal DB fields
- Optimize for clients, not your database
Step 6: Assign HTTP Methods to Resources
Now we assign correct methods to each resource based on operation semantics.
Users Resource:
Create user:
POST /usersGet all users:
GET /usersGet one user:
GET /users/{userId}Update full user:
PUT /users/{userId}Update partial user:
PATCH /users/{userId}Delete user:
DELETE /users/{userId}Orders Resource:
Create order:
POST /ordersGet all orders
GET /ordersGet one order:
GET /orders/{orderId}Update order status:
PATCH /orders/{orderId}Cancel order:
DELETE /orders/{orderId}User -> Orders Relations:
Create order for user:
POST /users/{userId}/orders
Get all orders of a user:
GET /users/{userId}/ordersOrder -> Items Relation:
Add item:
POST /orders/{orderId}/items
Update item quantity:
PATCH /orders/{orderId}/items/{itemId}
Remove item:
DELETE /orders/{orderId}/items/{itemId}HTTP Semantics: Safe, Unsafe & Idempotent Methods
Safe Methods (read-only):
- GET
- HEAD
Unsafe Methods (modify data):
- POST
- PUT
- PATCH
- DELETE
Idempotent Methods (Same result if called many times)
- GET
- PUT
- DELETE
Non-Idempotent Methods:
- POST
This is why:
- Payments must user idempotency keys with POST
- Update operations prefer PUT/PATCH
Pagination, Filtering & Sorting
Pagination, filtering, and sorting are not optional features in real-world REST APIs. They are mandatory once your system moves beyond a few records.
If your API:
- Returns thousands of records in one response
- Has no filtering
- Has no consistent sorting
Then it is not production-ready, no matter how correct your resources are.
Pagination:
Why Pagination Is Mandatory
Without pagination, your API suffers from:
- Memory exhaustion
- Slow query execution
- Network timeouts
- Poor mobile performance
- CDN inefficiency
- Database connection starvation
Example of a Dangerous API
GET /usersIf this returns 5 million users, your system is already broken.
Every list endpoint in production must be paginated.
Types of Pagination:
There are three major pagination strategies used in real systems:
| Type | Used By | Best For |
| Offset-based | Traditional systems | Admin dashboards |
| Cursor-based | Facebook, Instagram, Stripe | Infinite scroll |
| Keyset-based | High-performance data APIs | Large distributed DBs |
Offset-based Pagination (Beginner Friendly)
Request:
GET /users?page=3&limit=20Meaning:
- Skip first 40 records
- Return next 20 records
SQL Equivalent
LIMIT 20 OFFSET 40Response Example:
{
"data": [...],
"pagination": {
"page": 3,
"limit": 20,
"totalPages": 50,
"totalRecords": 1000
}
}Pros:
- Simple to implement
- Easy to understand
- Works well for small datasets
Cons:
- Becomes slow for large datasets
- Records shift when insert happen
- Can cause duplicate/missing records
- High DB cost at scale
Use only for:
- Admin dashboards
- Internal tools
- Small datasets
Cursor-Based Pagination (Used by Big Tech)
Request
GET /orders?limit=20&cursor=eyJpZCI6IjEwMDAifQ==Idea
Instead of saying:
"Give me page 5"You say:
"Give me records after this one"Response:
{
"data": [...],
"nextCursor": "eyJpZCI6IjEwMjAifQ==
}The client uses nextCursor for the next request.
Pros:
- Extremely fast
- No record shifting
- Perfect for infinite scroll
- Works with real-time inserts
Cons:
- Harder to debug
- Cannot jump to arbitrary page number
- More complex backend logic
Used by:
- Facebook feed
- Instagram timeline
- Stripe transactions
- Twitter timeline
Keyset Pagination (Database-Optimized)
GET /orders?limit=20&afterId=10500Equivalent SQL:
WHERE id > 10500
ORDER BY id ASC
LIMIT 20Pros:
- Fastest possible pagination
- DB index friendly
- No shifting
- Works on massive tables
Cons:
- Requires ordered unique field
- No random page jumps
Best for:
- Orders
- Transactions
- Logs
- Time-series data
Filtering:
Filtering means reducing result set based on conditions.
Basic Filtering
GET /users?status=active
GET /orders?state=deliveredAlways treat query params as filters
Never use path params for dynamic filters
Multiple Filters
GET /users?status=active&role=admin&country=indiaBackend interpretation:
status = active AND role = admin AND country = indiaRange Filtering (Dates, Numbers, Prices)
GET /orders?amountMin=500&amountMax=5000
GET /orders?createdAfter=2024-01-01Boolean Filtering
GET /products?inStock=true
GET /users?verified=falseText & Search Filtering
GET /products?search=iphone
GET /users?nameContains=manSorting:
Sorting defines result order.
Basic Sorting
GET /users?sort=createdAtDefault:
ASC (ascending)Descending Order
GET /users?sort=createdAt:desc
OR
GET /users?sort=-createdAtMulti-Field Sorting
GET /users?sort=role,-createdAtMeaning:
- Sort by role
- Then by
createdAtdescending
Sorting + Pagination Together
GET /orders?sort=createdAt:desc&page=1&limit=50Always:
- Sort first
- Then paginate
Never paginate unsorted datasets
Combining Pagination + Filtering + Sorting
A fully professional query look like:
GET /orders?
status=delivered&
amountMin=500&
amountMax=5000&
sort=createdAt:desc&
limit=20&
cursor=eyJpZCI6IjEwMDAifQ==This single endpoint supports:
- Pagination
- Filtering
- Sorting
- Infinite scrolling
- High performance
API Versioning
API Versioning is not a feature.
It is a long-term survival strategy for your platform.
If you deploy APIs without a versioning strategy:
- You will break clients
- You will block future features
- You will accumulate technical debt
- You will eventually face an unfixable migration crisis
What Is API Versioning?
API Versioning is the controlled evolution of your API contract over time without breaking existing clients.
It allows you to:
- Change request/response formats
- Modify behaviour
- Add or deprecate features
Rewrite internal systems
without forcing all users to upgrade immediately
Without versioning:
Your API becomes frozen on Day 1.
Why API Versioning Is Absolutely Mandatory
Any real system will eventually need to:
- Rename fields
- Change data structures
- Split or merge resources
- Improve validation rules
- Optimize performance
- Add security constraints
All of these are breaking changes.
If you deploy breaking changes without versioning:
- Mobile apps crash
- Partner integration fail
- Web clients break in production
- You lose trust permanently
Versioning = Controlled Change
What Is a “Breaking Change”?
A change is breaking if it forces clients to change their code.
Examples of Breaking Changes
- Renaming a field
totalAmount -> amount
- Removing a field
- Changing data type
price: string -> number
- Making optional fields mandatory
- Changing authentication rules
- Modifying business behaviour
- Changing pagination format
Non-Breaking Changes
- Adding new optional fields
- Adding new endpoints
- Adding new filters
- Adding new response metadata
Only breaking changes require a new API version.
The 4 Real API Versioning Strategies
There are only four valid strategies used in production.
| Strategy | Used By | Industry Adoption |
| URI Versioning | Twitter, Shopify, Stripe | Very High |
| Header Versioning | GitHub (partial), Microsoft | High |
| Media Type Versioning | Netflix, some hypermedia APIs | Medium |
| Query Parameter Versioning | Legacy systems | Low |
1 URI Versioning (Most Common & Most Practical)
Format
/v1/users
/v2/usersExamples:
GET /v1/users/123
GET /v2/users/123Advantages:
- Easier to understand
- Human readable
- CDN friendly
- Easy routing
- Works with all clients
- Excelled for monitoring & logging
Disadvantages:
- Breaks the “pure” REST URI philosophy (minor issue)
- Requires duplicate routes
This is the best default strategy for 90% of systems.
2 Header Versioning (Enterprise-Grade)
Format
GET /users
Accept-Version: v2OR:
Accept: application/vnd.myapp.v2+jsonAdvantages:
- Clean URIs
- Strict RESTful purity
- No URI duplication
Disadvantages:
- Hard to debug
- Poor browser support
- CDN caching complications
- Developers forget headers
Best for:
- Internal microservices
- SDK-driven platforms
3 Media Type Versioning (Advanced REST / HATEOAS)
Accept: application/vnd.shop.v2+jsonAdvantages:
- Strong content negotiation
- Multiple versions via same URI
- Hypermedia friendly
Disadvantages
- Very complex
- Few teams implement it correctly
- Tooling support is weak
4 Query Parameter Versioning (NOT Recommended)
GET /users?version=2Why this is bad:
- Breaks caching
- Breaks REST semantics
- Poor debugging
- Easy to misuse
Only acceptable for:
- Legacy systems
- Temporary migrations
Leave a comment
Your email address will not be published. Required fields are marked *
