Skip to main content

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?

important

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

note

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.

caution

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 Comment without a Post is meaningless. Therefore, Comment is a sub-resource of Post.
  • A CartItem cannot exist without a Cart. Therefore, CartItem is a sub-resource of Cart.
  • A Verification for a Payment is meaningless without that payment. Therefore, Verification is a sub-resource of Payment.

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
tip

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"

caution

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

Problem

Our system has multiple payment flows: QuickPay, JustPay, and Verify, currently implemented as:

  • v1/client/payment/quickpay
  • v1/client/payment/justpay
  • v1/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:

  1. Client creates a cart: POST /carts
  2. Server responds with cart details and available actions (HATEOAS)
  3. Client initiates payment: POST /payments with {"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.

Architectural Considerations

Server-Side Orchestration

info

The orchestration layer for business processes should reside on the server, not the client.

Benefits:

  1. Security: Business logic remains protected on the server
  2. Simplicity: Clients remain "dumb" and lightweight
  3. Consistency: Business rules are applied uniformly
  4. Evolvability: Business processes can change without client updates

API Design Patterns

Resource-Based APIs vs. Process-Based APIs

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

Implementation Guidelines

Naming Conventions

  1. Use plural nouns for collection resources: /users, /orders
  2. Use concrete, domain-specific names: /products not /items
  3. Use kebab-case for multi-word resources: /shipping-addresses
  4. Avoid verbs in URIs except for special controller resources
  5. Model business processes as resources or resource state changes, not as verbs in URIs
  6. Use sub-resources to represent relationships and hierarchical data

HTTP Methods

MethodPurposeExample
GETRetrieve resourcesGET /products
POSTCreate a new resourcePOST /orders
PUTReplace a resource completelyPUT /users/123
PATCHUpdate a resource partiallyPATCH /products/456
DELETERemove a resourceDELETE /carts/789

Status Codes

CodeMeaningWhen to Use
200OKSuccessful GET, PUT, PATCH, or DELETE
201CreatedSuccessful POST that created a resource
204No ContentSuccessful operation with no response body
400Bad RequestInvalid input, validation errors
401UnauthorizedAuthentication required
403ForbiddenAuthentication succeeded but insufficient permissions
404Not FoundResource doesn't exist
409ConflictRequest conflicts with current state
422Unprocessable EntitySemantic validation errors
500Internal Server ErrorServer-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

warning

Never change existing API endpoints that clients rely on.

  1. Version your API: Start work on v4 with the new principles
  2. Incremental implementation: Begin with simpler resources
  3. Deprecation plan: Communicate timeline for phasing out older versions
  4. Documentation: Provide clear migration guides for clients

Architectural Considerations

Separation of Concerns

important

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:

  1. Business logic remains on the server
  2. Clients remain "dumb" and lightweight
  3. API remains stable while implementation can evolve
  4. Security is properly enforced at each layer

Handling Complex Business Processes

Complex business processes like QuickPay should be:

  1. Implemented in the service layer
  2. Exposed through resource-oriented endpoints
  3. 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

important

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:

  1. 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?
  2. 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?
  3. Network Efficiency

    • Would a pure REST implementation require multiple round trips that significantly impact performance?
    • Is the operation frequently used in high-latency environments?
  4. 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?
  5. 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:

  1. Clean resource modeling: Using nouns, not verbs
  2. Proper sub-resource relationships: Reflecting domain relationships
  3. Server-side business logic: Keeping orchestration on the server
  4. Client simplicity: Reducing client-side complexity
  5. Consistency: Following established patterns and conventions