API组织模式Skill api-organization

API组织模式技能提供了一套完整的TypeScript API架构解决方案,涵盖5文件系统结构、基于角色的访问控制、Supabase身份验证集成和类型安全的最佳实践。该技能适用于前端开发、后端开发和全栈开发场景,帮助开发者构建可维护、安全且类型安全的API层。关键词包括:API架构、TypeScript类型安全、Supabase身份验证、基于角色访问控制、5文件系统、API端点管理、前端开发、后端开发、全栈开发、代码组织模式。

架构设计 4 次安装 63 次浏览 更新于 3/2/2026

名称: api-organization 描述: 解释此代码库中标准化的API组织模式。在创建新的API端点、API客户端或修改现有API结构时使用。涵盖5文件系统(端点类型、端点、API客户端、管理员API客户端、受保护端点)、基于角色的访问模式(管理员与普通用户)以及API层的TypeScript类型安全。所有API代码都位于src/lib/api/中,遵循此确切模式。

API组织模式

此技能定义了在整个代码库中使用的标准化API组织模式。所有外部API集成都遵循相同的5文件结构,以确保一致性、类型安全性和可维护性。

何时使用此技能

在以下情况下使用此技能:

  • 创建新的API端点或集成
  • 添加新的API类别或领域
  • 实现基于角色的API访问(管理员与普通用户)
  • 修改现有API结构
  • 设置API调用的身份验证
  • 创建类型安全的API包装器

核心原则

  1. 单一事实来源 - 所有API URL在endpoints.ts中定义一次
  2. 类型安全 - 从参数到响应的完整TypeScript覆盖
  3. 基于角色的访问 - 普通端点和管理员端点的独立客户端
  4. 集中式身份验证 - 客户端自动处理身份验证
  5. DRY代码 - 可重用的客户端函数,无重复的端点定义

5文件系统

所有API代码都位于src/lib/api/中,包含以下确切文件:

src/lib/api/
├── endpoint-types.ts       # 所有端点的TypeScript类型
├── endpoints.ts            # 按领域组织的URL定义
├── api-client.ts           # 通用身份验证API客户端
├── admin-api-client.ts     # 仅限管理员的API客户端,带角色检查
└── protected-endpoints.ts  # 类型安全的包装器函数

文件用途

1. endpoint-types.ts

  • 定义API数据的所有TypeScript接口
  • 按领域组织类型(例如,受众、实例、成员资格)
  • 包括参数类型、响应类型和请求体DTO
  • 提供用于提取类型的实用程序类型

详细结构请参见references/endpoint-types-pattern.md

2. endpoints.ts

  • 在一个位置定义所有API端点URL
  • 按与类型匹配的功能/领域组织
  • 使用接受参数并返回URL的函数
  • 支持环境变量基础URL

详细结构请参见references/endpoints-pattern.md

3. api-client.ts

  • 具有自动身份验证的通用API客户端
  • 错误解析和处理
  • 支持GET、POST、PUT、DELETE方法
  • 仅限服务器端(‘use server’)

实现请参见references/api-client-pattern.md

4. admin-api-client.ts

  • 管理员特定的API客户端
  • 角色验证(管理员/超级管理员组)
  • 权限检查函数
  • 如果未授权则抛出AdminAuthError

实现请参见references/admin-api-client-pattern.md

5. protected-endpoints.ts

  • 结合URL + 类型 + 客户端的类型安全包装器函数
  • 组织以匹配endpoint-types结构
  • 提供干净的导入:import { api } from '@/lib/api/protected-endpoints'
  • 无’use server’指令(导出对象)

详细结构请参见references/protected-endpoints-pattern.md

身份验证系统

此应用程序使用Supabase Auth进行身份验证。所有API请求都需要通过Supabase访问令牌进行身份验证。

Supabase客户端结构

src/lib/supabase/
├── client.ts      # 浏览器端Supabase客户端
├── server.ts      # 服务器端Supabase客户端(RSC、服务器操作)
└── middleware.ts  # 用于身份验证Cookie刷新的中间件助手

