API Design
Overview
This skill guides the design of well-structured APIs that are intuitive, consistent, and scalable. It covers REST, GraphQL, and gRPC patterns with focus on developer experience and long-term maintainability.
Instructions
1. Understand Requirements
- Identify API consumers and use cases
- Define resource models and relationships
- Determine authentication/authorization needs
- Plan for versioning strategy
2. Design Resource Structure
- Define clear resource hierarchies
- Establish naming conventions
- Plan URL patterns
- Design request/response schemas
3. Define Operations
- Map CRUD operations to HTTP methods
- Design query parameters for filtering/sorting
- Plan pagination approach
- Define error response formats
4. Document the API
- Create OpenAPI/Swagger specification
- Include examples for all endpoints
- Document authentication flows
- Provide SDK usage examples
Best Practices
RESTful API Design
- Use Nouns for Resources:
/users, not/getUsers - Consistent Naming: Use snake_case or camelCase consistently across all endpoints
- Proper HTTP Methods:
- GET (read/fetch), POST (create), PUT (full update), PATCH (partial update), DELETE (remove)
- Meaningful Status Codes:
- 200 (OK), 201 (Created), 204 (No Content)
- 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found)
- 409 (Conflict), 422 (Unprocessable Entity)
- 500 (Internal Server Error), 503 (Service Unavailable)
- Idempotency: PUT and DELETE should be idempotent; consider idempotency keys for POST
- HATEOAS: Include links to related resources in responses
- Filtering & Sorting: Use query parameters (
?status=active&sort=-created_at) - Pagination: Cursor-based for large datasets, offset for simpler cases
GraphQL Schema Design
- Use Descriptive Types: Clear, self-documenting type and field names
- Connection Pattern: Use relay-style connections for paginated lists
- Input Objects: Separate input types for mutations (CreateUserInput, UpdateUserInput)
- Error Handling: Return errors as part of payload, not just top-level exceptions
- Nullability: Be explicit about nullable vs non-nullable fields
- Mutations Return Payloads: Include both data and errors in mutation responses
- Avoid Over-fetching: Design fragments and fields for common query patterns
gRPC Service Design
- Service Naming: Use verbs for RPC methods (CreateUser, GetUser, ListUsers)
- Message Reuse: Define shared message types for common patterns
- Pagination: Use page_token pattern for cursor-based pagination
- Errors: Use google.rpc.Status for rich error details
- Versioning: Use package versioning (myapi.v1, myapi.v2)
- Streaming: Leverage server/client/bidirectional streaming where appropriate
API Versioning Strategies
- URL Path Versioning:
/v1/users,/v2/users(explicit, simple, cacheable) - Header Versioning:
API-Version: 2orAccept: application/vnd.api.v2+json(cleaner URLs) - Query Parameter:
?version=2(fallback option, less recommended) - Semantic Versioning: Major versions for breaking changes, minor for features, patch for fixes
- Deprecation Policy: Announce sunset dates, support N-1 versions minimum
Examples
Example 1: RESTful Resource Design
# OpenAPI 3.0 Specification
openapi: 3.0.0
info:
title: E-Commerce API
version: 1.0.0
paths:
/products:
get:
summary: List all products
parameters:
- name: category
in: query
schema:
type: string
- name: min_price
in: query
schema:
type: number
- name: page
in: query
schema:
type: integer
default: 1
- name: per_page
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
"200":
description: Paginated list of products
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/Product"
pagination:
$ref: "#/components/schemas/Pagination"
post:
summary: Create a new product
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ProductInput"
responses:
"201":
description: Product created
"400":
description: Validation error
/products/{id}:
get:
summary: Get a specific product
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: Product details
"404":
description: Product not found
Example 2: GraphQL Schema Design
type Query {
"""
Fetch a user by ID
"""
user(id: ID!): User
"""
List users with optional filtering
"""
users(filter: UserFilter, first: Int = 20, after: String): UserConnection!
}
type Mutation {
"""
Create a new user account
"""
createUser(input: CreateUserInput!): CreateUserPayload!
"""
Update an existing user
"""
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
}
type User {
id: ID!
email: String!
name: String!
createdAt: DateTime!
orders(first: Int, after: String): OrderConnection!
}
input CreateUserInput {
email: String!
name: String!
password: String!
}
type CreateUserPayload {
user: User
errors: [UserError!]!
}
type UserError {
field: String
message: String!
code: UserErrorCode!
}
Example 3: gRPC Service Definition
syntax = "proto3";
package ecommerce.v1;
import "google/protobuf/timestamp.proto";
service ProductService {
// Fetch a single product by ID
rpc GetProduct(GetProductRequest) returns (GetProductResponse);
// List products with filtering and pagination
rpc ListProducts(ListProductsRequest) returns (ListProductsResponse);
// Create a new product
rpc CreateProduct(CreateProductRequest) returns (CreateProductResponse);
// Update an existing product
rpc UpdateProduct(UpdateProductRequest) returns (UpdateProductResponse);
// Delete a product
rpc DeleteProduct(DeleteProductRequest) returns (DeleteProductResponse);
}
message Product {
string id = 1;
string name = 2;
string description = 3;
int64 price_cents = 4;
string category = 5;
google.protobuf.Timestamp created_at = 6;
google.protobuf.Timestamp updated_at = 7;
}
message ListProductsRequest {
// Optional category filter
string category = 1;
// Minimum price filter (in cents)
optional int64 min_price_cents = 2;
// Maximum number of items to return
int32 page_size = 3;
// Page token from previous response
string page_token = 4;
}
message ListProductsResponse {
repeated Product products = 1;
string next_page_token = 2;
int32 total_count = 3;
}
message CreateProductRequest {
string name = 1;
string description = 2;
int64 price_cents = 3;
string category = 4;
}
message CreateProductResponse {
Product product = 1;
}
Example 4: Error Response Design (REST)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Email must be a valid email address"
},
{
"field": "age",
"code": "OUT_OF_RANGE",
"message": "Age must be between 18 and 120"
}
],
"request_id": "req_abc123xyz",
"documentation_url": "https://api.example.com/docs/errors#VALIDATION_ERROR"
}
}
Example 5: API Versioning with Header
# Request with API version header
GET /users/123 HTTP/1.1
Host: api.example.com
Accept: application/vnd.api.v2+json
Authorization: Bearer eyJ...
# Response includes deprecation warning
HTTP/1.1 200 OK
Content-Type: application/vnd.api.v2+json
Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Deprecation: true
Link: <https://api.example.com/v3/users/123>; rel="successor-version"
{
"id": "123",
"email": "user@example.com",
"name": "John Doe"
}