api-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

API Design - REST & GraphQL

API设计 - REST与GraphQL

Design consistent, intuitive APIs that scale

设计一致、直观且可扩展的API

When to Use This Skill

何时使用此技能

Use this skill when:
  • Designing new API endpoints (REST or GraphQL)
  • Creating HTTP routes and handlers
  • Implementing pagination, filtering, or sorting
  • Versioning APIs for backward compatibility
  • Handling API errors and validation
  • Designing GraphQL schemas and resolvers
  • Optimizing API performance (N+1 queries, caching)
Don't use this skill for:
  • Frontend-only work with no API involvement
  • Direct database queries without an API layer
  • Internal function calls (not exposed as API)

在以下场景使用此技能:
  • 设计新的API端点(REST或GraphQL)
  • 创建HTTP路由与处理器
  • 实现分页、过滤或排序功能
  • 为向后兼容进行API版本控制
  • 处理API错误与验证
  • 设计GraphQL Schema与解析器
  • 优化API性能(N+1查询、缓存)
请勿在以下场景使用:
  • 仅涉及前端且无API交互的工作
  • 不通过API层直接进行数据库查询
  • 内部函数调用(未暴露为API)

Critical Patterns

核心设计模式

Pattern 1: HTTP Methods and Status Codes

模式1:HTTP方法与状态码

When: Building RESTful endpoints
Good:
typescript
// Correct HTTP methods and status codes
export async function GET(request: Request) {
  const users = await db.user.findMany();
  return NextResponse.json(users, { status: 200 });
}

export async function POST(request: Request) {
  const body = await request.json();

  if (!body.email || !body.name) {
    return NextResponse.json(
      { error: 'Email and name are required' },
      { status: 400 }  // Bad Request
    );
  }

  const user = await db.user.create({ data: body });
  return NextResponse.json(user, { status: 201 });  // Created
}

export async function DELETE(request: Request) {
  await db.user.delete({ where: { id: '123' } });
  return new Response(null, { status: 204 });  // No Content
}
Bad:
typescript
// ❌ Wrong: Using POST for everything
export async function POST(request: Request) {
  const { action, userId } = await request.json();

  if (action === 'get') {
    const user = await db.user.findUnique({ where: { id: userId } });
    return NextResponse.json(user);  // Should be GET
  }

  if (action === 'delete') {
    await db.user.delete({ where: { id: userId } });
    return NextResponse.json({ success: true });  // Should be DELETE
  }
}

// ❌ Wrong: Always returning 200
export async function GET(request: Request) {
  const user = await db.user.findUnique({ where: { id: '999' } });
  if (!user) {
    return NextResponse.json({ error: 'Not found' }, { status: 200 });  // Should be 404
  }
  return NextResponse.json(user);
}
Why: Correct HTTP methods and status codes make APIs predictable and RESTful. Clients can rely on standard semantics.
HTTP Methods Quick Reference:
GET     - Retrieve (Safe, Idempotent, Cacheable)
POST    - Create (Not safe, Not idempotent)
PUT     - Replace entire resource (Not safe, Idempotent)
PATCH   - Partial update (Not safe, Usually idempotent)
DELETE  - Remove (Not safe, Idempotent)
Status Codes Quick Reference:
2xx Success:
  200 OK            - Successful GET, PUT, PATCH, DELETE
  201 Created       - Successful POST (resource created)
  204 No Content    - Successful DELETE (no response body)

4xx Client Errors:
  400 Bad Request   - Invalid request data
  401 Unauthorized  - Authentication required
  403 Forbidden     - Authenticated but not authorized
  404 Not Found     - Resource doesn't exist
  409 Conflict      - Resource conflict (duplicate)
  422 Unprocessable - Validation errors

5xx Server Errors:
  500 Internal      - Unexpected server error
  503 Unavailable   - Server temporarily unavailable

适用场景:构建RESTful端点
正确示例
typescript
// Correct HTTP methods and status codes
export async function GET(request: Request) {
  const users = await db.user.findMany();
  return NextResponse.json(users, { status: 200 });
}

