Agent skill

api

RESTful API design patterns and best practices. Use when creating endpoints, designing APIs, or implementing routes.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/api

SKILL.md

API 开发技能

本技能提供 RESTful API 开发的最佳实践和模式。

触发条件

  • 创建 API 端点
  • 设计 REST API
  • 实现后端路由
  • 处理请求/响应
  • API 版本控制

RESTful 设计原则

URL 设计

# 资源命名 - 使用名词复数
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       # 删除用户

# 嵌套资源
GET    /api/v1/users/:id/orders           # 用户的订单
GET    /api/v1/users/:id/orders/:orderId  # 用户的特定订单

# 动作资源
POST   /api/v1/users/:id/activate         # 激活用户
POST   /api/v1/orders/:id/cancel          # 取消订单

HTTP 方法语义

方法 语义 幂等 安全
GET 读取资源
POST 创建资源
PUT 完整更新
PATCH 部分更新
DELETE 删除资源

统一响应格式

成功响应

typescript
// 单个资源
{
  "success": true,
  "data": {
    "id": "123",
    "name": "张三",
    "email": "zhangsan@example.com"
  }
}

// 列表资源
{
  "success": true,
  "data": [
    { "id": "1", "name": "用户1" },
    { "id": "2", "name": "用户2" }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 20,
    "total": 100,
    "totalPages": 5
  }
}

// 创建成功
{
  "success": true,
  "data": { "id": "123", ... },
  "message": "创建成功"
}

错误响应

typescript
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数无效",
    "details": [
      { "field": "email", "message": "邮箱格式不正确" },
      { "field": "age", "message": "年龄必须大于 0" }
    ]
  }
}

API 路由实现

Next.js App Router

typescript
// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";

const CreateUserSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).optional(),
});

export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const page = parseInt(searchParams.get("page") || "1");
    const pageSize = parseInt(searchParams.get("pageSize") || "20");

    const [users, total] = await Promise.all([
      db.users.findMany({
        skip: (page - 1) * pageSize,
        take: pageSize,
      }),
      db.users.count(),
    ]);

    return NextResponse.json({
      success: true,
      data: users,
      pagination: {
        page,
        pageSize,
        total,
        totalPages: Math.ceil(total / pageSize),
      },
    });
  } catch (error) {
    return NextResponse.json(
      { success: false, error: { message: "获取用户失败" } },
      { status: 500 },
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const validated = CreateUserSchema.parse(body);

    const user = await db.users.create({ data: validated });

    return NextResponse.json(
      { success: true, data: user, message: "创建成功" },
      { status: 201 },
    );
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        {
          success: false,
          error: {
            code: "VALIDATION_ERROR",
            message: "请求参数无效",
            details: error.errors,
          },
        },
        { status: 400 },
      );
    }
    throw error;
  }
}

FastAPI

python
# routers/users.py
from fastapi import APIRouter, Query, HTTPException
from pydantic import BaseModel, EmailStr

router = APIRouter(prefix="/api/v1/users", tags=["users"])

class UserCreate(BaseModel):
    name: str
    email: EmailStr
    age: int | None = None

class UserResponse(BaseModel):
    id: str
    name: str
    email: str

@router.get("")
async def list_users(
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100)
):
    skip = (page - 1) * page_size
    users = await db.users.find().skip(skip).limit(page_size).to_list()
    total = await db.users.count_documents({})

    return {
        "success": True,
        "data": users,
        "pagination": {
            "page": page,
            "pageSize": page_size,
            "total": total,
            "totalPages": (total + page_size - 1) // page_size
        }
    }

@router.post("", status_code=201)
async def create_user(user: UserCreate):
    result = await db.users.insert_one(user.dict())
    created = await db.users.find_one({"_id": result.inserted_id})

    return {
        "success": True,
        "data": created,
        "message": "创建成功"
    }

@router.get("/{user_id}")
async def get_user(user_id: str):
    user = await db.users.find_one({"_id": user_id})
    if not user:
        raise HTTPException(status_code=404, detail="用户未找到")

    return {"success": True, "data": user}

Express.js

typescript
// routes/users.ts
import { Router } from "express";
import { body, query, validationResult } from "express-validator";

const router = Router();