身份验证流程

  1. 用户通过Supabase进行身份验证(电子邮件/密码、OAuth等)
  2. Supabase将会话存储在httpOnly Cookie中
  3. 中间件在每个请求上刷新会话
  4. 服务器组件/操作使用server.ts中的createClient()
  5. API客户端自动从会话中提取访问令牌

详细实现请参见references/supabase-auth-integration.md

基于角色的访问模式

普通用户端点

使用具有自动Supabase身份验证的api-client.ts函数:

import { apiGet, apiPost } from '@/lib/api/api-client';

// 自动从Supabase会话中提取访问令牌
const data = await apiGet<ResponseType>(url);

仅限管理员端点

使用具有角色验证的admin-api-client.ts函数:

import { adminApiRequest, checkAdminPermission } from '@/lib/api/admin-api-client';

// 首先验证管理员访问权限(从数据库检查用户角色)
await checkAdminPermission(); // 如果不是管理员则抛出错误

// 发出管理员API请求
const data = await adminApiRequest<ResponseType>(url, options);

管理员角色

管理员角色存储在users表中:

  • users.role = 'admin' - 标准管理员访问权限
  • users.role = 'member' - 普通用户访问权限

家庭中的第一个用户自动分配管理员角色。

添加新的API端点

将新的API集成到应用程序时,请遵循以下确切顺序:

步骤1:在endpoint-types.ts中定义类型

// 1. 定义响应类型
导出接口 ResourceItem {
  id: string;
  name: string;
  // ... 来自API响应的其他字段
}

// 2. 为变更定义请求DTO
导出接口 CreateResourceDto {
  name: string;
  // ... 创建所需的字段
}

// 3. 将参数类型添加到EndpointParams接口
导出接口 EndpointParams {
  // ... 现有领域

  resources: {
    list: void;              // 不需要参数
    get: { id: string };     // 需要ID
    create: void;            // 请求体中的内容,非参数
    update: { id: string };
    delete: { id: string };
  };
}

// 4. 将响应类型添加到EndpointResponses接口
导出接口 EndpointResponses {
  // ... 现有领域

  resources: {
    list: ResourceItem[];
    get: ResourceItem;
    create: ResourceItem;
    update: ResourceItem;
    delete: void;
  };
}

// 5. 将请求体类型添加到EndpointBodies接口(如果需要)
导出接口 EndpointBodies {
  // ... 现有领域

  resources: {
    create: CreateResourceDto;
    update: CreateResourceDto;
  };
}

步骤2:在endpoints.ts中定义URL

导出 const API_ENDPOINTS = {
  // ... 现有类别

  resources: {
    list: () => `${API_BASE}/api/resources`,
    get: (id: string) => `${API_BASE}/api/resources/${id}`,
    create: () => `${API_BASE}/api/resources`,
    update: (id: string) => `${API_BASE}/api/resources/${id}`,
    delete: (id: string) => `${API_BASE}/api/resources/${id}`,
  },
};

步骤3:在protected-endpoints.ts中添加包装器

导出 const api = {
  // ... 现有领域

  resources: {
    async list(): Promise<ResourceItem[]> {
      return apiGet<ResourceItem[]>(
        API_ENDPOINTS.resources.list()
      );
    },

    async get(id: string): Promise<ResourceItem> {
      return apiGet<ResourceItem>(
        API_ENDPOINTS.resources.get(id)
      );
    },

    async create(data: CreateResourceDto): Promise<ResourceItem> {
      return apiPost<ResourceItem, CreateResourceDto>(
        API_ENDPOINTS.resources.create(),
        data
      );
    },

    async update(id: string, data: CreateResourceDto): Promise<ResourceItem> {
      return apiPut<ResourceItem, CreateResourceDto>(
        API_ENDPOINTS.resources.update(id),
        data
      );
    },

    async delete(id: string): Promise<void> {
      return apiDelete<void>(
        API_ENDPOINTS.resources.delete(id)
      );
    },
  },
};

步骤4:在应用程序代码中使用

'use server';

import { api } from '@/lib/api/protected-endpoints';