export async function POST(request: Request) {
  const body = await request.json();

  if (!body.email || !body.name) {
    return NextResponse.json(
      { error: 'Email and name are required' },
      { status: 400 }  // Bad Request
    );
  }

  const user = await db.user.create({ data: body });
  return NextResponse.json(user, { status: 201 });  // Created
}

export async function DELETE(request: Request) {
  await db.user.delete({ where: { id: '123' } });
  return new Response(null, { status: 204 });  // No Content
}
错误示例
typescript
// ❌ Wrong: Using POST for everything
export async function POST(request: Request) {
  const { action, userId } = await request.json();

  if (action === 'get') {
    const user = await db.user.findUnique({ where: { id: userId } });
    return NextResponse.json(user);  // Should be GET
  }

  if (action === 'delete') {
    await db.user.delete({ where: { id: userId } });
    return NextResponse.json({ success: true });  // Should be DELETE
  }
}

// ❌ Wrong: Always returning 200
export async function GET(request: Request) {
  const user = await db.user.findUnique({ where: { id: '999' } });
  if (!user) {
    return NextResponse.json({ error: 'Not found' }, { status: 200 });  // Should be 404
  }
  return NextResponse.json(user);
}
原因:正确的HTTP方法与状态码让API具备可预测性且符合REST规范。客户端可以依赖标准语义进行开发。
HTTP方法速查
GET     - 检索资源(安全、幂等、可缓存)
POST    - 创建资源(不安全、非幂等)
PUT     - 替换整个资源(不安全、幂等)
PATCH   - 部分更新资源(不安全、通常幂等)
DELETE  - 删除资源(不安全、幂等)
状态码速查
2xx 成功:
  200 OK            - GET、PUT、PATCH、DELETE请求成功
  201 Created       - POST请求成功(资源已创建)
  204 No Content    - DELETE请求成功(无响应体)

4xx 客户端错误:
  400 Bad Request   - 请求数据无效
  401 Unauthorized  - 需要身份验证
  403 Forbidden     - 已认证但无权限
  404 Not Found     - 资源不存在
  409 Conflict      - 资源冲突(如重复)
  422 Unprocessable - 验证错误

5xx 服务器错误:
  500 Internal      - 意外服务器错误
  503 Unavailable   - 服务器暂时不可用

Pattern 2: Resource Naming and Nesting

模式2:资源命名与嵌套

When: Designing API URL structure
Good:
typescript
// ✅ Use nouns, not verbs
GET /api/users
POST /api/users
GET /api/users/123
DELETE /api/users/123

// ✅ Plural nouns for collections
GET /api/products
GET /api/orders

// ✅ Nested resources (max 2 levels)
GET /api/users/123/posts
POST /api/users/123/posts
GET /api/users/123/posts/456
Bad:
typescript
// ❌ Wrong: Verbs in URLs
GET /api/getUsers
POST /api/createUser
DELETE /api/deleteUser/123

// ❌ Wrong: Singular for collections
GET /api/user
GET /api/product

// ❌ Wrong: Too deeply nested (3+ levels)
GET /api/users/123/posts/456/comments/789/likes
Why: Nouns represent resources, verbs are implied by HTTP methods. Avoid deep nesting to keep URLs simple and predictable.
Nested Resources Pattern:
GET /api/users/123/posts           - Get all posts by user 123
GET /api/users/123/posts/456       - Get specific post by user 123
POST /api/users/123/posts          - Create post for user 123

// ⚠️ For deep relationships, use query params instead:
GET /api/comments?postId=456
GET /api/likes?commentId=789

适用场景:设计API的URL结构
正确示例
typescript
// ✅ Use nouns, not verbs
GET /api/users
POST /api/users
GET /api/users/123
DELETE /api/users/123

// ✅ Plural nouns for collections
GET /api/products
GET /api/orders

// ✅ Nested resources (max 2 levels)
GET /api/users/123/posts
POST /api/users/123/posts
GET /api/users/123/posts/456
错误示例
typescript
// ❌ Wrong: Verbs in URLs
GET /api/getUsers
POST /api/createUser
DELETE /api/deleteUser/123

// ❌ Wrong: Singular for collections
GET /api/user
GET /api/product

