Published on

REST and gRPC API Design Best Practices (2026 Edition)

views·10 mins read

REST and gRPC API Design Best Practices

Comprehensive API design ensures scalability, security, maintainability, and excellent developer experience across distributed systems. Perfect for NextJS + Supabase travel platforms and sports booking systems.

REST API Design Principles

1. Resource-Oriented Design

Treat everything as nouns (resources), never verbs in URIs.

✅ Correct Patterns:

GET    /users                    # List users
GET    /users/123                # Get specific user
POST   /users                    # Create user
PUT    /users/123                # Replace user completely
PATCH  /users/123                # Partial update user
DELETE /users/123                # Delete user
GET    /users/123/orders         # Nested resources (shallow)
GET    /products?category=travel # Filtering

❌ Avoid These Anti-Patterns:

GET    /getUsers              # Verbs in URI
POST   /createUser            # Action-based endpoints
GET    /users/search?q=john   # Search logic in path
PUT    /users/123/activate    # State change in URI

2. HTTP Methods & Semantic Correctness

MethodIdempotentSafeUse CaseStatus CodeResponse Body
GET✅ Yes✅ YesRetrieve200 OKResource(s)
POST❌ No❌ NoCreate201 CreatedCreated resource
PUT✅ Yes❌ NoReplace/Create200 OK / 201Updated resource
PATCH⚠️ Maybe❌ NoPartial Update200 OKUpdated resource
DELETE✅ Yes❌ NoDelete204 No ContentNone

Golden Rules:

  • Always return Location: /users/123 header on 201 Created.
  • GET responses can be cached, POST/PUT/PATCH/DELETE typically cannot.
  • Use 202 Accepted for async operations (email sending, file processing).

3. Standardized Response Envelope

Consistent structure across ALL endpoints:

{
  "data": {
    "id": "user-123",
    "email": "john@example.com",
    "name": "John Doe"
  },
  "meta": {
    "pagination": {
      "total": 150,
      "page": 2,
      "limit": 25,
      "pages": 6,
      "has_next": true,
      "has_prev": true
    },
    "trace_id": "req-abc123def456",
    "rate_limit": {
      "remaining": 998,
      "reset": 1642497600
    }
  },
  "links": {
    "self": "/v1/users?page=2&limit=25",
    "next": "/v1/users?page=3&limit=25",
    "first": "/v1/users?page=1&limit=25",
    "last": "/v1/users?page=6&limit=25"
  }
}

Error Response Format:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input provided",
    "details": {
      "field_errors": {
        "email": ["Invalid email format"],
        "age": ["Must be greater than 18"],
        "password": ["Must be at least 8 characters"]
      }
    },
    "request_id": "req-abc123def456",
    "timestamp": "2026-01-18T14:30:00Z",
    "retry_after": 60,
    "docs": "/docs/errors/VALIDATION_ERROR"
  }
}

4. Query Parameter Conventions

Standardize ALL filtering, sorting, and pagination:

GET /v1/products?category=electronics&min_price=100&max_price=1000
                   &sort=-price,created_at&limit=20&offset=40
                   &fields=id,name,price&include=category,reviews
                   &status=active,pending&search=iphone

Parameter Reference:

ParameterPurposeExample
categoryFilter?category=electronics
min_price, max_priceRange filter?min_price=100&max_price=1000
sortMulti-field sort?sort=-price,created_at (- = DESC)
limit/offsetOffset pagination?limit=20&offset=40
after/beforeCursor pagination?after=user_456
fieldsSparse fieldsets?fields=id,name,price
includeRelationship inclusion?include=category,reviews
statusEnum filter?status=active,pending
searchFull-text search?search=iphone

5. HTTP Status Codes Reference

2xx Success:

200 OK - GET success
201 Created - POST/PUT success (new resource)
202 Accepted - Async operation accepted
204 No Content - DELETE success

4xx Client Error:

400 Bad Request - Malformed request
401 Unauthorized - Missing/wrong auth
403 Forbidden - Authenticated but no permission
404 Not Found - Resource doesn't exist
409 Conflict - Business rule violation
422 Unprocessable Entity - Valid JSON, invalid data
429 Too Many Requests - Rate limited

5xx Server Error:

500 Internal Server Error - Unexpected error
503 Service Unavailable - Maintenance/overload

REST Advanced Patterns

6. API Versioning Strategies

URI Versioning (Recommended for Public APIs):

GET /v1/users/123
GET /v2/users/123  # Breaking changes only

Header Versioning (Internal APIs):

Accept: application/vnd.myapi.v1+json
Accept: application/vnd.myapi.v2+json

Deprecation Headers (Graceful Migration):

Sunset: Sun, 31 Dec 2026 23:59:59 GMT
Deprecation: Wed, 01 Jan 2026 00:00:00 GMT
Link: </v2/migrate>; rel="upgrade"

7. HATEOAS (Hypermedia as Engine of Application State)

Self-discoverable APIs:

{
  "id": "user-123",
  "name": "John Doe",
  "status": "active",
  "_links": {
    "self": { 
      "href": "/v1/users/user-123",
      "method": "GET",
      "title": "Get user details"
    },
    "profile": { 
      "href": "/v1/users/user-123/profile",
      "method": "GET"
    },
    "orders": { 
      "href": "/v1/users/user-123/orders",
      "method": "GET",
      "title": "View orders",
      "deprecated": false
    },
    "update": {
      "href": "/v1/users/user-123",
      "method": "PATCH",
      "title": "Update profile",
      "required_scopes": ["users:write"]
    },
    "suspend": {
      "href": "/v1/users/user-123/suspend",
      "method": "POST",
      "title": "Suspend account",
      "required_scopes": ["users:admin"]
    }
  }
}

8. Caching & Performance Optimization

