Articles/Phase 3 — Backend Architecture & Layering

API Design & Principles: The Definitive Guide

From Roy Fielding's REST constraints and UI-first resource mapping to clean URLs, HTTP method semantics, pagination defaults, and custom actions.

Core Concept & First Principle

Designing a professional REST API means establishing clear standards: plural nouns in URLs, strict HTTP method semantics (GET, POST, PUT, PATCH, DELETE), automatic pagination defaults, and clean custom action endpoints.

1. Why API Design Matters & The Historical Origins of REST

Designing an API is not just about making code run—it is about creating an intuitive, stable, and predictable contract for frontend developers and external clients. Following established industry standards eliminates guesswork, reduces bugs, and lets engineering teams focus on business logic instead of debating URL syntax.

11990: Tim Berners-Lee & The Early Web

Tim Berners-Lee invented the World Wide Web, creating URIs (identifiers), HTTP (wire protocol), HTML (markup), and the first web server and browser. By 1993, unexpected user growth threatened to crash early internet servers because scalability had not been planned for.

22000: Roy Fielding Introduces REST

Roy Fielding (co-founder of Apache HTTP Server and co-author of HTTP/1.1) solved the web scalability crisis in his doctoral dissertation by formalizing REST (Representational State Transfer).
Fielding's 6 Architectural Constraints (In Plain English)
1. Client-Server Separation: Decouples UI concerns from database logic. 2. Statelessness: The server never stores client session memory between requests. Every request carries all data needed to execute, making horizontal load balancing across clusters seamless. 3. Cacheability: Responses must explicitly declare if they can be cached (Cache-Control, ETag) to eliminate redundant network traffic. 4. Uniform Interface: All API routes look, navigate, and respond in a standardized, predictable pattern. 5. Layered System: Clients cannot tell if they are connected directly to the server or to an intermediate proxy, load balancer, or CDN. 6. Code on Demand (Optional): Servers can temporarily extend client features by sending executable code (JS/Wasm).
What 'Representational State Transfer' Literally Means
• Resource: A domain entity (e.g. an Organization, Project, or Task). • State: The resource's current database condition and attributes. • Representation: The wire format used to expose the state (JSON, XML, Protocol Buffers). • Transfer: Moving that representation across the network using standard HTTP methods.

2. UI-First Resource Mapping & URL Taxonomy

Professional API design begins from user interfaces (e.g., Figma wireframes) before writing any backend code. Extract the nouns visible in the UI to discover your core domain resources:

1The UI-to-API Workflow

UI Wireframes (Linear/Jira clone) ➔ Identify Nouns (Organizations, Projects, Tasks, Users) ➔ Design Database Schema ➔ Design API URL Interfaces ➔ Implement Business Logic.

2Standard API URL Pattern

