DevToolBox免费
博客

REST API 最佳实践:2026 完整指南

15 分钟阅读作者 DevToolBox

构建设计良好的 REST API 是现代开发者最重要的技能之一。优秀的 API 直观、一致、安全且易于维护。本综合指南涵盖从 URL 命名约定和 HTTP 方法到认证、分页、限流和常见错误的所有内容。无论你是在构建第一个 API 还是在优化现有 API,这些最佳实践都将帮助你创建开发者喜爱的 API。

URL 命名约定

良好 REST API 的基础从精心设计的 URL 开始。你的端点应该是可预测的、可读的,并遵循一致的模式。

使用名词,而非动词

REST 资源应该用名词来标识。HTTP 方法已经指示了操作,因此不需要在 URL 路径中包含动词。将 URL 视为资源的地址,而不是命令。

# Good - nouns as resources
GET    /users          # Get all users
GET    /users/123      # Get user 123
POST   /users          # Create a new user
DELETE /users/123      # Delete user 123

# Bad - verbs in URLs
GET    /getUsers
POST   /createUser
DELETE /deleteUser/123

集合使用复数形式

始终对集合端点使用复数名词。无论是引用集合还是集合中的单个资源,这都保持了一致性。

# Good - consistent plurals
GET /users          # Collection
GET /users/123      # Single resource in collection
GET /products       # Collection
GET /products/456   # Single resource

# Bad - inconsistent or singular
GET /user           # Ambiguous: one user or all users?
GET /user/123
GET /product-list

嵌套资源表示关系

使用嵌套来表达资源之间的关系,但避免超过两层。深层嵌套使 URL 难以阅读和维护。

# Good - clear parent-child relationship (max 2 levels)
GET /users/123/orders          # Orders belonging to user 123
GET /users/123/orders/456      # Specific order of user 123

# Bad - too deeply nested
GET /users/123/orders/456/items/789/reviews
# Better: use a direct resource endpoint
GET /order-items/789/reviews
# Or: flatten with query parameters
GET /reviews?order_item_id=789

URL 模式:好 vs 坏

原因
GET /usersGET /getUsersHTTP method already implies the action
GET /users/123GET /user?id=123Use path parameters for resource identity
POST /usersPOST /createUserPOST already means create
PUT /users/123POST /updateUserPUT/PATCH for updates, use resource path
DELETE /users/123POST /deleteUserUse DELETE method, not POST with verb
GET /users/123/ordersGET /getUserOrders?userId=123Use nested resources for relationships
GET /products?category=electronicsGET /electronics/productsUse query params for filtering
GET /users?status=active&sort=nameGET /active-users-sorted-by-nameUse query params, not encoded URLs
PATCH /users/123POST /users/123/updateUse HTTP methods, not URL verbs
GET /users/123/avatarGET /getAvatarForUser/123Keep it resource-oriented

HTTP 方法

每个 HTTP 方法都有特定的语义。正确使用它们使你的 API 可预测,并允许客户端无需阅读文档即可理解每个请求的意图。

方法用途幂等性请求体示例
GETRetrieve a resource or collectionYesNoGET /users/123
POSTCreate a new resourceNoYesPOST /users
PUTReplace a resource entirelyYesYesPUT /users/123
PATCHPartially update a resourceNo*YesPATCH /users/123
DELETERemove a resourceYesOptionalDELETE /users/123

* PATCH can be idempotent depending on implementation. If the patch describes the final state (e.g., {"name": "John"}), it is idempotent. If it describes an operation (e.g., {"op": "increment", "path": "/count"}), it is not.

HTTP 状态码

使用正确的状态码帮助客户端适当地处理响应。以下是 REST API 最重要的状态码及其使用时机。