// ❌ Wrong: Too deeply nested (3+ levels)
GET /api/users/123/posts/456/comments/789/likes
原因:名词代表资源,动词由HTTP方法隐含。避免过深嵌套以保持URL简洁且可预测。
资源嵌套模式
GET /api/users/123/posts           - 获取用户123的所有文章
GET /api/users/123/posts/456       - 获取用户123的指定文章
POST /api/users/123/posts          - 为用户123创建文章

// ⚠️ 对于深层关联,使用查询参数替代:
GET /api/comments?postId=456
GET /api/likes?commentId=789

Pattern 3: Pagination

模式3:分页机制

When: Returning large collections
Good - Cursor-based (recommended for large datasets):
typescript
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const cursor = searchParams.get('cursor');
  const limit = parseInt(searchParams.get('limit') || '20');

  const users = await db.user.findMany({
    take: limit + 1,
    ...(cursor && { cursor: { id: cursor }, skip: 1 }),
    orderBy: { id: 'asc' },
  });

  const hasMore = users.length > limit;
  const data = hasMore ? users.slice(0, -1) : users;

  return NextResponse.json({
    data,
    pagination: {
      nextCursor: hasMore ? data[data.length - 1].id : null,
      hasMore,
    },
  });
}
Good - Offset-based (simpler, for admin panels):
typescript
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const page = parseInt(searchParams.get('page') || '1');
  const limit = parseInt(searchParams.get('limit') || '20');

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

  return NextResponse.json({
    data: users,
    pagination: {
      page,
      limit,
      total,
      totalPages: Math.ceil(total / limit),
    },
  });
}
Bad:
typescript
// ❌ No pagination - returns all records
export async function GET() {
  const users = await db.user.findMany();  // Could be millions!
  return NextResponse.json(users);
}
Why: Pagination prevents performance issues and timeouts. Cursor-based is more efficient for large datasets, offset-based is simpler for small datasets.

适用场景:返回大型数据集时
正确示例 - 基于游标(推荐用于大型数据集):
typescript
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const cursor = searchParams.get('cursor');
  const limit = parseInt(searchParams.get('limit') || '20');

  const users = await db.user.findMany({
    take: limit + 1,
    ...(cursor && { cursor: { id: cursor }, skip: 1 }),
    orderBy: { id: 'asc' },
  });

  const hasMore = users.length > limit;
  const data = hasMore ? users.slice(0, -1) : users;

  return NextResponse.json({
    data,
    pagination: {
      nextCursor: hasMore ? data[data.length - 1].id : null,
      hasMore,
    },
  });
}
正确示例 - 基于偏移量(实现简单,适用于管理面板):
typescript
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const page = parseInt(searchParams.get('page') || '1');
  const limit = parseInt(searchParams.get('limit') || '20');

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

  return NextResponse.json({
    data: users,
    pagination: {
      page,
      limit,
      total,
      totalPages: Math.ceil(total / limit),
    },
  });
}
错误示例
typescript
// ❌ No pagination - returns all records
export async function GET() {
  const users = await db.user.findMany();  // Could be millions!
  return NextResponse.json(users);
}
原因:分页可避免性能问题与超时。基于游标分页在大型数据集上更高效,基于偏移量分页实现更简单,适用于小型数据集。

Pattern 4: API Versioning

模式4:API版本控制

When: Making breaking changes to existing APIs
Good - URL versioning (most explicit):
GET /api/v1/users
GET /api/v2/users
typescript
// app/api/v1/users/route.ts
export async function GET() {
  const users = await db.user.findMany();
  return NextResponse.json(users);  // Old format
}

// app/api/v2/users/route.ts
export async function GET() {
  const users = await db.user.findMany({
    include: { profile: true },  // New: Include related data
  });
  return NextResponse.json(users);
}
Breaking vs Non-Breaking Changes:
typescript
// ✅ Non-breaking (no version bump needed):
// - Add new endpoint
// - Add optional field to request
// - Add new field to response

// ❌ Breaking (requires new version):
// - Remove endpoint
// - Remove field from response
// - Rename field
// - Change field type
// - Make optional field required
Why: Versioning allows backward compatibility while evolving the API. Existing clients continue working while new clients use improved versions.