// 在服务器组件或服务器操作中
const resources = await api.resources.list();
const resource = await api.resources.get(id);
const newResource = await api.resources.create({
  name: '新资源'
});

命名约定

端点操作

  • list / listAll - 获取多个项目
  • get - 获取单个项目
  • create - 创建新项目
  • update - 更新现有项目
  • delete - 删除项目

类型命名

  • 响应类型:描述实体的帕斯卡命名法(例如,AudienceListItem
  • DTO:带后缀的帕斯卡命名法(例如,CreateUserDtoUpdateSettingsDto
  • 接口匹配集合的复数形式,项目的单数形式

错误处理

所有API客户端自动处理错误:

try {
  const data = await api.myFeature.get(id);
} catch (error) {
  // 错误已解析并格式化
  console.error('API错误:', error.message);
}

常见错误类型:

  • AdminAuthError - 管理员权限被拒绝
  • AdminApiError - 管理员API请求失败
  • 来自api-client的通用错误,带有解析后的消息

身份验证流程

普通端点

  1. 客户端调用受保护的端点包装器(例如,api.resources.list()
  2. 包装器调用api-client中的apiGet/apiPost等
  3. api-client调用getAuthHeaders()
  4. getAuthHeaders使用Supabase服务器客户端获取会话:
    const supabase = await createClient();
    const { data: { session } } = await supabase.auth.getSession();
    const accessToken = session?.access_token;
    
  5. 访问令牌添加到Authorization标头
  6. 请求发送时自动进行身份验证

管理员端点

与上述流程相同,但带有额外的角色检查:

  1. 首先调用checkAdminPermission()
  2. checkAdminPermission查询users表以获取当前用户的角色
  3. 如果角色不是’admin’,则抛出AdminAuthError
  4. 然后继续进行正常的身份验证流程

最佳实践

  1. 切勿硬编码URL - 始终使用API_ENDPOINTS
  2. 始终先定义类型 - 类型驱动实现
  3. 每个端点一个包装器 - 保持protected-endpoints整洁
  4. 按领域分组 - 在所有5个文件中匹配结构
  5. 使用辅助函数 - apiGetapiPost等处理身份验证
  6. 尽早验证管理员访问权限 - 首先调用checkAdminPermission()
  7. 记录复杂端点 - 添加JSDoc注释
  8. 优雅地处理错误 - API客户端提供良好的错误消息

环境变量

Supabase身份验证所需

  • NEXT_PUBLIC_SUPABASE_URL - Supabase项目URL
  • NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY - Supabase匿名公钥

API客户端所需

  • INSTANCE_API_URLAPI_URL - 外部API端点的基础URL
  • NEXT_PUBLIC_SITE_URL - 站点URL用于重定向(可选,默认为localhost:3000)

数据库

  • Supabase连接通过Supabase客户端自动处理
  • 无需手动连接字符串

从其他模式迁移

如果将现有API代码迁移到此模式:

  1. 将所有端点URL提取到endpoints.ts
  2. endpoint-types.ts中为参数/响应创建类型
  3. 将直接fetch调用替换为apiGet/apiPost
  4. 将包装器添加到protected-endpoints.ts
  5. 更新导入以使用api对象

迁移示例:

// 之前(分散的fetch调用)
const response = await fetch(`${API_BASE}/api/resources/${id}`, {
  headers: { Authorization: `Bearer ${token}` }
});
const resource = await response.json();

// 之后(集中式模式)
import { api } from '@/lib/api/protected-endpoints';
const resource = await api.resources.get(id); // 身份验证自动,包含类型

参考

详细实现模式请参见参考文件:

  • references/supabase-auth-integration.md - Supabase身份验证设置和集成
  • references/endpoint-types-pattern.md - 类型定义结构
  • references/endpoints-pattern.md - URL组织模式
  • references/api-client-pattern.md - 带有Supabase的通用客户端实现
  • references/admin-api-client-pattern.md - 具有基于角色访问权限的管理员客户端
  • references/protected-endpoints-pattern.md - 包装器函数模式