状态码名称何时使用
200OKSuccessful GET, PUT, PATCH, or DELETE. Return the resource or confirmation.
201CreatedSuccessful POST that created a new resource. Include Location header with the new resource URL.
204No ContentSuccessful DELETE or PUT/PATCH when no response body is needed.
400Bad RequestThe request is malformed — invalid JSON, missing required fields, wrong data types.
401UnauthorizedNo valid authentication credentials provided. The client must authenticate first.
403ForbiddenThe client is authenticated but does not have permission for this action.
404Not FoundThe requested resource does not exist. Also use for hidden resources (instead of 403) for security.
409ConflictThe request conflicts with the current state — duplicate email, version mismatch, etc.
422Unprocessable EntityThe request is well-formed but fails validation — email format invalid, password too short.
429Too Many RequestsRate limit exceeded. Include Retry-After header with seconds until the client can retry.
500Internal Server ErrorAn unexpected server error occurred. Log the details server-side but do not expose them to clients.
503Service UnavailableThe server is temporarily unavailable — maintenance mode, overloaded. Include Retry-After header.

错误处理

一致的错误响应使你的 API 更容易使用。每个错误都应该返回结构化的 JSON 正文,包含足够的信息让客户端理解和修复问题。

标准错误响应格式

对所有错误响应使用一致的错误封装。包括机器可读的错误代码、人类可读的消息和可选的详细信息。

// Standard error response
{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "The user with ID 123 was not found.",
    "status": 404,
    "timestamp": "2026-01-15T10:30:00Z",
    "request_id": "req_abc123def456",
    "documentation_url": "https://api.example.com/docs/errors#RESOURCE_NOT_FOUND"
  }
}

// Another example: authentication error
{
  "error": {
    "code": "TOKEN_EXPIRED",
    "message": "The access token has expired. Please refresh your token.",
    "status": 401,
    "timestamp": "2026-01-15T10:30:00Z",
    "request_id": "req_xyz789"
  }
}

带字段级详情的验证错误

对于 422 验证错误,包含字段级错误信息,以便客户端在正确的表单字段旁显示错误。

// 422 Validation error with field-level details
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "The request body contains invalid fields.",
    "status": 422,
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address.",
        "rejected_value": "not-an-email"
      },
      {
        "field": "password",
        "message": "Must be at least 8 characters long.",
        "rejected_value": "short"
      },
      {
        "field": "age",
        "message": "Must be a positive integer.",
        "rejected_value": -5
      }
    ],
    "timestamp": "2026-01-15T10:30:00Z",
    "request_id": "req_val456"
  }
}

认证与授权

选择正确的认证策略取决于你的使用场景。以下是三种最常见方法的比较。

API 密钥

通过头部或查询参数传递的简单字符串令牌。最适合服务器间通信和简单集成。易于实现但安全性有限,因为密钥是长期有效的且难以限定范围。

# API Key in header (recommended)
GET /api/users HTTP/1.1
Host: api.example.com
X-API-Key: sk_live_abc123def456ghi789

# API Key in query parameter (less secure - visible in logs)
GET /api/users?api_key=sk_live_abc123def456ghi789

JWT Bearer 令牌

携带用户声明的自包含令牌。服务器可以在不查询数据库的情况下验证令牌。非常适合微服务中的无状态认证。令牌应该是短期的,配合刷新令牌轮换。

# JWT Bearer Token in Authorization header
GET /api/users/me HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

# Token refresh flow
POST /api/auth/refresh HTTP/1.1
Content-Type: application/json
{
  "refresh_token": "rt_abc123def456"
}

# Response
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rt_new789xyz"
}

OAuth 2.0

委托授权的行业标准。允许第三方应用代表用户访问资源而不暴露凭证。实现更复杂但提供最大的灵活性和安全性。

# OAuth 2.0 Authorization Code Flow

# Step 1: Redirect user to authorization server
GET https://auth.example.com/authorize?
  response_type=code&
  client_id=your_client_id&
  redirect_uri=https://yourapp.com/callback&
  scope=read:users write:users&
  state=random_csrf_token

# Step 2: Exchange authorization code for tokens
POST https://auth.example.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=AUTH_CODE_FROM_CALLBACK&
client_id=your_client_id&
client_secret=your_client_secret&
redirect_uri=https://yourapp.com/callback

# Step 3: Use the access token
GET /api/users/me HTTP/1.1
Authorization: Bearer ACCESS_TOKEN_HERE

认证方式比较

