RESTful API Design Principles
Introduction
This document establishes core principles and best practices for designing RESTful APIs within our system. It addresses common misconceptions about REST architecture and provides clear guidelines for resource modeling, especially for complex business operations beyond basic CRUD. The principles outlined here aim to create a clean separation between client and server, ensuring business logic remains on the server while providing a consistent, intuitive interface for clients.
Core Concepts
What is a Resource?
A resource is a noun, not a verb. It represents a concept or entity in our business domain that can be identified, named, addressed, and transferred between client and server.
Good examples: Cart, User, Payment, Product, Invoice, Subscription
Poor examples: QuickPay, VerifyPayment, AddItemToCart
A resource is not necessarily a database table. Resources exist in the API and business layer, while tables are implementation details in the data layer. Keep these concerns separate.
Business processes (like QuickPay) should not appear in URIs. They should be implemented as orchestration in the service layer, not exposed directly in the API.
What is a Sub-resource?
A sub-resource is a resource whose existence only makes sense within the context of another resource. This represents a composition relationship.
Examples:
- A
Commentwithout aPostis meaningless. Therefore,Commentis a sub-resource ofPost. - A
CartItemcannot exist without aCart. Therefore,CartItemis a sub-resource ofCart. - A
Verificationfor aPaymentis meaningless without that payment. Therefore,Verificationis a sub-resource ofPayment.
This relationship is elegantly reflected in URIs:
GET /posts/{postId}/comments // List all comments for a specific post
GET /carts/{cartId}/items // List all items in a specific cart
POST /payments/{paymentId}/verifications // Create a verification for a specific payment
Sub-resources help organize complex domains and create clearer, more intuitive APIs. They also naturally enforce access control and data integrity constraints.
Common Misconceptions
"REST is Only for CRUD Operations"
This is one of the biggest misconceptions about REST. REST is an architectural style, not a protocol limited to four basic operations.
While CRUD operations naturally map to HTTP methods (POST, GET, PUT/PATCH, DELETE), REST can handle complex operations through proper resource modeling.
Guiding principle: Instead of thinking about the "verb", think about what "noun" that verb creates or what "noun's" state it changes.
Resource Modeling for Complex Operations
Example: Payment Verification
Instead of:
POST /payment/verify
Use:
POST /payments/{paymentId}/verifications
This approach:
- Creates an audit trail of verification attempts
- Enables idempotency (preventing duplicate processing)
- Adheres to REST principles by creating a new resource
- Clearly models the relationship between payment and its verification
- Provides a clean URI structure for retrieving verification history
Case Studies
- Payment Processing
- Cart Management
- Coupon Operations
Payment Processing
Problem
Our system has multiple payment flows: QuickPay, JustPay, and Verify, currently implemented as:
v1/client/payment/quickpayv1/client/payment/justpayv1/client/payment/verify
These endpoints leak business logic to the API layer and create tight coupling between client and server.
Solution
QuickPay (combines cart creation and payment):
Instead of one endpoint that does two things, separate the concerns:
- Client creates a cart:
POST /carts - Server responds with cart details and available actions (HATEOAS)
- Client initiates payment:
POST /paymentswith{"cartId": "cart_123"}
Alternatively, if QuickPay must be preserved as a high-level business process, implement it as a process-based endpoint that creates the final result resource:
POST /orders
With request body containing all necessary data for the quick purchase flow. The server handles all orchestration internally.
JustPay (payment for existing entities):
For updating an existing payment:
PATCH /payments/{paymentId}
With request body specifying the changes:
{ "status": "processing", "gatewayReference": "ref_789" }
For creating a new payment from various sources:
POST /payments
With request body specifying the source:
// Payment for a cart
{ "sourceType": "cart", "sourceId": "cart_123" }
// Payment for an invoice
{ "sourceType": "invoice", "sourceId": "inv_456" }
Verify (payment verification):
POST /payments/{paymentId}/verifications
This creates a verification resource that logs the attempt, result, and any relevant data from the bank. It properly models verification as a sub-resource of payment.
Cart Management
Problem
Current endpoints like cart/addItem and cart/deleteItem use verbs in the URI.
Solution
Adding an item:
POST /carts/{cartId}/items
Removing an item:
DELETE /carts/{cartId}/items/{itemId}
These endpoints properly model cart items as sub-resources of a cart.
Coupon Operations
Problem
Endpoints like coupon/check, coupon/apply, and coupon/unapply use verbs and don't clearly indicate what they apply to.
Solution
Checking coupon validity:
POST /coupon-validations
Body includes coupon code and cart information. Response indicates validity.
Applying a coupon:
POST /carts/{cartId}/applied-coupons
Creates a relationship between cart and coupon.
Removing a coupon:
DELETE /carts/{cartId}/applied-coupons/{couponCode}
Removes the relationship between cart and coupon.
Architectural Considerations
Server-Side Orchestration
The orchestration layer for business processes should reside on the server, not the client.
Benefits:
- Security: Business logic remains protected on the server
- Simplicity: Clients remain "dumb" and lightweight
- Consistency: Business rules are applied uniformly
- Evolvability: Business processes can change without client updates
API Design Patterns
Resource-Based APIs vs. Process-Based APIs
- Resource-Based (Pure REST)
- Process-Based
Goal: Build a flexible, reusable API that can be consumed by different clients in various ways.
Approach:
- API exposes stable resources (nouns): carts, payments, users
- Orchestration is handled by the client
- Client knows the sequence of operations
When to use: When building a platform where clients decide how to use the building blocks.
Advantages:
- Maximum flexibility and reusability
- Clear separation of concerns
- Easier to evolve individual resources
Disadvantages:
- More complex client implementation
- Multiple network requests for complex operations
- Client needs to understand business processes
Goal: Hide implementation details and business logic from clients.
Approach:
- API exposes high-level endpoints representing complete use cases
- Server handles all orchestration internally
- Client simply invokes the process with required data
When to use: When clients should be decoupled from business logic and implementation details.
Advantages:
- Simpler client implementation
- Better encapsulation of business logic
- Fewer network requests for complex operations
- Better security by hiding implementation details
Disadvantages:
- Less flexible for varied use cases
- API surface may grow large with many use cases
- Potential for duplication across endpoints
Implementation Note:
Even with process-based APIs, the endpoints should still follow RESTful principles by creating or modifying resources. For example, a QuickPay process might be implemented as POST /orders with all necessary data, resulting in the creation of an order resource.
Implementation Guidelines
Naming Conventions
- Use plural nouns for collection resources:
/users,/orders - Use concrete, domain-specific names:
/productsnot/items - Use kebab-case for multi-word resources:
/shipping-addresses - Avoid verbs in URIs except for special controller resources
- Model business processes as resources or resource state changes, not as verbs in URIs
- Use sub-resources to represent relationships and hierarchical data
HTTP Methods
| Method | Purpose | Example |
|---|---|---|
| GET | Retrieve resources | GET /products |
| POST | Create a new resource | POST /orders |
| PUT | Replace a resource completely | PUT /users/123 |
| PATCH | Update a resource partially | PATCH /products/456 |
| DELETE | Remove a resource | DELETE /carts/789 |
Status Codes
| Code | Meaning | When to Use |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH, or DELETE |
| 201 | Created | Successful POST that created a resource |
| 204 | No Content | Successful operation with no response body |
| 400 | Bad Request | Invalid input, validation errors |
| 401 | Unauthorized | Authentication required |
| 403 | Forbidden | Authentication succeeded but insufficient permissions |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Request conflicts with current state |
| 422 | Unprocessable Entity | Semantic validation errors |
| 500 | Internal Server Error | Server-side error |
Advanced Patterns
HATEOAS (Hypermedia as the Engine of Application State)
Include links to related resources and possible actions in responses:
{
"id": "cart_123",
"items": [],
"totalPrice": 0,
"_links": {
"self": { "href": "/carts/cart_123" },
"items": { "href": "/carts/cart_123/items" },
"checkout": { "href": "/payments", "method": "POST" }
}
}
Idempotency
For operations that should not be accidentally repeated:
POST /payments
Idempotency-Key: 123e4567-e89b-12d3-a456-426614174000
Server checks if a request with this key has been processed before responding.
Migration Strategy
Never change existing API endpoints that clients rely on.
- Version your API: Start work on v4 with the new principles
- Incremental implementation: Begin with simpler resources
- Deprecation plan: Communicate timeline for phasing out older versions
- Documentation: Provide clear migration guides for clients
Architectural Considerations
Separation of Concerns
A well-designed API should maintain clear separation between:
- Client (Jupiter): Presentation and user interaction
- API Layer: Resource representation and HTTP semantics
- Service Layer (SUN): Business logic, orchestration, and domain rules
- Data Layer: Storage and retrieval
This separation ensures:
- Business logic remains on the server
- Clients remain "dumb" and lightweight
- API remains stable while implementation can evolve
- Security is properly enforced at each layer
Handling Complex Business Processes
Complex business processes like QuickPay should be:
- Implemented in the service layer
- Exposed through resource-oriented endpoints
- Orchestrated on the server, not the client
This approach reduces client complexity, improves security, and maintains clean separation of concerns.
Decision Framework for API Design Approach
Default to Resource-Based (Pure REST) APIs unless there's a compelling reason to use Process-Based APIs.
Use the following checklist to determine if a Process-Based API is appropriate for a specific use case:
-
Atomicity Requirement
- Does the operation need to be executed atomically as a single transaction?
- Would splitting it into multiple API calls increase the risk of inconsistent state?
-
Client Complexity
- Would implementing this using pure REST APIs make the client code overly complex?
- Would the client need to understand too much about the business process?
-
Network Efficiency
- Would a pure REST implementation require multiple round trips that significantly impact performance?
- Is the operation frequently used in high-latency environments?
-
Security Concerns
- Does the operation involve sensitive business rules that should not be exposed to the client?
- Would exposing the individual steps create potential security vulnerabilities?
-
Client Capabilities
- Is the client limited in its ability to orchestrate complex flows (e.g., IoT devices, simple mobile apps)?
If you answer "Yes" to one or more of these questions, a Process-Based API may be appropriate. However, even when using Process-Based APIs:
- The endpoint should still create or modify a resource (noun)
- The URI should reflect the resource being created or modified
- Standard HTTP methods and status codes should be used
- The response should include links to related resources (HATEOAS)
Conclusion
Properly designed RESTful APIs lead to more maintainable, scalable, and secure systems. By modeling resources correctly and keeping business logic on the server, we create a clean separation of concerns that benefits both the development team and API consumers.
Remember: The initial investment in proper API design pays significant dividends in reduced maintenance costs and increased system flexibility over time. Our API design should prioritize:
- Clean resource modeling: Using nouns, not verbs
- Proper sub-resource relationships: Reflecting domain relationships
- Server-side business logic: Keeping orchestration on the server
- Client simplicity: Reducing client-side complexity
- Consistency: Following established patterns and conventions