Cache-Control Headers:

Cache-Control: public, max-age=3600, stale-while-revalidate=86400, stale-if-error=86400
ETag: "686897696a7c876b7e"
Last-Modified: Wed, 18 Jan 2026 14:30:00 GMT
Vary: Accept-Encoding, Accept-Language

Conditional Requests:

If-None-Match: "686897696a7c876b7e"
If-Modified-Since: Wed, 18 Jan 2026 14:30:00 GMT

gRPC Design Principles

9. Protocol Buffer (.proto) Best Practices

1-1-1 Rule: One message/service per file

// File: api/v1/users.proto
syntax = "proto3";
package api.v1;

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
import "google/api/annotations.proto";

// User Service
service UserService {
  rpc GetUser(GetUserRequest) returns (User) {
    option (google.api.http) = {
      get: "/v1/users/{id}"
    };
  }
  
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
  rpc CreateUser(CreateUserRequest) returns (User);
  rpc BatchUpdateUsers(stream UpdateUserRequest) returns (BatchUpdateUsersResponse);
  rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);
}

// Requests
message GetUserRequest {
  string id = 1;
}

message ListUsersRequest {
  int32 page_size = 1;
  string page_token = 2;
  repeated string status = 3;
}

message CreateUserRequest {
  CreateUserPayload user = 1;
}

message CreateUserPayload {
  string email = 1;
  string name = 2;
  string phone = 3;
}

// Responses
message User {
  string id = 1;
  string email = 2;
  string name = 3;
  google.protobuf.Timestamp created_at = 4;
  google.protobuf.Timestamp updated_at = 5;
  UserStatus status = 6;
}

message ListUsersResponse {
  repeated User users = 1;
  string next_page_token = 2;
}

enum UserStatus {
  USER_STATUS_UNSPECIFIED = 0;
  USER_STATUS_ACTIVE = 1;
  USER_STATUS_INACTIVE = 2;
  USER_STATUS_SUSPENDED = 3;
  USER_STATUS_DELETED = 4;
}

10. Backward/Forward Compatibility Rules

✅ Safe Changes:

✓ Add new fields (unique field numbers)
✓ Delete fields (mark as `reserved`)
✓ Add new enum values
✓ Change enum value names
✓ Add new messages/services

❌ Breaking Changes:

✗ Remove fields without `reserved`
✗ Change field numbers
✗ Change field types
✗ Remove enum values
✗ Rename messages/services

Field Evolution Example:

// v1: users.proto
message User {
  string id = 1;
  string name = 2;        // Will be deprecated
  string email = 3;
}

// v2: users.proto (backward compatible)
message User {
  string id = 1;
  reserved 2;             // Deprecated field
  reserved "name";        // Also reserve by name
  string full_name = 4;   // New field
  string email = 3;
}

11. gRPC Service Patterns

Method Types:

// 1. Unary RPC (REST equivalent)
rpc GetUser(GetUserRequest) returns (User);

// 2. Server Streaming (Paginated lists)
rpc ListUsers(ListUsersRequest) returns (stream User);

// 3. Client Streaming (Bulk operations)
rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchResponse);

// 4. Bidirectional Streaming (Real-time)
rpc Chat(stream ChatMessage) returns (stream ChatMessage);

Production Security Patterns

12. Authentication & Authorization

REST (Stateless JWT + OAuth2):

Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
X-Requested-With: XMLHttpRequest
X-Client-ID: travel-app-v1.2.3

gRPC (Metadata + mTLS):

Metadata Headers:
authorization: Bearer <jwt_token>
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
user-agent: TravelApp/1.2.3

13. Rate Limiting & Throttling

Standard Headers:

X-RateLimit-Limit: 1000              # Max requests per window
X-RateLimit-Remaining: 999           # Remaining requests
X-RateLimit-Reset: 1642497600        # Unix timestamp
Retry-After: 3600                    # Seconds until reset
X-RateLimit-Interval: 3600           # Window size in seconds

429 Response:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests",
    "retry_after": 3600,
    "rate_limit": {
      "limit": 1000,
      "remaining": 0,
      "reset": 1642497600
    }
  }
}

14. CORS Configuration

Access-Control-Allow-Origin: https://travel-app.com
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,OPTIONS
Access-Control-Allow-Headers: Content-Type,Authorization,X-Requested-With
Access-Control-Max-Age: 86400
Access-Control-Expose-Headers: X-RateLimit-Remaining,ETag

Performance Optimization

15. Compression & Encoding

REST:

Accept-Encoding: gzip, deflate, br
Content-Encoding: br
Transfer-Encoding: chunked

gRPC (Automatic):

  • Protocol Buffers: 3-10x smaller than JSON
  • HTTP/2 HPACK header compression
  • Built-in message compression

16. HTTP/2 Benefits for Both

✅ Multiple requests over single TCP connection (Multiplexing)
✅ Binary framing + Header compression (HPACK)
✅ Server push capability
✅ Stream prioritization
✅ Flow control

Documentation Standards

17. REST - OpenAPI 3.1

openapi: 3.1.0
info:
  title: Travel Platform API
  description: Travel booking and management API
  version: 1.0.0
servers:
  - url: https://api.travelapp.com/v1
    description: Production
  - url: https://staging-api.travelapp.com/v1
    description: Staging
paths:
  /users/{id}:
    get:
      summary: Get user profile
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: "user-123"
      responses:
        '200':
          description: User found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        email:
          type: string
          format: email
        name:
          type: string
  responses:
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

18. gRPC Documentation

# Generate Markdown docs from .proto
protoc --doc_out=./docs --doc_opt=markdown,api.md api/v1/*.proto

# Generate OpenAPI from gRPC (gateway)
protoc --openapi_out=./docs --proto_path=. api/v1/*.proto