适用场景:对现有API进行破坏性变更时
正确示例 - URL版本控制(最直观):
GET /api/v1/users
GET /api/v2/users
typescript
// app/api/v1/users/route.ts
export async function GET() {
  const users = await db.user.findMany();
  return NextResponse.json(users);  // Old format
}

// app/api/v2/users/route.ts
export async function GET() {
  const users = await db.user.findMany({
    include: { profile: true },  // New: Include related data
  });
  return NextResponse.json(users);
}
破坏性变更 vs 非破坏性变更
typescript
// ✅ Non-breaking (no version bump needed):
// - Add new endpoint
// - Add optional field to request
// - Add new field to response

// ❌ Breaking (requires new version):
// - Remove endpoint
// - Remove field from response
// - Rename field
// - Change field type
// - Make optional field required
原因:版本控制允许在演进API的同时保持向后兼容性。现有客户端可继续正常工作,新客户端则可使用改进后的版本。

Pattern 5: Consistent Error Handling

模式5:一致性错误处理

When: Handling errors and validation
Good:
typescript
interface ApiError {
  code: string;
  message: string;
  details?: Array<{ field: string; message: string }>;
}

function errorResponse(status: number, error: ApiError) {
  return NextResponse.json({ error }, { status });
}

export async function POST(request: Request) {
  try {
    const body = await request.json();

    const errors = validateUser(body);
    if (errors.length > 0) {
      return errorResponse(400, {
        code: 'VALIDATION_ERROR',
        message: 'Invalid input data',
        details: errors,
      });
    }

    const user = await db.user.create({ data: body });
    return NextResponse.json(user, { status: 201 });
  } catch (error) {
    if (error.code === 'P2002') {
      return errorResponse(409, {
        code: 'DUPLICATE_EMAIL',
        message: 'Email already exists',
      });
    }

    return errorResponse(500, {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred',
    });
  }
}
Bad:
typescript
// ❌ Inconsistent error formats
export async function POST(request: Request) {
  try {
    const body = await request.json();

    if (!body.email) {
      return NextResponse.json('Email required');  // Plain string
    }

    const user = await db.user.create({ data: body });
    return NextResponse.json(user);
  } catch (error) {
    return NextResponse.json({
      message: error.message,  // Inconsistent format
      stack: error.stack,      // Leaks implementation details
    });
  }
}
Why: Consistent error format makes client error handling predictable. Never expose stack traces or internal details in production.

适用场景:处理错误与验证时
正确示例
typescript
interface ApiError {
  code: string;
  message: string;
  details?: Array<{ field: string; message: string }>;
}

function errorResponse(status: number, error: ApiError) {
  return NextResponse.json({ error }, { status });
}

export async function POST(request: Request) {
  try {
    const body = await request.json();

    const errors = validateUser(body);
    if (errors.length > 0) {
      return errorResponse(400, {
        code: 'VALIDATION_ERROR',
        message: 'Invalid input data',
        details: errors,
      });
    }

    const user = await db.user.create({ data: body });
    return NextResponse.json(user, { status: 201 });
  } catch (error) {
    if (error.code === 'P2002') {
      return errorResponse(409, {
        code: 'DUPLICATE_EMAIL',
        message: 'Email already exists',
      });
    }

    return errorResponse(500, {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred',
    });
  }
}
错误示例
typescript
// ❌ Inconsistent error formats
export async function POST(request: Request) {
  try {
    const body = await request.json();

    if (!body.email) {
      return NextResponse.json('Email required');  // Plain string
    }

    const user = await db.user.create({ data: body });
    return NextResponse.json(user);
  } catch (error) {
    return NextResponse.json({
      message: error.message,  // Inconsistent format
      stack: error.stack,      // Leaks implementation details
    });
  }
}
原因:一致的错误格式让客户端的错误处理更具可预测性。生产环境中绝不要暴露堆栈跟踪或内部实现细节。

GraphQL Critical Patterns

GraphQL核心设计模式

Pattern 1: Schema Design with Types and Relationships

模式1:包含类型与关联关系的Schema设计