router.get(
  "/",
  [
    query("page").optional().isInt({ min: 1 }),
    query("pageSize").optional().isInt({ min: 1, max: 100 }),
  ],
  async (req, res, next) => {
    try {
      const errors = validationResult(req);
      if (!errors.isEmpty()) {
        return res.status(400).json({
          success: false,
          error: { code: "VALIDATION_ERROR", details: errors.array() },
        });
      }

      const page = parseInt(req.query.page as string) || 1;
      const pageSize = parseInt(req.query.pageSize as string) || 20;

      const [users, total] = await Promise.all([
        User.find()
          .skip((page - 1) * pageSize)
          .limit(pageSize),
        User.countDocuments(),
      ]);

      res.json({
        success: true,
        data: users,
        pagination: {
          page,
          pageSize,
          total,
          totalPages: Math.ceil(total / pageSize),
        },
      });
    } catch (error) {
      next(error);
    }
  },
);

router.post(
  "/",
  [body("name").notEmpty().isLength({ max: 100 }), body("email").isEmail()],
  async (req, res, next) => {
    try {
      const errors = validationResult(req);
      if (!errors.isEmpty()) {
        return res.status(400).json({
          success: false,
          error: { code: "VALIDATION_ERROR", details: errors.array() },
        });
      }

      const user = await User.create(req.body);
      res.status(201).json({ success: true, data: user, message: "创建成功" });
    } catch (error) {
      next(error);
    }
  },
);

export default router;

查询参数处理

分页

typescript
interface PaginationParams {
  page?: number;
  pageSize?: number;
  cursor?: string; // 游标分页
}

// 偏移分页
const { page = 1, pageSize = 20 } = query;
const skip = (page - 1) * pageSize;

// 游标分页(大数据量推荐)
const { cursor, pageSize = 20 } = query;
const where = cursor ? { id: { gt: cursor } } : {};

排序

typescript
// ?sort=createdAt:desc,name:asc
const sortParam = query.sort as string;
const orderBy = sortParam?.split(",").map((s) => {
  const [field, order] = s.split(":");
  return { [field]: order || "asc" };
}) || [{ createdAt: "desc" }];

筛选

typescript
// ?status=active&role=admin
const { status, role } = query;
const where = {
  ...(status && { status }),
  ...(role && { role }),
};

搜索

typescript
// ?q=keyword
const { q } = query;
const where = q
  ? {
      OR: [
        { name: { contains: q, mode: "insensitive" } },
        { email: { contains: q, mode: "insensitive" } },
      ],
    }
  : {};

API 版本控制

URL 路径版本

/api/v1/users
/api/v2/users

请求头版本

Accept: application/vnd.api+json; version=1

版本控制实现

typescript
// app/api/v1/users/route.ts
export async function GET() { ... }

// app/api/v2/users/route.ts
export async function GET() { ... }

认证与授权

JWT 认证中间件

typescript
// middlewares/auth.ts
import jwt from "jsonwebtoken";

export async function authMiddleware(request: NextRequest) {
  const token = request.headers.get("Authorization")?.replace("Bearer ", "");

  if (!token) {
    return NextResponse.json(
      {
        success: false,
        error: { code: "UNAUTHORIZED", message: "未提供认证信息" },
      },
      { status: 401 },
    );
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!);
    request.user = decoded;
  } catch (error) {
    return NextResponse.json(
      {
        success: false,
        error: { code: "UNAUTHORIZED", message: "Token 无效" },
      },
      { status: 401 },
    );
  }
}

权限检查

typescript
export function requireRole(...roles: string[]) {
  return (request: NextRequest) => {
    if (!roles.includes(request.user.role)) {
      return NextResponse.json(
        { success: false, error: { code: "FORBIDDEN", message: "权限不足" } },
        { status: 403 },
      );
    }
  };
}

// 使用
export async function DELETE(request: NextRequest) {
  const authError = await authMiddleware(request);
  if (authError) return authError;

  const roleError = requireRole("admin")(request);
  if (roleError) return roleError;

  // 执行删除
}

HTTP 状态码

状态码 含义 使用场景
200 OK 成功获取/更新
201 Created 成功创建
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 服务器错误

最佳实践

  1. 使用名词复数 - /users 而非 /user
  2. HTTP 方法语义 - GET 读取,POST 创建,PUT/PATCH 更新
  3. 统一响应格式 - success、data、error、pagination
  4. 输入验证 - 使用 Zod/Pydantic 验证
  5. 错误处理 - 返回有意义的错误码和消息
  6. 版本控制 - /api/v1/
  7. 分页 - 列表接口必须分页
  8. HATEOAS - 可选,返回相关链接
  9. 文档 - OpenAPI/Swagger 文档
  10. 幂等性 - PUT、DELETE 保持幂等

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results