Introduction to REST
REST (Representational State Transfer) is an architectural style for designing networked applications, first introduced by Roy Fielding in his 2000 doctoral dissertation. It has become the dominant pattern for building web APIs due to its simplicity, scalability, and alignment with the web's underlying HTTP protocol.
Unlike other API paradigms that focus on actions or procedures, REST is fundamentally resource-oriented. It treats everything as a resource that can be created, read, updated, or deleted using standard HTTP methods. This approach creates intuitive, predictable APIs that leverage the existing capabilities of HTTP.
Principles)) Resource-Oriented Everything is a resource Resources identified by URIs Resources have multiple representations Statelessness Each request contains all needed information No client context stored on server Authentication is per-request Uniform Interface Consistent resource identifiers Standard HTTP methods Self-descriptive messages HATEOAS (Hypermedia) Cacheability Responses explicitly mark themselves as cacheable Improves scalability and performance Layered System Client cannot tell if connected directly to end server Enables load balancing and security policies Code-On-Demand optional Server can extend client functionality
The Library Catalog Analogy
A helpful way to understand RESTful API design is to compare it to a library catalog system:
- Resources are like Books: Each book (resource) has a unique identifier in the catalog.
- URIs are like Call Numbers: Just as a call number leads you to a specific book's location, a URI identifies where to find a specific resource.
-
HTTP Methods are like Library Actions:
- GET is like looking up a book in the catalog without removing it
- POST is like adding a new book to the collection
- PUT is like replacing a book with a new edition
- PATCH is like updating information about a book
- DELETE is like removing a book from the collection
- Resource Representations are like Book Formats: The same book might be available as a hardcover, paperback, e-book, or audiobook, just as an API resource might be available in JSON, XML, or other formats.
- Statelessness is like Self-Contained References: Each library request (like a reference slip) contains all the information needed without relying on previous requests.
- HATEOAS is like "See Also" References: Books often contain references to related books, just as REST responses can include links to related resources.
Resource-Oriented Design
Identifying Resources
The first step in RESTful API design is identifying the resources your API will expose. Resources are the nouns in your system—the entities users need to interact with.
Resources often map to domain objects in your system, but they can also represent:
- Collections of objects (e.g., all products)
- Individual items (e.g., a specific product)
- Computed values (e.g., sales statistics)
- Virtual resources (e.g., search results)
- State transitions (e.g., order fulfillment)
Designing Resource URIs
URIs (Uniform Resource Identifiers) provide the addresses for your resources. Well-designed URIs are:
- Intuitive and predictable
- Resource-focused, not action-focused
- Consistent across the API
URI Design Best Practices
-
Use nouns, not verbs:
- Good:
/products - Avoid:
/getProducts
- Good:
-
Use plural nouns for collections:
- Good:
/products,/users - Avoid:
/product,/user
- Good:
-
Use concrete names over abstract concepts:
- Good:
/articles,/authors - Avoid:
/items,/assets(unless genuinely generic)
- Good:
-
Express hierarchical relationships:
- Parent-child:
/categories/123/products - Ownership:
/users/456/orders
- Parent-child:
-
Use query parameters for filtering, sorting, and pagination:
- Filtering:
/products?category=electronics - Sorting:
/products?sort=price&order=asc - Pagination:
/products?page=2&limit=20
- Filtering:
-
Use hyphens for multi-word names:
- Good:
/shopping-carts - Avoid:
/shoppingCartsor/shopping_carts
- Good:
-
Be consistent with pluralization:
- Either always use plurals or always use singulars
- Most APIs use plurals for collections and singular IDs
Example URI Patterns
| Resource | Collection URI | Individual URI |
|---|---|---|
| Products | /products |
/products/42 |
| Users | /users |
/users/john.doe |
| Orders | /orders |
/orders/ORD-2023-1234 |
| User's Orders | /users/john.doe/orders |
/users/john.doe/orders/ORD-2023-1234 |
| Order Items | /orders/ORD-2023-1234/items |
/orders/ORD-2023-1234/items/5 |
HTTP Methods and CRUD Operations
RESTful APIs use standard HTTP methods to perform CRUD (Create, Read, Update, Delete) operations on resources:
| Operation | HTTP Method | URI Pattern | Description |
|---|---|---|---|
| List Collection | GET | /resources |
Retrieve all resources (usually paginated) |
| Get Single Item | GET | /resources/{id} |
Retrieve a specific resource by ID |
| Create Item | POST | /resources |
Create a new resource in the collection |
| Update Item (Full) | PUT | /resources/{id} |
Replace a resource entirely |
| Update Item (Partial) | PATCH | /resources/{id} |
Update specific fields of a resource |
| Delete Item | DELETE | /resources/{id} |
Remove a resource |
Example CRUD Operations for a Products API
Create a Product (POST)
POST /api/products HTTP/1.1
Host: example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJ...
{
"name": "Wireless Headphones",
"price": 129.99,
"description": "Premium noise-cancelling wireless headphones",
"category": "electronics",
"in_stock": true
}
// Response
HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/products/42
{
"id": 42,
"name": "Wireless Headphones",
"price": 129.99,
"description": "Premium noise-cancelling wireless headphones",
"category": "electronics",
"in_stock": true,
"created_at": "2023-08-15T10:30:00Z"
}
Get All Products (GET)
GET /api/products?category=electronics&sort=price HTTP/1.1
Host: example.com
Accept: application/json
// Response
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600
{
"products": [
{
"id": 42,
"name": "Wireless Headphones",
"price": 129.99,
"category": "electronics",
"in_stock": true
},
{
"id": 17,
"name": "Bluetooth Speaker",
"price": 79.99,
"category": "electronics",
"in_stock": true
}
],
"total": 2,
"page": 1,
"limit": 10
}
Get a Specific Product (GET)
GET /api/products/42 HTTP/1.1
Host: example.com
Accept: application/json
// Response
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: max-age=3600
{
"id": 42,
"name": "Wireless Headphones",
"price": 129.99,
"description": "Premium noise-cancelling wireless headphones",
"category": "electronics",
"in_stock": true,
"created_at": "2023-08-15T10:30:00Z",
"updated_at": "2023-08-15T10:30:00Z"
}
Update a Product (PUT)
PUT /api/products/42 HTTP/1.1
Host: example.com
Content-Type: application/json
If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Authorization: Bearer eyJhbGciOiJ...
{
"name": "Wireless Headphones Pro",
"price": 149.99,
"description": "Premium noise-cancelling wireless headphones with extended battery life",
"category": "electronics",
"in_stock": true
}
// Response
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "4d2a46c69fde6fcd2810cd7054b42fba98e91de2"
{
"id": 42,
"name": "Wireless Headphones Pro",
"price": 149.99,
"description": "Premium noise-cancelling wireless headphones with extended battery life",
"category": "electronics",
"in_stock": true,
"created_at": "2023-08-15T10:30:00Z",
"updated_at": "2023-08-15T11:15:00Z"
}
Update a Product Partially (PATCH)
PATCH /api/products/42 HTTP/1.1
Host: example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJ...
{
"price": 139.99,
"in_stock": false
}
// Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 42,
"name": "Wireless Headphones Pro",
"price": 139.99,
"description": "Premium noise-cancelling wireless headphones with extended battery life",
"category": "electronics",
"in_stock": false,
"created_at": "2023-08-15T10:30:00Z",
"updated_at": "2023-08-15T11:30:00Z"
}
Delete a Product (DELETE)
DELETE /api/products/42 HTTP/1.1
Host: example.com
Authorization: Bearer eyJhbGciOiJ...
// Response
HTTP/1.1 204 No Content
Relationship Handling
Most applications have resources that are related to each other. RESTful APIs need effective ways to express and manipulate these relationships.
Types of Relationships
-
One-to-Many: A resource contains or owns multiple other resources
- Example: A user has many orders
- URI Pattern:
/users/123/orders
-
Many-to-Many: Resources have multiple relationships with other resources
- Example: Products belong to multiple categories, categories contain multiple products
- URI Pattern:
/products/42/categoriesor/categories/7/products
-
One-to-One: A resource has a single related resource
- Example: A user has one profile
- URI Pattern:
/users/123/profile
Nested Resources vs. Linking
There are two main approaches to handling relationships:
Nested Resources Approach
// Get all orders for a specific user
GET /api/users/123/orders
// Get a specific order for a user
GET /api/users/123/orders/456
// Add a new order for a user
POST /api/users/123/orders
Advantages:
- Expresses ownership or containment clearly
- Enforces access control at the parent level
- Reduces the need for separate authorization checks
Disadvantages:
- Can lead to deep nesting (
/a/1/b/2/c/3) which becomes unwieldy - May create duplication (same resource accessible through different paths)
- Can be challenging to implement complex queries
Linking Approach
// User representation includes links to related resources
GET /api/users/123
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"links": {
"orders": "/api/orders?user_id=123",
"profile": "/api/profiles/123"
}
}
// Orders can be accessed directly with filtering
GET /api/orders?user_id=123
Advantages:
- Flatter structure, simpler URIs
- More flexible for complex querying and filtering
- Avoids duplication of endpoints
Disadvantages:
- May require more authorization checks
- Relationship might be less obvious
- Can lead to more complex query parameters
Best Practices for Resource Relationships
-
Limit nesting depth: Avoid more than one or two levels of nesting
- Good:
/departments/123/employees - Avoid:
/companies/1/departments/123/employees/456/tasks
- Good:
-
Use nesting for strong ownership: When one resource truly belongs to another
- Example:
/articles/123/comments(comments belong to a specific article)
- Example:
-
Use query parameters for filtering by relationship: When a resource can be accessed directly
- Example:
/orders?customer_id=123&status=shipped
- Example:
-
Consider the resource's primary identity: Does it make sense independently?
- A product review might be accessed via
/products/123/reviewsbut also have its own identity at/reviews/456
- A product review might be accessed via
-
Include relationship links in responses: Help clients navigate between related resources
- Use hypermedia principles (HATEOAS) to provide navigation paths
HATEOAS and API Evolution
HATEOAS (Hypermedia as the Engine of Application State) is a constraint of the REST application architecture that distinguishes it from other network application architectures. It's often one of the most overlooked aspects of true RESTful design.
Understanding HATEOAS
The core principle is that a client interacts with a REST API entirely through the hypermedia provided dynamically by the server. In other words, the API responses include links that tell the client what it can do next.
Think of it like browsing a website: you don't need to know all the URLs in advance—you follow links from page to page. Similarly, a HATEOAS-compliant API provides "links" that guide the client through the available actions.
Example: Without HATEOAS
GET /api/orders/12345 HTTP/1.1
Host: example.com
// Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "12345",
"total": 99.99,
"status": "shipped",
"customer_id": "789"
}
The client needs prior knowledge about what endpoints are available for further actions.
Example: With HATEOAS
GET /api/orders/12345 HTTP/1.1
Host: example.com
// Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "12345",
"total": 99.99,
"status": "shipped",
"customer_id": "789",
"links": [
{ "rel": "self", "href": "/api/orders/12345", "method": "GET" },
{ "rel": "customer", "href": "/api/customers/789", "method": "GET" },
{ "rel": "items", "href": "/api/orders/12345/items", "method": "GET" },
{ "rel": "cancel", "href": "/api/orders/12345/cancel", "method": "POST" },
{ "rel": "return", "href": "/api/orders/12345/return", "method": "POST" }
]
}
Now the client can discover what actions are possible based on the current state of the resource.
Benefits of HATEOAS
- Decoupling clients from server URLs: Clients don't need to hardcode URLs or build them with string concatenation
- API evolvability: The server can introduce new capabilities without breaking clients
- State-appropriate actions: Only show actions that are valid for the current resource state
- Discoverability: Clients can discover the API capabilities as they interact with it
- Self-documenting: Responses indicate what's possible, reducing the need for separate documentation
Implementing HATEOAS
Several formats exist for implementing HATEOAS:
HAL (Hypertext Application Language)
{
"id": "12345",
"total": 99.99,
"status": "shipped",
"_links": {
"self": { "href": "/api/orders/12345" },
"customer": { "href": "/api/customers/789" },
"items": { "href": "/api/orders/12345/items" }
}
}
JSON:API
{
"data": {
"type": "orders",
"id": "12345",
"attributes": {
"total": 99.99,
"status": "shipped"
},
"relationships": {
"customer": {
"links": {
"related": "/api/customers/789"
}
},
"items": {
"links": {
"related": "/api/orders/12345/items"
}
}
},
"links": {
"self": "/api/orders/12345"
}
}
}
Custom Link Structure
{
"id": "12345",
"total": 99.99,
"status": "shipped",
"links": [
{ "rel": "self", "href": "/api/orders/12345", "method": "GET" },
{ "rel": "cancel", "href": "/api/orders/12345/cancel", "method": "POST",
"available": true, "description": "Cancel this order" }
]
}
API Evolution Strategies
HATEOAS is one approach to API evolution, but there are several strategies:
Versioning
-
URI Versioning: Include version in the path
- Example:
/api/v1/productsvs/api/v2/products - Advantages: Explicit, easy to understand
- Disadvantages: Resources exist at multiple URLs, can't mix versions
- Example:
-
Header Versioning: Use custom headers to specify version
- Example:
Accept-Version: v1orX-API-Version: v2 - Advantages: Same URI for a resource, cleaner URLs
- Disadvantages: Less visible, harder to test in browser
- Example:
-
Media Type Versioning: Use content negotiation
- Example:
Accept: application/vnd.company.v1+json - Advantages: Uses HTTP's built-in content negotiation, resource-focused
- Disadvantages: More complex, less common
- Example:
Compatibility Guidelines
-
Non-breaking changes (no new version needed):
- Adding new endpoints
- Adding optional request fields
- Adding response fields
- Adding new enum values (with graceful handling)
-
Breaking changes (new version needed):
- Removing or renaming fields
- Changing field types
- Adding required request fields
- Changing success response codes
- Changing the URL structure
Content Negotiation
Content negotiation is the process of selecting the best representation of a resource when multiple representations are available. This allows clients to request data in their preferred format.
Format Negotiation with Accept Header
// Client requests JSON
GET /api/products/123 HTTP/1.1
Host: example.com
Accept: application/json
// Server responds with JSON
HTTP/1.1 200 OK
Content-Type: application/json
{ "id": 123, "name": "Product Name", "price": 99.99 }
// Client requests XML
GET /api/products/123 HTTP/1.1
Host: example.com
Accept: application/xml
// Server responds with XML
HTTP/1.1 200 OK
Content-Type: application/xml
<product>
<id>123</id>
<name>Product Name</name>
<price>99.99</price>
</product>
Language Negotiation with Accept-Language
// Client requests French
GET /api/products/123 HTTP/1.1
Host: example.com
Accept: application/json
Accept-Language: fr-FR,fr;q=0.9,en;q=0.8
// Server responds with French content
HTTP/1.1 200 OK
Content-Type: application/json
Content-Language: fr-FR
{ "id": 123, "nom": "Nom du produit", "prix": 99.99 }
Implementing Content Negotiation
Node.js (Express)
app.get('/api/products/:id', (req, res) => {
const productId = req.params.id;
const product = getProductById(productId);
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
// Content negotiation based on Accept header
res.format({
'application/json': () => {
res.json(product);
},
'application/xml': () => {
const xml = convertToXml(product);
res.type('application/xml').send(xml);
},
default: () => {
// Default to JSON if no match
res.json(product);
}
});
});
Python (Flask)
from flask import request, jsonify, Response
import dicttoxml
@app.route('/api/products/')
def get_product(product_id):
product = get_product_by_id(product_id)
if not product:
return jsonify({"error": "Product not found"}), 404
# Check Accept header
best_match = request.accept_mimetypes.best_match(['application/json', 'application/xml'])
if best_match == 'application/xml':
xml = dicttoxml.dicttoxml(product)
return Response(xml, mimetype='application/xml')
else:
return jsonify(product)
PHP
<?php
// Parse Accept header
$accept = $_SERVER['HTTP_ACCEPT'] ?? 'application/json';
// Get product
$productId = // extract from URL
$product = getProductById($productId);
if (!$product) {
header('Content-Type: application/json');
http_response_code(404);
echo json_encode(['error' => 'Product not found']);
exit;
}
// Content negotiation
if (strpos($accept, 'application/xml') !== false) {
// Respond with XML
header('Content-Type: application/xml');
$xml = new SimpleXMLElement('<product/>');
foreach ($product as $key => $value) {
$xml->addChild($key, $value);
}
echo $xml->asXML();
} else {
// Default to JSON
header('Content-Type: application/json');
echo json_encode($product);
}
?>
Common REST API Patterns
Pagination
Pagination is essential when dealing with large collections of resources.
Offset-Based Pagination
GET /api/products?page=2&limit=20 HTTP/1.1
Host: example.com
// Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"products": [...],
"pagination": {
"total_items": 547,
"total_pages": 28,
"current_page": 2,
"items_per_page": 20,
"next_page": "/api/products?page=3&limit=20",
"prev_page": "/api/products?page=1&limit=20"
}
}
Cursor-Based Pagination
GET /api/products?limit=20&after=eyJpZCI6MTIzfQ== HTTP/1.1
Host: example.com
// Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"products": [...],
"pagination": {
"limit": 20,
"after": "eyJpZCI6MTQ1fQ==",
"next": "/api/products?limit=20&after=eyJpZCI6MTQ1fQ=="
}
}
Filtering
Filtering allows clients to request subsets of resources.
Simple Field Filtering
GET /api/products?category=electronics&in_stock=true&min_price=100&max_price=500 HTTP/1.1
Host: example.com
Advanced Filtering
GET /api/products?filter[category]=electronics&filter[price][gt]=100&filter[price][lt]=500 HTTP/1.1
Host: example.com
Sorting
Sorting controls the order of returned resources.
Single Field Sorting
GET /api/products?sort=price&order=asc HTTP/1.1
Host: example.com
Multi-Field Sorting
GET /api/products?sort=category,-price HTTP/1.1
Host: example.com
This sorts first by category (ascending) then by price (descending, indicated by the minus sign).
Field Selection
Field selection lets clients request only the fields they need, reducing payload size.
Including Fields
GET /api/products/123?fields=id,name,price HTTP/1.1
Host: example.com
// Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "Wireless Headphones",
"price": 129.99
}
Excluding Fields
GET /api/products/123?exclude=description,metadata,related_products HTTP/1.1
Host: example.com
Bulk Operations
Bulk operations allow clients to perform actions on multiple resources at once.
Bulk Creation
POST /api/products/bulk HTTP/1.1
Host: example.com
Content-Type: application/json
{
"products": [
{ "name": "Product 1", "price": 19.99 },
{ "name": "Product 2", "price": 29.99 },
{ "name": "Product 3", "price": 39.99 }
]
}
// Response
HTTP/1.1 201 Created
Content-Type: application/json
{
"created": 3,
"products": [
{ "id": 101, "name": "Product 1", "price": 19.99 },
{ "id": 102, "name": "Product 2", "price": 29.99 },
{ "id": 103, "name": "Product 3", "price": 39.99 }
]
}
Bulk Update
PATCH /api/products HTTP/1.1
Host: example.com
Content-Type: application/json
{
"updates": [
{ "id": 101, "price": 24.99 },
{ "id": 102, "price": 34.99 }
]
}
// Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"updated": 2,
"products": [
{ "id": 101, "price": 24.99 },
{ "id": 102, "price": 34.99 }
]
}
Error Handling
Consistent error handling improves API usability.
Basic Error Response
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "Invalid product data",
"message": "Price must be greater than zero",
"code": "INVALID_PRICE"
}
Validation Errors
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": "Validation failed",
"message": "Multiple validation errors occurred",
"code": "VALIDATION_ERROR",
"validation_errors": [
{
"field": "name",
"message": "Name is required",
"code": "FIELD_REQUIRED"
},
{
"field": "price",
"message": "Price must be greater than zero",
"code": "INVALID_VALUE"
}
]
}
Security Considerations for REST APIs
Authentication Methods
-
API Keys: Simple string tokens sent with each request
- Example:
Authorization: ApiKey abc123def456 - Good for public APIs with low security requirements
- Example:
-
OAuth 2.0: Framework for delegated authorization
- Example:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - Good for APIs that need to access user data across services
- Example:
-
JWT (JSON Web Tokens): Compact, self-contained tokens
- Example:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - Good for stateless authentication with claims
- Example:
-
Basic Authentication: Username/password credentials
- Example:
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ= - Simple but less secure; should only be used with HTTPS
- Example:
Authorization
Authorization determines what authenticated users can do:
-
Role-Based Access Control (RBAC): Permissions based on user roles
- Example: Admin role can DELETE resources, user role can only GET
-
Attribute-Based Access Control (ABAC): More fine-grained based on attributes
- Example: Users can only access resources they created or were shared with them
-
Scopes: Permissions defined in tokens
- Example: Token with
read:productsscope but notwrite:products
- Example: Token with
Common Security Vulnerabilities
-
Injection Attacks: SQL, NoSQL, command injection
- Mitigation: Use parameterized queries, input validation
-
Cross-Site Request Forgery (CSRF): Unauthorized commands from a trusted user
- Mitigation: CSRF tokens, SameSite cookies, Origin/Referer validation
-
Cross-Origin Resource Sharing (CORS): Misconfigured policies
- Mitigation: Properly configured CORS headers
-
Insecure Direct Object References: Accessing resources without authorization
- Mitigation: Authorization checks for all resource access
-
Rate Limiting: Preventing abuse and DDoS
- Mitigation: Implement rate limiting with 429 Too Many Requests responses
Practical Exercise: RESTful API Design
Task: Design a RESTful API for a Library Management System
For this exercise, you'll design a RESTful API for a library management system that handles books, patrons, and loans.
Requirements:
- Identify the key resources in the system
- Design URI patterns for each resource and their relationships
- Specify which HTTP methods apply to each resource
- Design the request and response formats, including status codes
- Consider pagination, filtering, and sorting
- Design at least one example of using HATEOAS in responses
Resources to Consider:
- Books: Information about books in the library
- Authors: Information about book authors
- Patrons: Library members who can borrow books
- Loans: Records of books borrowed by patrons
- Reservations: Requests to borrow books that are currently unavailable
Sample Operations:
- Search for books by title, author, or category
- Check out a book
- Return a book
- Place a reservation for a book
- List books currently borrowed by a patron
- List loan history for a book
Deliverable:
Document your API design, including:
- Resource URIs
- HTTP methods
- Request/response formats
- Status codes
- Example requests and responses for at least three operations
Summary
In this session, we've explored the principles of RESTful API design:
- Resource-Oriented Design: Identifying resources and structuring URIs around them
- HTTP Methods for CRUD: Using standard HTTP methods for create, read, update, and delete operations
- Relationship Handling: Expressing relationships between resources through nested routes and linking
- HATEOAS: Using hypermedia to drive application state and enable API evolution
- Content Negotiation: Supporting multiple representations of resources
- Common Patterns: Pagination, filtering, sorting, and error handling
- Security Considerations: Authentication, authorization, and common vulnerabilities
Understanding these principles will help you design APIs that are:
- Intuitive and easy to use
- Consistent and predictable
- Flexible and evolvable
- Efficient and scalable
- Secure and robust
In our next session, we'll explore API best practices in more detail, including documentation, versioning, and performance optimization.
Additional Resources
- REST APIs must be hypertext-driven - Roy Fielding's thoughts on HATEOAS
- JSON:API Specification - A standard for building APIs in JSON
- OpenAPI Specification - Standard for describing RESTful APIs
- Best Practices for a Pragmatic RESTful API
- REST API Design Rulebook - Comprehensive book on REST API design