特性API KeyJWTOAuth 2.0
ComplexityLowMediumHigh
StatelessYesYesDepends
User contextNoYesYes
Token expirationManual rotationBuilt-in (exp claim)Built-in
Scope/permissionsLimitedVia claimsFine-grained scopes
Third-party accessNoNoYes (delegated)
Best forServer-to-serverSPAs, Mobile appsThird-party integrations
RevocationDelete keyBlacklist neededRevoke at auth server

分页模式

任何返回资源列表的端点都应该支持分页。没有分页,随着数据增长,响应可能变得巨大且缓慢。

基于偏移的分页

最简单的方法。使用页码和限制参数。易于实现并允许跳转到任何页面,但当数据在请求之间被修改时可能不一致。

# Offset-based pagination request
GET /api/users?page=2&limit=20

# How it works internally
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 20 OFFSET 20;  -- Skip first 20, return next 20

# Pros: Simple, allows jumping to any page
# Cons: Inconsistent with concurrent modifications,
#        slow on large offsets (OFFSET 100000)

基于游标的分页

使用不透明的游标令牌来标记数据集中的位置。对于实时数据和大型数据集更可靠,因为当数据变化时不会跳过或重复项目。

# Cursor-based pagination request
GET /api/users?cursor=eyJpZCI6MTIzfQ&limit=20

# The cursor is an opaque token (often base64-encoded)
# that points to a specific position in the dataset

# How it works internally (cursor decodes to {"id": 123})
SELECT * FROM users
WHERE id > 123
ORDER BY id ASC
LIMIT 20;

# Pros: Consistent results, fast on large datasets
# Cons: Cannot jump to arbitrary pages,
#        only next/previous navigation

分页响应格式

在每个分页响应中包含分页状态的元数据。这允许客户端构建导航控件。

// Offset-based pagination response
{
  "data": [
    { "id": 21, "name": "Alice" },
    { "id": 22, "name": "Bob" }
    // ... 18 more items
  ],
  "meta": {
    "current_page": 2,
    "per_page": 20,
    "total_pages": 15,
    "total_count": 294
  },
  "links": {
    "self": "/api/users?page=2&limit=20",
    "first": "/api/users?page=1&limit=20",
    "prev": "/api/users?page=1&limit=20",
    "next": "/api/users?page=3&limit=20",
    "last": "/api/users?page=15&limit=20"
  }
}

// Cursor-based pagination response
{
  "data": [
    { "id": 124, "name": "Charlie" },
    { "id": 125, "name": "Diana" }
    // ... 18 more items
  ],
  "meta": {
    "has_more": true,
    "next_cursor": "eyJpZCI6MTQzfQ",
    "prev_cursor": "eyJpZCI6MTI0fQ"
  },
  "links": {
    "next": "/api/users?cursor=eyJpZCI6MTQzfQ&limit=20",
    "prev": "/api/users?cursor=eyJpZCI6MTI0fQ&limit=20&direction=prev"
  }
}

API 版本控制策略

API 随时间演进。版本控制允许你在不中断现有客户端的情况下进行破坏性更改。以下是三种最常见的方法。

策略示例优点缺点
URL Path/v1/users, /v2/usersExplicit, easy to route and cache, clear in logs and docsURL pollution, harder to sunset old versions
Custom HeaderAccept-Version: v2 or X-API-Version: 2Clean URLs, version hidden from casual usersHarder to test (need tools to set headers), not cacheable by default
Query Parameter/users?version=2Easy to test in browser, no URL path changesCan be accidentally omitted, harder to route, pollutes query string
Content NegotiationAccept: application/vnd.api.v2+jsonMost RESTful approach, per-resource versioningComplex, hard to test, poorly supported by many tools
# URL Path versioning (recommended)
GET /v1/users/123        # Version 1
GET /v2/users/123        # Version 2 with breaking changes

# Express.js router example
const v1Router = express.Router();
const v2Router = express.Router();

v1Router.get('/users/:id', v1UserController.get);
v2Router.get('/users/:id', v2UserController.get);

app.use('/v1', v1Router);
app.use('/v2', v2Router);

限流

限流保护你的 API 免受滥用并确保所有客户端的公平使用。始终通过响应头传达限流状态。

限流头部