Always follow: Scheme (https://) + Subdomain (api.) + Version (/v1/) + Plural Resource Path (/projects). Example: https://api.linear.app/v1/projects.

3Always Use Plural Nouns for Resources

Always name your resource with a plural noun: /v1/organizations, /v1/projects, /v1/tasks. Even when targeting a single record, keep the path plural: /v1/projects/proj_101.

4Lowercase Kebab-Case Formatting

Use hyphens to separate multi-word paths (/api/v1/user-profiles/). Never use underscores (_), camelCase, or spaces in URLs.

5Hierarchical Sub-Resource Slashes

Use forward slashes to express parent-child ownership: /organizations/:orgId/projects/:projectId/tasks indicates tasks belonging to a specific project within an organization.

3. HTTP Method Semantics & Idempotency Matrix

HTTP methods act as the semantic action verbs of your API. Each method carries strict guarantees regarding Safety (read-only) and Idempotency (repeated requests produce the same server state):

Comparison Table↔ Scroll horizontally
MethodAction IntentSafe? (Read-Only)Idempotent? (Same effect if repeated)Standard Status Code
GETFetch a resource or collectionYes (Zero state changes)Yes (Calling 100 times produces identical result)200 OK
POSTCreate a new resource / Execute custom actionNo (Mutates state)No (Calling 5 times creates 5 new distinct IDs)201 Created / 200 OK
PUTCompletely replace an entire existing resourceNo (Mutates state)Yes (Overwriting with identical payload leaves identical state)200 OK / 204 No Content
PATCHPartially modify specific fields of a resourceNo (Mutates state)Yes (Applying identical field deltas leaves identical state)200 OK
DELETERemove a resourceNo (Mutates state)Yes (Deleting an already-deleted item causes no further side effect)204 No Content / 200 OK
The PUT vs. PATCH Invariant
• PUT = Complete Entity Replacement: The client must send the entire object representation. If existing properties are omitted from the payload, they are wiped out or reset to default. • PATCH = Selective Field Delta: The server modifies ONLY the specific attributes sent in the JSON payload, leaving all omitted properties untouched.

4. Production CRUD, Pagination, Filtering & Sane Defaults

Never return unbounded database tables to clients. A production API must implement structured query controls with intelligent fallbacks:

1The Golden Rule of Sane Defaults

If a client calls GET /projects without query parameters, never crash or execute an unbounded query. Safely default to page=1, limit=10, status=active, and sort=createdAt-desc. This protects database CPU and memory pools.

2Query Parameter Filtering & Sorting

Allow clients to refine results without changing endpoint paths: ?status=active&sort=name-asc or ?sort=-createdAt.

3Structured Pagination Metadata Envelope

Wrap responses with a clean data array alongside a meta.pagination envelope containing page, limit, totalItems, totalPages, hasNextPage, and hasPrevPage.
Example: Production Paginated Response Structure
INPUT / REQUEST
GET /api/v1/projects?page=1&limit=2&status=active&sort=name-asc HTTP/1.1
Host: api.linear.app
Accept: application/json
OUTPUT / RESULT
HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": [
    {
      "id": "proj_alpha_01",
      "name": "Distributed Sockets Gateway",
      "description": "High-throughput TCP & WebSocket connection proxy multiplexer",
      "status": "active",
      "tags": ["networking", "sockets", "tcp"],
      "ownerId": "usr_sachin_101",
      "createdAt": "2026-01-15T09:00:00Z"
    },
    {
      "id": "proj_beta_02",
      "name": "SQLite WAL Cache Engine",
      "description": "Ultra low-latency on-disk relational caching engine",
      "status": "active",
      "tags": ["database", "sqlite", "wal"],
      "ownerId": "usr_sachin_101",
      "createdAt": "2026-02-10T14:30:00Z"
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "limit": 2,
      "totalItems": 14,
      "totalPages": 7,
      "hasNextPage": true,
      "hasPrevPage": false
    },
    "filtersApplied": {
      "status": "active",
      "sort": "name-asc"
    }
  }
}
Takeaway:Frontend clients can render pagination buttons, page counts, and active filters immediately without calculating offsets client-side.

5. Designing Custom Non-CRUD Actions

In real-world business domains, many operations represent workflows rather than simple CRUD (e.g., cloning a project with all sub-tasks, archiving an organization, locking an account, or triggering billing invoices):

1The Sub-Resource Action Verb Pattern

When an operation is a specific business action, append an explicit action verb to the resource sub-path using POST: POST /projects/:id/clone, POST /organizations/:id/archive, POST /invoices/:id/send, POST /tasks/:id/complete.

2Why Always Use POST for Custom Actions?

Custom actions trigger complex side effects (duplicating sub-trees, dispatching emails, state transitions) that are non-idempotent and create new resources with new IDs.

6. Enterprise Patterns: Standardized Errors, Rate Limiting & Content Negotiation

Enterprise-grade APIs go beyond transport codes to provide predictable error structures, quota headers, and seamless format evolution:

1Standardized Error Response Bodies

HTTP status codes describe the transport outcome, but error payloads must be strictly structured so frontend and mobile clients can parse errors automatically.

2Rate-Limiting & Quota Headers

Public APIs must include rate-limit headers informing clients of their remaining budget: X-RateLimit-Limit (max allowed), X-RateLimit-Remaining (calls left), and X-RateLimit-Reset (epoch reset timestamp).

3Content Negotiation & Evolution

By inspecting Accept and Content-Type headers (e.g. application/vnd.api+json or application/x-protobuf), servers can evolve schemas and payload representations without breaking existing URL paths.
Example: Production Error Payload Schema
INPUT / REQUEST
PUT /api/v1/projects/proj_01 HTTP/1.1
Content-Type: application/json

