API Design Patterns & Best Practices
Улучшите Claude Code с помощью шаблонов проектирования REST API. Стандартизируйте именование ресурсов, коды состояния, пагинацию и версионирование для API промышленного уровня.
Этот навык предоставляет Claude всестороннюю основу для проектирования согласованных, удобных для разработчика REST API. Он включает глубокую экспертизу в области структуры URL ресурсов, семантических HTTP-статусов и продвинутых функций, таких как курсорная пагинация и ограничение скорости. Создаете ли вы новый микросервис или рефакторите существующий эндпоинт, этот навык гарантирует, что ваш API будет соответствовать отраслевым стандартам версионирования, безопасности и форматирования ответов в различных средах, включая TypeScript, Python и Go.
Ключевые возможности
Варианты использования
| name | api-design | ||
|---|---|---|---|
| description | REST API design patterns including resource naming, status codes, pagination, filtering, error responses, versioning, and rate limiting for production APIs. | ||
| metadata |
|
Conventions and best practices for designing consistent, developer-friendly REST APIs.
- Designing new API endpoints
- Reviewing existing API contracts
- Adding pagination, filtering, or sorting
- Implementing error handling for APIs
- Planning API versioning strategy
- Building public or partner-facing APIs
# Resources are nouns, plural, lowercase, kebab-case
GET /api/v1/users
GET /api/v1/users/:id
POST /api/v1/users
PUT /api/v1/users/:id
PATCH /api/v1/users/:id
DELETE /api/v1/users/:id
# Sub-resources for relationships
GET /api/v1/users/:id/orders
POST /api/v1/users/:id/orders
# Actions that don't map to CRUD (use verbs sparingly)
POST /api/v1/orders/:id/cancel
POST /api/v1/auth/login
POST /api/v1/auth/refresh
# GOOD
/api/v1/team-members # kebab-case for multi-word resources
/api/v1/orders?status=active # query params for filtering
/api/v1/users/123/orders # nested resources for ownership
# BAD
/api/v1/getUsers # verb in URL
/api/v1/user # singular (use plural)
/api/v1/team_members # snake_case in URLs
/api/v1/users/123/getOrders # verb in nested resource
| Method | Idempotent | Safe | Use For |
|---|---|---|---|
| GET | Yes | Yes | Retrieve resources |
| POST | No | No | Create resources, trigger actions |
| PUT | Yes | No | Full replacement of a resource |
| PATCH | No* | No | Partial update of a resource |
| DELETE | Yes | No | Remove a resource |
*PATCH can be made idempotent with proper implementation
# Success
200 OK — GET, PUT, PATCH (with response body)
201 Created — POST (include Location header)
204 No Content — DELETE, PUT (no response body)
# Client Errors
400 Bad Request — Validation failure, malformed JSON
401 Unauthorized — Missing or invalid authentication
403 Forbidden — Authenticated but not authorized
404 Not Found — Resource doesn't exist
409 Conflict — Duplicate entry, state conflict
422 Unprocessable Entity — Semantically invalid (valid JSON, bad data)
429 Too Many Requests — Rate limit exceeded
# Server Errors
500 Internal Server Error — Unexpected failure (never expose details)
502 Bad Gateway — Upstream service failed
503 Service Unavailable — Temporary overload, include Retry-After
# BAD: 200 for everything
{ "status": 200, "success": false, "error": "Not found" }
# GOOD: Use HTTP status codes semantically
HTTP/1.1 404 Not Found
{ "error": { "code": "not_found", "message": "User not found" } }
# BAD: 500 for validation errors
# GOOD: 400 or 422 with field-level details
# BAD: 200 for created resources
# GOOD: 201 with Location header
HTTP/1.1 201 Created
Location: /api/v1/users/abc-123
{
"data": {
"id": "abc-123",
"email": "alice@example.com",
"name": "Alice",
"created_at": "2025-01-15T10:30:00Z"
}
}{
"data": [
{ "id": "abc-123", "name": "Alice" },
{ "id": "def-456", "name": "Bob" }
],
"meta": {
"total": 142,
"page": 1,
"per_page": 20,
"total_pages": 8
},
"links": {
"self": "/api/v1/users?page=1&per_page=20",
"next": "/api/v1/users?page=2&per_page=20",
"last": "/api/v1/users?page=8&per_page=20"
}
}{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Must be a valid email address",
"code": "invalid_format"
},
{
"field": "age",
"message": "Must be between 0 and 150",
"code": "out_of_range"
}
]
}
}// Option A: Envelope with data wrapper (recommended for public APIs)
interface ApiResponse<T> {
data: T;
meta?: PaginationMeta;
links?: PaginationLinks;
}
interface ApiError {
error: {
code: string;
message: string;
details?: FieldError[];
};
}
// Option B: Flat response (simpler, common for internal APIs)
// Success: just return the resource directly
// Error: return error object
// Distinguish by HTTP status codeGET /api/v1/users?page=2&per_page=20
# Implementation
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 20 OFFSET 20;
Pros: Easy to implement, supports "jump to page N" Cons: Slow on large offsets (OFFSET 100000), inconsistent with concurrent inserts
GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20
# Implementation
SELECT * FROM users
WHERE id > :cursor_id
ORDER BY id ASC
LIMIT 21; -- fetch one extra to determine has_next
{
"data": [...],
"meta": {
"has_next": true,
"next_cursor": "eyJpZCI6MTQzfQ"
}
}Pros: Consistent performance regardless of position, stable with concurrent inserts Cons: Cannot jump to arbitrary page, cursor is opaque
| Use Case | Pagination Type |
|---|---|
| Admin dashboards, small datasets (<10K) | Offset |
| Infinite scroll, feeds, large datasets | Cursor |
| Public APIs | Cursor (default) with offset (optional) |
| Search results | Offset (users expect page numbers) |
# Simple equality
GET /api/v1/orders?status=active&customer_id=abc-123
# Comparison operators (use bracket notation)
GET /api/v1/products?price[gte]=10&price[lte]=100
GET /api/v1/orders?created_at[after]=2025-01-01
# Multiple values (comma-separated)
GET /api/v1/products?category=electronics,clothing
# Nested fields (dot notation)
GET /api/v1/orders?customer.country=US
# Single field (prefix - for descending)
GET /api/v1/products?sort=-created_at
# Multiple fields (comma-separated)
GET /api/v1/products?sort=-featured,price,-created_at
# Search query parameter
GET /api/v1/products?q=wireless+headphones
# Field-specific search
GET /api/v1/users?email=alice
# Return only specified fields (reduces payload)
GET /api/v1/users?fields=id,name,email
GET /api/v1/orders?fields=id,total,status&include=customer.name
# Bearer token in Authorization header
GET /api/v1/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
# API key (for server-to-server)
GET /api/v1/data
X-API-Key: sk_live_abc123
// Resource-level: check ownership
app.get("/api/v1/orders/:id", async (req, res) => {
const order = await Order.findById(req.params.id);
if (!order) return res.status(404).json({ error: { code: "not_found" } });
if (order.userId !== req.user.id) return res.status(403).json({ error: { code: "forbidden" } });
return res.json({ data: order });
});
// Role-based: check permissions
app.delete("/api/v1/users/:id", requireRole("admin"), async (req, res) => {
await User.delete(req.params.id);
return res.status(204).send();
});HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000
# When exceeded
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Try again in 60 seconds."
}
}
| Tier | Limit | Window | Use Case |
|---|---|---|---|
| Anonymous | 30/min | Per IP | Public endpoints |
| Authenticated | 100/min | Per user | Standard API access |
| Premium | 1000/min | Per API key | Paid API plans |
| Internal | 10000/min | Per service | Service-to-service |
/api/v1/users
/api/v2/users
Pros: Explicit, easy to route, cacheable Cons: URL changes between versions
GET /api/users
Accept: application/vnd.myapp.v2+json
Pros: Clean URLs Cons: Harder to test, easy to forget
1. Start with /api/v1/ — don't version until you need to
2. Maintain at most 2 active versions (current + previous)
3. Deprecation timeline:
- Announce deprecation (6 months notice for public APIs)
- Add Sunset header: Sunset: Sat, 01 Jan 2026 00:00:00 GMT
- Return 410 Gone after sunset date
4. Non-breaking changes don't need a new version:
- Adding new fields to responses
- Adding new optional query parameters
- Adding new endpoints
5. Breaking changes require a new version:
- Removing or renaming fields
- Changing field types
- Changing URL structure
- Changing authentication method
import { z } from "zod";
import { NextRequest, NextResponse } from "next/server";
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
});
export async function POST(req: NextRequest) {
const body = await req.json();
const parsed = createUserSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json({
error: {
code: "validation_error",
message: "Request validation failed",
details: parsed.error.issues.map(i => ({
field: i.path.join("."),
message: i.message,
code: i.code,
})),
},
}, { status: 422 });
}
const user = await createUser(parsed.data);
return NextResponse.json(
{ data: user },
{
status: 201,
headers: { Location: `/api/v1/users/${user.id}` },
},
);
}from rest_framework import serializers, viewsets, status
from rest_framework.response import Response
class CreateUserSerializer(serializers.Serializer):
email = serializers.EmailField()
name = serializers.CharField(max_length=100)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "email", "name", "created_at"]
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
if self.action == "create":
return CreateUserSerializer
return UserSerializer
def create(self, request):
serializer = CreateUserSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = UserService.create(**serializer.validated_data)
return Response(
{"data": UserSerializer(user).data},
status=status.HTTP_201_CREATED,
headers={"Location": f"/api/v1/users/{user.id}"},
)func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
return
}
if err := req.Validate(); err != nil {
writeError(w, http.StatusUnprocessableEntity, "validation_error", err.Error())
return
}
user, err := h.service.Create(r.Context(), req)
if err != nil {
switch {
case errors.Is(err, domain.ErrEmailTaken):
writeError(w, http.StatusConflict, "email_taken", "Email already registered")
default:
writeError(w, http.StatusInternalServerError, "internal_error", "Internal error")
}
return
}
w.Header().Set("Location", fmt.Sprintf("/api/v1/users/%s", user.ID))
writeJSON(w, http.StatusCreated, map[string]any{"data": user})
}Before shipping a new endpoint:
- Resource URL follows naming conventions (plural, kebab-case, no verbs)
- Correct HTTP method used (GET for reads, POST for creates, etc.)
- Appropriate status codes returned (not 200 for everything)
- Input validated with schema (Zod, Pydantic, Bean Validation)
- Error responses follow standard format with codes and messages
- Pagination implemented for list endpoints (cursor or offset)
- Authentication required (or explicitly marked as public)
- Authorization checked (user can only access their own resources)
- Rate limiting configured
- Response does not leak internal details (stack traces, SQL errors)
- Consistent naming with existing endpoints (camelCase vs snake_case)
- Documented (OpenAPI/Swagger spec updated)
📐 Что это
Этот навык — свод правил и рекомендаций по проектированию REST API, готовых к промышленной эксплуатации. Он охватывает все ключевые аспекты: от именования ресурсов и выбора HTTP-методов до пагинации, фильтрации, обработки ошибок, версионирования и rate limiting. Навык пригодится backend-разработчикам, архитекторам и техлидам, которые проектируют новые эндпоинты, ревьюят существующие контракты или строят публичные/партнёрские API.
⚙️ Как работает
🏷️ Проектирование ресурсов
Структура URL
- Ресурсы — существительные во множественном числе, в
kebab-case. - Для связей используйте вложенные пути:
/api/v1/users/:id/orders. - Действия, не укладывающиеся в CRUD, оформляйте как
POSTс глаголом:/api/v1/orders/:id/cancel.
Правила именования
- ✅ Хорошо:
/api/v1/team-members,/api/v1/orders?status=active. - ❌ Плохо:
/api/v1/getUsers(глагол),/api/v1/user(единственное число),/api/v1/team_members(snake_case).
🔌 HTTP-методы и статус-коды
Семантика методов
GET— идемпотентный, безопасный, для чтения.POST— создание или запуск действия.PUT— полная замена ресурса.PATCH— частичное обновление.DELETE— удаление.
Коды ответов
- Успех:
200 OK,201 Created(с заголовкомLocation),204 No Content. - Ошибки клиента:
400 Bad Request,401 Unauthorized,403 Forbidden,404 Not Found,409 Conflict,422 Unprocessable Entity,429 Too Many Requests. - Ошибки сервера:
500 Internal Server Error(никогда не показывайте детали),502 Bad Gateway,503 Service Unavailable.
Типичные ошибки
- Возвращать
200для всего — плохая практика. Используйте семантические коды. - Не используйте
500для ошибок валидации — для этого есть400или422. - Для созданных ресурсов всегда отдавайте
201с заголовкомLocation.
📦 Формат ответов
Успешный ответ
{
"data": {
"id": "abc-123",
"email": "alice@example.com",
"name": "Alice",
"created_at": "2025-01-15T10:30:00Z"
}
}
Коллекция с пагинацией
{
"data": [...],
"meta": {
"total": 142,
"page": 1,
"per_page": 20,
"total_pages": 8
},
"links": {
"self": "/api/v1/users?page=1&per_page=20",
"next": "/api/v1/users?page=2&per_page=20",
"last": "/api/v1/users?page=8&per_page=20"
}
}
Ошибка с деталями
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Must be a valid email address",
"code": "invalid_format"
}
]
}
}
📄 Пагинация
Offset-based (простая)
- Параметры:
pageиper_page. - Плюсы: легко реализовать, можно прыгнуть на любую страницу.
- Минусы: медленная на больших смещениях, нестабильна при параллельных вставках.
Cursor-based (масштабируемая)
- Параметры:
cursor(непрозрачный токен) иlimit. - Плюсы: стабильная производительность, консистентность при вставках.
- Минусы: нельзя прыгнуть на произвольную страницу.
Когда что использовать
- Админки и маленькие наборы данных (<10K) — offset.
- Бесконечная лента, ленты, большие наборы — cursor.
- Публичные API — cursor по умолчанию, offset опционально.
- Поиск — offset (пользователи ожидают номера страниц).
🔍 Фильтрация, сортировка и поиск
Фильтрация
- Простое равенство:
?status=active. - Операторы сравнения:
?price[gte]=10&price[lte]=100. - Несколько значений:
?category=electronics,clothing. - Вложенные поля:
?customer.country=US.
Сортировка
- По одному полю:
?sort=-created_at(префикс-для убывания). - По нескольким:
?sort=-featured,price.
Поиск
- Полнотекстовый:
?q=wireless+headphones. - По конкретному полю:
?email=alice.
Sparse fieldsets
- Возвращайте только нужные поля:
?fields=id,name,email.
🔐 Аутентификация и авторизация
Токены
- Bearer token в заголовке
Authorization. - API-ключ для server-to-server:
X-API-Key.
Паттерны авторизации
- На уровне ресурса: проверяйте владельца (например,
order.userId === req.user.id). - На основе ролей: используйте middleware
requireRole("admin").
⏱️ Rate Limiting
Заголовки ответа
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset.- При превышении:
429 Too Many Requestsс заголовкомRetry-After.
Тиры
- Анонимные: 30/мин (по IP).
- Аутентифицированные: 100/мин (по пользователю).
- Премиум: 1000/мин (по API-ключу).
- Внутренние: 10000/мин (по сервису).
🔄 Версионирование
Рекомендуемый способ — версия в пути URL: /api/v1/users.
Стратегия
- Начинайте с
/api/v1/, не версионируйте без необходимости. - Поддерживайте не более двух активных версий.
- Для breaking changes создавайте новую версию.
- Не-breaking изменения (новые поля, опциональные параметры) не требуют новой версии.
- Используйте заголовок
Sunsetдля объявления о деприкации.
🎯 Когда использовать
- Проектирование нового API — с нуля закладывайте правильные паттерны.
- Ревью существующих контрактов — проверьте соответствие рекомендациям.
- Добавление пагинации, фильтрации или сортировки — выбирайте подходящий тип.
- Реализация обработки ошибок — стандартизируйте формат и коды.
- Планирование версионирования — определите стратегию заранее.
- Построение публичных или партнёрских API — требования к консистентности выше.
💡 Важно знать
- Чеклист перед релизом: проверьте URL, HTTP-метод, статус-код, валидацию, формат ошибок, пагинацию, аутентификацию, авторизацию, rate limiting, отсутствие утечек внутренних деталей, консистентность именования и документацию (OpenAPI).
- Выбор конверта: для публичных API используйте обёртку с полями
data,meta,links. Для внутренних — плоский ответ, различаемый по HTTP-статусу. - Идемпотентность PATCH: может быть достигнута при правильной реализации (например, с использованием версий ресурса).
- Безопасность: никогда не возвращайте стектрейсы, SQL-ошибки или внутренние идентификаторы в ответах API.
Может ли это помочь со стратегиями управления версиями API?
Да, включает подробные рекомендации по версионированию через URL path, заголовки и управлению сроками устаревания для обратно несовместимых изменений.
Поддерживает ли этот навык конкретные языки программирования?
Да, он предоставляет шаблоны реализации и примеры кода для популярных фреймворков на TypeScript (Django) и Go.
Что такое навык API Design для Claude Code?
Это специализированная возможность, которая помогает Claude генерировать, проверять и реализовывать архитектуры RESTful API в соответствии с лучшими отраслевыми практиками.
Как он обрабатывает предложения по пагинации?
Навык предоставляет логику как для простой offset-based пагинации для небольших наборов данных, так и для высокомасштабируемой cursor-based пагинации для производственных каналов большого объема.
Синхронизируйте навыки с Claude Cowork, Claude Code, Codex и другими.
Установка одной командой.
npx skillfish add affaan-m/everything-claude-code api-designИсточник: https://mcpmarket.com/tools/skills/api-design-patterns-best-practices-1
Комментарии
Комментариев пока нет. Будьте первым.