在每个响应中包含这些头部,以便客户端监控其使用情况并避免达到限制。

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 1000          # Max requests per window
X-RateLimit-Remaining: 847       # Remaining requests in window
X-RateLimit-Reset: 1706176800    # Unix timestamp when window resets
X-RateLimit-Window: 3600         # Window duration in seconds (1 hour)

# Some APIs also include:
RateLimit-Policy: 1000;w=3600    # IETF draft standard format

429 请求过多响应

当客户端超过限流时,返回带有 Retry-After 头的 429 状态,指示他们何时可以再次发出请求。

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 45
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1706176800

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "You have exceeded the rate limit of 1000 requests per hour.",
    "status": 429,
    "retry_after": 45,
    "documentation_url": "https://api.example.com/docs/rate-limiting"
  }
}

退避策略

客户端在收到 429 响应时应实现指数退避。从短延迟开始,每次重试加倍,直到最大值。

// Exponential backoff with jitter
async function fetchWithRetry(url, options, maxRetries = 5) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status !== 429) {
      return response;
    }

    // Use Retry-After header if available
    const retryAfter = response.headers.get('Retry-After');
    let delay;

    if (retryAfter) {
      delay = parseInt(retryAfter, 10) * 1000;
    } else {
      // Exponential backoff: 1s, 2s, 4s, 8s, 16s
      const baseDelay = Math.pow(2, attempt) * 1000;
      // Add jitter (random 0-1s) to prevent thundering herd
      const jitter = Math.random() * 1000;
      delay = baseDelay + jitter;
    }

    console.log(`Rate limited. Retrying in ${delay}ms (attempt ${attempt + 1})`);
    await new Promise(resolve => setTimeout(resolve, delay));
  }

  throw new Error('Max retries exceeded');
}

安全头部

安全头部保护你的 API 免受常见的 Web 漏洞。这些头部应包含在 API 的每个响应中。

头部推荐值用途
Access-Control-Allow-Originhttps://yourapp.com (specific origin)CORS: Controls which domains can call your API. Never use * in production with credentials.
Access-Control-Allow-MethodsGET, POST, PUT, PATCH, DELETECORS: Specifies which HTTP methods are allowed for cross-origin requests.
Access-Control-Allow-HeadersAuthorization, Content-TypeCORS: Specifies which request headers are allowed in cross-origin requests.
Strict-Transport-Securitymax-age=31536000; includeSubDomainsHSTS: Forces browsers to use HTTPS for all future requests to your domain.
Content-Security-Policydefault-src 'none'; frame-ancestors 'none'CSP: Prevents XSS and data injection attacks. For APIs, restrict everything.
X-Content-Type-OptionsnosniffPrevents browsers from MIME-type sniffing. Ensures responses are treated as their declared content type.
X-Frame-OptionsDENYPrevents your API responses from being embedded in iframes (clickjacking protection).
Cache-Controlno-store, no-cache, must-revalidatePrevents caching of sensitive API responses. Adjust per endpoint as needed.
// Express.js security headers middleware
const helmet = require('helmet');

app.use(helmet());

// Custom CORS configuration
const cors = require('cors');
app.use(cors({
  origin: ['https://yourapp.com', 'https://admin.yourapp.com'],
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Authorization', 'Content-Type'],
  credentials: true,
  maxAge: 86400  // Cache preflight for 24 hours
}));

// Additional security headers
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('Cache-Control', 'no-store');
  next();
});

使用 OpenAPI 编写 API 文档

良好的文档对 API 的采用至关重要。OpenAPI(以前称为 Swagger)是描述 REST API 的标准。它支持自动生成文档、客户端 SDK 和测试工具。

  • 先编写 OpenAPI 规范(设计优先方法),然后实现 API 以匹配规范。
  • 为每个端点、参数和响应 schema 包含详细描述。
  • 为所有请求和响应正文提供真实的示例值。
  • 为每个端点记录错误响应,不仅仅是成功情况。
  • 使用标签对相关端点进行分组,使文档易于导航。
  • 将 OpenAPI 规范与 API 代码一起进行版本控制。
  • 设置交互式文档(Swagger UI 或 Redoc),以便开发者可以直接尝试请求。
# OpenAPI 3.1 specification example
openapi: 3.1.0
info:
  title: User Management API
  version: 1.0.0
  description: API for managing users and their resources