{
  "name": ""
}
OUTPUT / RESULT
HTTP/1.1 400 Bad Request
Content-Type: application/json
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 1786976400

{
  "error": {
    "code": "INVALID_REQUEST_BODY",
    "message": "The provided payload failed validation constraints.",
    "details": [
      {
        "field": "name",
        "issue": "Project name cannot be empty."
      },
      {
        "field": "ownerId",
        "issue": "Missing mandatory field 'ownerId' for complete PUT replacement."
      }
    ]
  }
}
Takeaway:Client applications can programmatically match on error.code to trigger UI toasts or highlight specific input fields using error.details.

7. Developer Experience (DX), Consistency & OpenAPI

Great APIs are a joy to integrate with because they adhere to strict consistency and documentation standards:

1Strict JSON Naming Consistency (camelCase)

Always standardize on camelCase for JSON payload keys (ownerId, createdAt, totalPages). Never mix snake_case and camelCase across different endpoints.

2Descriptive Field Names (Avoid Cryptic Abbreviations)

Use readable names like description and createdAt. Avoid cryptic abbreviations like DSC or cr_dt that create integration errors.

3Interactive OpenAPI / Swagger Documentation

Maintain an OpenAPI specification. Providing an interactive sandbox reduces integration friction and serves as the single source of truth.

8. Clean Multi-Language REST Architecture Implementations

Production REST controller implementations with sane defaults, rate-limit headers, and structured error formatting across TypeScript, Go, and Rust:

1import { Request, Response } from 'express';
2
3// GET /v1/projects?page=1&limit=10&status=active
4export const listProjects = async (req: Request, res: Response) => {
5 // Inject Rate Limiting Headers
6 res.setHeader('X-RateLimit-Limit', '100');
7 res.setHeader('X-RateLimit-Remaining', '97');
8 res.setHeader('X-RateLimit-Reset', Math.floor(Date.now() / 1000 + 60).toString());
9
10 // Sane Defaults
11 const page = Math.max(1, parseInt(req.query.page as string, 10) || 1);
12 const limit = Math.min(50, Math.max(1, parseInt(req.query.limit as string, 10) || 10));
13 const status = (req.query.status as string) || 'active';
14
15 const { data, total } = await projectService.findPaginated({ page, limit, status });
16
17 return res.status(200).json({
18 data,
19 meta: {
20 pagination: {
21 page,
22 limit,
23 totalItems: total,
24 totalPages: Math.ceil(total / limit)
25 }
26 }
27 });
28};
29
30// Standardized Error Response Handler
31export const sendError = (res: Response, statusCode: number, code: string, message: string, details?: any[]) => {
32 return res.status(statusCode).json({
33 error: {
34 code,
35 message,
36 details: details || []
37 }
38 });
39};
Summary & Core Takeaways
Key Insights
1REST was formalized by Roy Fielding in 2000 through 6 core constraints, with statelessness unlocking horizontal scaling and cluster load balancing.
2Always start API design from UI wireframes (Figma), extracting nouns into plural resources (/organizations, /projects, /tasks) before writing code.
3GET is safe and idempotent; PUT and PATCH are idempotent modifiers (PUT replaces the entire entity, PATCH updates partial fields); POST is non-idempotent.
4Provide sane defaults for all query parameters (page=1, limit=10, sort=createdAt-desc) so requests never trigger unbounded database scans.
5Standardize error responses with machine-readable error codes and field-level details, and include X-RateLimit headers to inform clients of quota usage.
API Sandbox (2 Live Endpoints)
API Design
Live API

26. REST CRUD with Sane Defaults, Pagination & Sorting

Demonstrates professional REST resource collections (/projects) with automatic fallback sane defaults (page=1, limit=10), status filtering, and sorting.

Headers:Accept: application/json
API Design
Live API

27. Non-CRUD Custom Action & PUT vs PATCH Semantics

Executes a custom business action (POST /projects/:id/clone) that falls outside standard CRUD, creating a duplicated resource draft.

Headers:Content-Type: application/json
Request Body (JSON Payload)Valid JSON