GraphQL - 验证
GraphQL - 验证
Section titled “GraphQL - 验证”通过 GraphQL API 添加或修改数据时,验证对于确保数据完整性至关重要。GraphQL 提供了多层验证:
- Schema 验证: 服务器在执行 之前 会根据 Schema (模式) 验证传入的 Queries (查询)。它检查语法是否正确、Types (类型) 和 Fields (字段) 是否存在、Argument (参数) 类型是否正确等。
- 类型系统验证: GraphQL 强大的类型系统本身就强制执行基本的验证。你可以将 Fields (字段) 和 Arguments (参数) 标记为 Non-Nullable (非空) 或确保它们符合特定的 Scalar Types (标量类型)(Int、String、Boolean、自定义 Scalar)。
- 自定义 Input 验证: 对于更复杂的业务规则(例如,字符串长度、电子邮件格式、值范围),你通常在 Resolver (解析器) 函数或专门的验证层中实现自定义验证逻辑。
使用 Non-Nullable 类型 (!)
Section titled “使用 Non-Nullable 类型 (!)”最简单的验证形式是确保一个 Field (字段) 或 Argument (参数) 永远不为 null。你可以通过在 Schema (模式) 中的类型定义后附加感叹号 (!) 来实现这一点。
type User { id: ID! # ID must always be present and non-null username: String! # Username must always be present and non-null email: String # Email is optional (can be null) age: Int # Age is optional (can be null)}
input CreateUserInput { username: String! # Required when creating a user email: String # Optional when creating a user password: String! # Required when creating a user}
type Mutation { createUser(input: CreateUserInput!): User! # Input object and returned User must be non-null}如果客户端尝试执行违反这些非空约束的 Query (查询) 或 Mutation (变更)(例如,省略一个必需的 Argument,或者 Resolver 为一个 Non-Nullable Field 返回 null),GraphQL 将自动生成错误,甚至在执行依赖 Fields 的 Resolver 逻辑之前。
在 Resolvers 中实现自定义 Input 验证
Section titled “在 Resolvers 中实现自定义 Input 验证”对于超出基本类型检查和 Nullability (非空) 的验证(如格式检查、长度限制、业务规则),逻辑位于你的 Resolver (解析器) 函数内部。最佳实践是在尝试数据库操作或调用其他服务 之前 执行验证。
一种常见的方法是在你的 Mutation (变更) Resolver 开头检查 Input Arguments (输入参数),并在验证失败时抛出错误。GraphQL 客户端库(如 Apollo Client)随后可以捕获这些错误并将其显示给用户。
示例:用户注册验证
Section titled “示例:用户注册验证”让我们为一个 signUp Mutation (变更) 添加自定义验证,包括电子邮件格式、密码长度和用户名长度。
步骤 1:服务器设置
Section titled “步骤 1:服务器设置”假设你已经有一个基本的 Apollo Server 设置,类似于“基本服务器示例”章节中的。确保你已安装依赖项,如 @apollo/server、graphql、express。
步骤 2:定义 Schema (schema.graphql 或在 server.js 中)
Section titled “步骤 2:定义 Schema (schema.graphql 或在 server.js 中)”定义必要的 Types (类型) 和 Mutation (变更):
#graphqltype Query { # Placeholder query to make schema valid _: Boolean}
type User { id: ID! username: String! email: String!}
# Input type bundles arguments for the mutationinput SignUpInput { username: String! email: String! password: String!}
type Mutation { signUp(input: SignUpInput!): User # Returns the created User on success}我们使用一个 input type (Input 类型) SignUpInput 来分组 signUp Mutation (变更) 的 Arguments (参数),这对于具有多个 Arguments 的 Mutation 来说是一种推荐做法。
步骤 3:创建包含验证的 Resolvers (resolvers.js)
Section titled “步骤 3:创建包含验证的 Resolvers (resolvers.js)”实现包含验证逻辑的 signUp Resolver (解析器)。我们将使用 Apollo Server 的 GraphQLError 来抛出对用户友好的错误。
import { GraphQLError } from 'graphql';
// 模拟数据库交互 (用实际的数据库逻辑替换)const mockUsers = [];let nextUserId = 1;
const resolvers = { Query: { _: () => true, // Resolver for the placeholder query }, Mutation: { signUp: (_, { input }) => { const { username, email, password } = input;
// --- 验证逻辑 --- const errors = {};
// 1. 用户名验证 if (username.length < 3) { errors.username = '用户名必须至少包含 3 个字符。'; } if (username.length > 20) { errors.username = '用户名不能超过 20 个字符。'; } // 检查用户名是否已存在 (简单模拟) if (mockUsers.some(user => user.username === username)) { errors.username = '用户名已被占用。'; }
// 2. 电子邮件验证 (基本格式检查) const emailRegex = /^(([^<>()[]\.,;:s@"]+(.[^<>()[]\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/; if (!emailRegex.test(String(email).toLowerCase())) { errors.email = '请提供一个有效的电子邮件地址。'; } // 检查电子邮件是否已存在 (简单模拟) if (mockUsers.some(user => user.email === email)) { errors.email = '电子邮件已被注册。'; }
// 3. 密码验证 if (password.length < 8) { errors.password = '密码必须至少包含 8 个字符。'; }
// --- 验证逻辑结束 ---
// 如果存在任何错误,抛出 GraphQLError if (Object.keys(errors).length > 0) { throw new GraphQLError('输入验证失败', { extensions: { code: 'BAD_USER_INPUT', // 在 extensions 中传递验证错误详情 validationErrors: errors }, }); }
// --- 如果验证通过,继续创建用户 --- console.log('验证通过。正在创建用户...'); const newUser = { id: String(nextUserId++), username: username, email: email, // 在实际应用中,这里应该对密码进行哈希处理! }; mockUsers.push(newUser); console.log('User created:', newUser); console.log('Current users:', mockUsers);
// 返回新创建的用户对象 (与 schema 中的 'User' 类型匹配) return newUser; }, },};
export default resolvers; // 或者 module.exports = resolvers;关键点:
- 从
graphql导入GraphQLError。 - 检查用户名长度、电子邮件格式和密码长度。
- 将所有验证错误收集到一个
errors对象中。 - 如果
errors不为空,则抛出GraphQLError。 - 使用
GraphQLError的extensions属性提供结构化的错误详情(例如code: 'BAD_USER_INPUT'和具体的validationErrors)。这有助于客户端应用显示有针对性的反馈。 - 仅在验证通过时才继续创建用户(此处为模拟)。
- 返回新创建的用户对象,符合 Schema 的返回类型。
步骤 4:运行和测试
Section titled “步骤 4:运行和测试”将 Schema (模式) 和 Resolvers (解析器) 集成到你的 Apollo Server 设置中并运行服务器 (npm start)。
打开 Apollo Sandbox (http://localhost:9000/graphql) 并尝试使用以下包含无效数据的 Mutation (变更):
mutation SignUpAttempt($inputData: SignUpInput!) { signUp(input: $inputData) { id username email }}在操作下方的“Variables”(变量) 面板中,输入无效内容:
{ "inputData": { "username": "Al", "email": "invalid-email", "password": "short" }}执行 Mutation (变更)。你应该收到包含验证详情的错误响应:
{ "errors": [ { "message": "输入验证失败", "locations": [ ... ], // 指示发生错误的位置 "path": [ "signUp" ], "extensions": { "code": "BAD_USER_INPUT", "validationErrors": { "username": "用户名必须至少包含 3 个字符。", "email": "请提供一个有效的电子邮件地址。", "password": "密码必须至少包含 8 个字符。" } } } ], "data": null // Data 为 null,因为 mutation 失败}现在尝试使用有效数据:
{ "inputData": { "username": "ValidUser", "email": "valid@example.com", "password": "longenoughpassword" }}这次,Mutation (变更) 应该成功并返回创建的用户数据:
{ "data": { "signUp": { "id": "1", "username": "ValidUser", "email": "valid@example.com" } }}- 使用 Input Types: 使用
inputtypes (Input 类型) 对 Mutation 的 Arguments (参数) 进行分组。 - 抛出
GraphQLError: 对验证失败使用标准的 GraphQL 错误。 - 在 Extensions 中提供详情: 使用
extensions字段为客户端提供关于 什么 失败的结构化信息。 - 尽早验证: 在产生副作用(如数据库写入)之前执行验证。
- 考虑使用验证库: 对于复杂的验证,考虑在你的 Resolvers (解析器) 中使用诸如
Yup、Joi或class-validator等库。 - 分离关注点: 在大型应用中,你可能将验证逻辑提取到单独的函数或模块中,以获得更好的组织性和可测试性。