servers:
  - url: https://api.example.com/v1

paths:
  /users:
    get:
      summary: List all users
      tags: [Users]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        '200':
          description: A paginated list of users
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserListResponse'
              example:
                data:
                  - id: 1
                    name: "Alice Johnson"
                    email: "alice@example.com"
                meta:
                  current_page: 1
                  total_pages: 10
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      summary: Create a new user
      tags: [Users]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
            example:
              name: "Bob Smith"
              email: "bob@example.com"
              password: "securePassword123"
      responses:
        '201':
          description: User created successfully
        '422':
          $ref: '#/components/responses/ValidationError'

常见 REST API 错误

即使是经验丰富的开发者也会犯这些错误。查看此列表以确保你的 API 遵循最佳实践。

#错误为什么是错的修复方法
1Using verbs in URLsREST is resource-oriented; HTTP methods express actionsUse nouns: /users not /getUsers
2Returning 200 for errorsClients rely on status codes for error handlingUse proper status codes: 400, 404, 422, 500
3Not paginating list endpointsReturns grow unbounded, causing timeouts and memory issuesAlways paginate with sensible defaults (limit=20)
4Exposing internal errorsStack traces reveal implementation details to attackersLog details server-side, return generic error messages
5Ignoring CORS headersBrowser-based clients cannot call your APIConfigure CORS for allowed origins and methods
6Not versioning the APIBreaking changes affect all clients simultaneouslyUse URL path versioning: /v1/users
7Using POST for everythingLoses HTTP method semantics, breaks caching and idempotencyUse GET, POST, PUT, PATCH, DELETE appropriately
8Inconsistent naming conventionsMixed case or formats confuse API consumersPick one (snake_case or camelCase) and be consistent
9No rate limitingAPI is vulnerable to abuse and DDoS attacksImplement rate limiting with clear headers
10Missing Content-Type headerClients cannot parse responses correctlyAlways set Content-Type: application/json

试试这些相关开发者工具

常见问题

REST 和 RESTful 有什么区别?

REST(表述性状态转移)是 Roy Fielding 定义的架构风格。RESTful 是描述遵守 REST 约束的 API 的形容词。实际上,大多数"REST API"并未严格遵循所有 REST 约束(如 HATEOAS),但这两个术语可以互换使用。

更新资源应该使用 PUT 还是 PATCH?

当客户端发送资源的完整替换时使用 PUT。对于只发送更改字段的部分更新使用 PATCH。PATCH 对大型资源更节省带宽。大多数现代 API 倾向于对典型更新操作使用 PATCH。

应该如何处理 API 版本控制?

URL 路径版本控制(例如 /v1/users)是最常见且最明确的方法。它易于理解、路由和缓存。头部版本控制更简洁但更难测试和调试。选择一种策略并在整个 API 中一致应用。

在 REST API 中处理认证的最佳方式是什么?

对于大多数应用,JWT Bearer 令牌提供了安全性和简单性的良好平衡。使用短期访问令牌(15-60 分钟)配合刷新令牌轮换。对于第三方集成,使用 OAuth 2.0。API 密钥仅适用于服务器间通信。

如何在 REST API 中设计嵌套资源?

使用嵌套显示父子关系:/users/123/orders。最多限制两层嵌套。对于更深的关系,使用查询参数或提供直接资源端点。例如,用 /order-items/789 或 /orders/456/items/789 代替 /users/123/orders/456/items/789。

REST API 响应应该包含 null 字段还是省略它们?

这取决于你的约定。包含 null 字段使 schema 可预测——客户端总是知道期望什么字段。省略 null 字段减少负载大小。保持一致:选择一种方法并记录下来。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

4xxHTTP Status Code Reference>>cURL to Code ConverterJWTJWT Decoder{ }JSON Formatter

相关文章

HTTP 状态码:开发者完全参考指南

完整的 HTTP 状态码参考:1xx 到 5xx,包含实用解释、API 最佳实践和常见调试技巧。

API 认证:OAuth 2.0 vs JWT vs API Key

比较 API 认证方式:OAuth 2.0、JWT Bearer Token 和 API Key。了解每种方式的适用场景、安全权衡和实现模式。