Good:
graphql
type User {
  id: ID!
  name: String!
  email: String!
  role: UserRole!
  posts: [Post!]!
  createdAt: DateTime!
}

enum UserRole {
  ADMIN
  USER
  GUEST
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  published: Boolean!
}

type Query {
  user(id: ID!): User
  users(limit: Int, cursor: String): UserConnection!
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
}

input CreateUserInput {
  name: String!
  email: String!
  role: UserRole
}
Why: Clear types, non-null fields (the
!
suffix), and input types make the API self-documenting and type-safe.

正确示例
graphql
type User {
  id: ID!
  name: String!
  email: String!
  role: UserRole!
  posts: [Post!]!
  createdAt: DateTime!
}

enum UserRole {
  ADMIN
  USER
  GUEST
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  published: Boolean!
}

type Query {
  user(id: ID!): User
  users(limit: Int, cursor: String): UserConnection!
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
}

input CreateUserInput {
  name: String!
  email: String!
  role: UserRole
}
原因:清晰的类型定义、非空字段(
!
后缀)以及输入类型让API具备自文档性且类型安全。

Pattern 2: Solving N+1 Queries with DataLoader

模式2:使用DataLoader解决N+1查询问题

Problem:
typescript
// ❌ N+1 queries: 1 for users + N for posts
const resolvers = {
  User: {
    posts: async (parent, _args, context) => {
      return context.db.post.findMany({
        where: { authorId: parent.id },
      });
    },
  },
};
// Querying 100 users = 101 database queries!
Solution:
typescript
// ✅ DataLoader batches queries
import DataLoader from 'dataloader';

const postLoader = new DataLoader(async (userIds: readonly string[]) => {
  const posts = await db.post.findMany({
    where: { authorId: { in: [...userIds] } },
  });

  const postsByUserId = userIds.map(userId =>
    posts.filter(post => post.authorId === userId)
  );

  return postsByUserId;
});

const resolvers = {
  User: {
    posts: (parent, _args, context) => {
      return context.loaders.post.load(parent.id);
    },
  },
};
// Querying 100 users = 2 queries (users + batched posts)!
Why: DataLoader batches and caches database queries, solving the N+1 problem and dramatically improving performance.

问题
typescript
// ❌ N+1 queries: 1 for users + N for posts
const resolvers = {
  User: {
    posts: async (parent, _args, context) => {
      return context.db.post.findMany({
        where: { authorId: parent.id },
      });
    },
  },
};
// Querying 100 users = 101 database queries!
解决方案
typescript
// ✅ DataLoader batches queries
import DataLoader from 'dataloader';

const postLoader = new DataLoader(async (userIds: readonly string[]) => {
  const posts = await db.post.findMany({
    where: { authorId: { in: [...userIds] } },
  });

  const postsByUserId = userIds.map(userId =>
    posts.filter(post => post.authorId === userId)
  );

  return postsByUserId;
});

const resolvers = {
  User: {
    posts: (parent, _args, context) => {
      return context.loaders.post.load(parent.id);
    },
  },
};
// Querying 100 users = 2 queries (users + batched posts)!
原因:DataLoader可批量处理并缓存数据库查询,解决N+1查询问题并显著提升性能。

Anti-Patterns

反模式

❌ Anti-Pattern 1: Exposing Database Structure Directly

❌ 反模式1:直接暴露数据库结构

Don't do this:
typescript
// ❌ API mirrors database exactly
GET /api/users
Response: {
  id: 123,
  password_hash: "bcrypt...",  // Exposing sensitive data!
  created_at: "2024-01-15",
  internal_notes: "VIP customer"
}
Do this instead:
typescript
// ✅ API has its own contract
GET /api/users/123
Response: {
  id: 123,
  name: "Alice",
  email: "alice@example.com",
  role: "admin",
  joinedAt: "2024-01-15T10:00:00Z"
}

// Server-side: Transform before sending
export async function GET(request: Request, { params }) {
  const user = await db.user.findUnique({ where: { id: params.id } });

  return NextResponse.json({
    id: user.id,
    name: user.name,
    email: user.email,
    role: user.role,
    joinedAt: user.createdAt,
  });
}

请勿这样做
typescript
// ❌ API mirrors database exactly
GET /api/users
Response: {
  id: 123,
  password_hash: "bcrypt...",  // Exposing sensitive data!
  created_at: "2024-01-15",
  internal_notes: "VIP customer"
}
正确做法
typescript
// ✅ API has its own contract
GET /api/users/123
Response: {
  id: 123,
  name: "Alice",
  email: "alice@example.com",
  role: "admin",
  joinedAt: "2024-01-15T10:00:00Z"
}

// Server-side: Transform before sending
export async function GET(request: Request, { params }) {
  const user = await db.user.findUnique({ where: { id: params.id } });

  return NextResponse.json({
    id: user.id,
    name: user.name,
    email: user.email,
    role: user.role,
    joinedAt: user.createdAt,
  });
}

❌ Anti-Pattern 2: No Input Validation

❌ 反模式2:无输入验证

Don't do this:
typescript
// ❌ Trusting all input
export async function POST(request: Request) {
  const body = await request.json();
  const user = await db.user.create({ data: body });
  return NextResponse.json(user);
}
Do this instead:
typescript
// ✅ Validate all input
import { z } from 'zod';

const userSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  age: z.number().int().min(18).max(120),
});

export async function POST(request: Request) {
  const body = await request.json();

  const result = userSchema.safeParse(body);
  if (!result.success) {
    return NextResponse.json(
      { error: result.error.format() },
      { status: 400 }
    );
  }

  const user = await db.user.create({ data: result.data });
  return NextResponse.json(user, { status: 201 });
}

请勿这样做
typescript
// ❌ Trusting all input
export async function POST(request: Request) {
  const body = await request.json();
  const user = await db.user.create({ data: body });
  return NextResponse.json(user);
}
正确做法
typescript
// ✅ Validate all input
import { z } from 'zod';

const userSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  age: z.number().int().min(18).max(120),
});

export async function POST(request: Request) {
  const body = await request.json();

  const result = userSchema.safeParse(body);
  if (!result.success) {
    return NextResponse.json(
      { error: result.error.format() },
      { status: 400 }
    );
  }

  const user = await db.user.create({ data: result.data });
  return NextResponse.json(user, { status: 201 });
}

❌ Anti-Pattern 3: Ignoring Authentication

❌ 反模式3:忽略身份验证

Don't do this:
typescript
// ❌ No auth checks
export async function DELETE(request: Request) {
  const { id } = await request.json();
  await db.user.delete({ where: { id } });
  return NextResponse.json({ success: true });
}
Do this instead:
typescript
// ✅ Check authentication and authorization
export async function DELETE(request: Request) {
  const session = await getSession(request);

  if (!session) {
    return new Response('Unauthorized', { status: 401 });
  }

  if (session.role !== 'ADMIN') {
    return new Response('Forbidden', { status: 403 });
  }

  const { id } = await request.json();
  await db.user.delete({ where: { id } });
  return new Response(null, { status: 204 });
}

请勿这样做
typescript
// ❌ No auth checks
export async function DELETE(request: Request) {
  const { id } = await request.json();
  await db.user.delete({ where: { id } });
  return NextResponse.json({ success: true });
}
正确做法
typescript
// ✅ Check authentication and authorization
export async function DELETE(request: Request) {
  const session = await getSession(request);

  if (!session) {
    return new Response('Unauthorized', { status: 401 });
  }

  if (session.role !== 'ADMIN') {
    return new Response('Forbidden', { status: 403 });
  }

  const { id } = await request.json();
  await db.user.delete({ where: { id } });
  return new Response(null, { status: 204 });
}

Code Examples

代码示例

Example 1: RESTful CRUD Endpoint

示例1:RESTful CRUD端点

typescript
// app/api/users/[id]/route.ts
export async function GET(request: Request, { params }: { params: { id: string } }) {
  const user = await db.user.findUnique({ where: { id: params.id } });

  if (!user) {
    return new Response('User not found', { status: 404 });
  }

  return NextResponse.json(user);
}

export async function PATCH(request: Request, { params }: { params: { id: string } }) {
  const body = await request.json();
  const user = await db.user.update({
    where: { id: params.id },
    data: body,
  });

  return NextResponse.json(user);
}
typescript
// app/api/users/[id]/route.ts
export async function GET(request: Request, { params }: { params: { id: string } }) {
  const user = await db.user.findUnique({ where: { id: params.id } });

  if (!user) {
    return new Response('User not found', { status: 404 });
  }

  return NextResponse.json(user);
}

export async function PATCH(request: Request, { params }: { params: { id: string } }) {
  const body = await request.json();
  const user = await db.user.update({
    where: { id: params.id },
    data: body,
  });

  return NextResponse.json(user);
}

Example 2: GraphQL Resolver with DataLoader

示例2:带DataLoader的GraphQL解析器

typescript
const resolvers = {
  Query: {
    users: async (_parent, { limit = 20, cursor }, context) => {
      const users = await context.db.user.findMany({
        take: limit + 1,
        ...(cursor && { cursor: { id: cursor }, skip: 1 }),
      });

      const hasMore = users.length > limit;
      const data = hasMore ? users.slice(0, -1) : users;

      return {
        edges: data.map(user => ({ node: user, cursor: user.id })),
        pageInfo: {
          hasNextPage: hasMore,
          endCursor: hasMore ? data[data.length - 1].id : null,
        },
      };
    },
  },
  User: {
    posts: (parent, _args, context) => {
      return context.loaders.posts.load(parent.id);
    },
  },
};
For comprehensive examples and detailed implementations, see the references/ folder.

typescript
const resolvers = {
  Query: {
    users: async (_parent, { limit = 20, cursor }, context) => {
      const users = await context.db.user.findMany({
        take: limit + 1,
        ...(cursor && { cursor: { id: cursor }, skip: 1 }),
      });

      const hasMore = users.length > limit;
      const data = hasMore ? users.slice(0, -1) : users;

      return {
        edges: data.map(user => ({ node: user, cursor: user.id })),
        pageInfo: {
          hasNextPage: hasMore,
          endCursor: hasMore ? data[data.length - 1].id : null,
        },
      };
    },
  },
  User: {
    posts: (parent, _args, context) => {
      return context.loaders.posts.load(parent.id);
    },
  },
};
如需完整示例与详细实现,请查看references/文件夹。

Quick Reference

快速检查清单

REST Checklist

REST检查清单

  • Use appropriate HTTP methods (GET, POST, PUT, PATCH, DELETE)
  • Return correct status codes (2xx, 4xx, 5xx)
  • Use nouns for resources, not verbs
  • Implement pagination for collections
  • Version API for breaking changes
  • Validate all input
  • Return consistent error format
  • Add authentication/authorization checks
  • 使用合适的HTTP方法(GET、POST、PUT、PATCH、DELETE)
  • 返回正确的状态码(2xx、4xx、5xx)
  • 资源使用名词而非动词
  • 为数据集实现分页
  • 对破坏性变更进行API版本控制
  • 验证所有输入
  • 返回一致的错误格式
  • 添加身份验证/授权检查

GraphQL Checklist

GraphQL检查清单

  • Design clear schema with types and relationships
  • Use DataLoader to prevent N+1 queries
  • Validate inputs with Zod or similar
  • Return meaningful error codes in extensions
  • Implement authentication via context
  • Use connection pattern for pagination
  • Keep mutations simple and focused

  • 设计清晰的Schema,包含类型与关联关系
  • 使用DataLoader防止N+1查询
  • 使用Zod或类似工具验证输入
  • 在扩展字段中返回有意义的错误码
  • 通过上下文实现身份验证
  • 使用连接模式进行分页
  • 保持变更操作简单且聚焦

Progressive Disclosure

进阶内容

For detailed implementations, see:
  • REST Patterns - Pagination, filtering, versioning, rate limiting, HATEOAS
  • GraphQL Design - Resolvers, DataLoader, subscriptions, directives, input validation

如需详细实现指南,请查看:
  • REST模式参考 - 分页、过滤、版本控制、速率限制、HATEOAS
  • GraphQL设计指南 - 解析器、DataLoader、订阅、指令、输入验证

References

参考资料


Maintained by dsmj-ai-toolkit

由dsmj-ai-toolkit维护