Skip to content

GraphQL - Schema

GraphQL Schema(模式)是任何 GraphQL API 的绝对核心。它作为客户端和服务器之间的强契约,精确定义了客户端可以请求什么数据、可以执行什么变更操作(Mutation),以及相关数据的结构。模式是使用 GraphQL Schema Definition Language (SDL) 定义的。

可以将模式视为 API 的蓝图或文档,但与传统文档不同的是,它是机器可读的,并由 GraphQL 服务器强制执行。

  • 类型(Types): 定义 API 中存在的不同种类的对象(Object)、标量(Scalar)、枚举(Enum)、接口(Interface)和联合(Union)。
  • 字段(Fields): 类型中的属性,用于存放数据。每个字段都有特定的类型。
  • 根操作类型(Root Operation Types): 定义操作入口点的特殊对象类型:
    • Query:定义可用的读取操作。
    • Mutation:定义可用的写入操作(创建、更新、删除)。
    • Subscription:定义可用的实时数据更新操作。
  • 模式定义(可选但推荐): 明确声明哪些类型作为根操作类型(query、mutation、subscription)。

SDL 提供了一种简洁、人类可读的语法来定义模式。它是语言无关的,这意味着用 SDL 定义的模式可以由任何语言(如 JavaScript、Python、Java、C# 等)编写的 GraphQL 服务器实现。

常见的 SDL 元素:

  • type: 定义一个对象类型(Object type)。
  • scalar: 定义一个自定义的原始类型(超出内置的 Int、Float、String、Boolean、ID)。
  • enum: 定义一组预定义字符串值。
  • input: 定义一个输入对象类型(Input Object type),主要用于 mutation 参数。
  • interface: 定义一个契约,多个对象类型可以实现该契约。
  • union: 定义一个类型,它可以是多个不同对象类型中的一个。
  • !(感叹号): 表示字段或参数是 Non-Nullable(非空)的。
  • [](方括号): 表示字段或参数是一个 List(列表/数组)。
  • # 或 """: 用于注释或描述(描述会成为可内省模式的一部分)。
schema.graphql
""" 表示一个教育机构 """
type College {
id: ID! # 学院的唯一标识符
name: String!
location: String
rating: Float
students: [Student] # 与该学院关联的学生列表
}
""" 表示注册入学的学生 """
type Student {
id: ID!
firstName: String!
lastName: String
email: String!
collegeId: ID! # 外键引用
college: College # 关联的 College 对象
}
# 定义读取数据的入口点
type Query {
""" 获取所有学生的列表 """
students: [Student]
""" 根据学生的唯一 ID 获取单个学生 """
studentById(id: ID!): Student
""" 根据学院的唯一 ID 获取单个学院 """
collegeById(id: ID!): College
}
# 用于创建新学生的输入类型
input CreateStudentInput {
firstName: String!
lastName: String
email: String!
collegeId: ID!
}
# 定义修改数据的入口点
type Mutation {
""" 创建一个新学生 """
createStudent(input: CreateStudentInput!): Student # 返回新创建的学生
}
# 明确定义根操作类型(良好实践)
schema {
query: Query
mutation: Mutation
# subscription: Subscription # 如果有订阅则添加
}

通常,你在设置过程中将模式定义(typeDefs)和相应的解析器(Resolvers)提供给 Apollo Server 实例。

方法 1:在代码中使用模式字符串(简单情况)

Section titled “方法 1:在代码中使用模式字符串(简单情况)”

对于非常小的模式,你可以直接将 SDL 定义为模板字符串:

// server.js
import { ApolloServer } from '@apollo/server';
// ... 其他导入
const typeDefs = `#graphql
type Query {
greeting: String
}
`;
const resolvers = {
Query: { greeting: () => 'Hello!' },
};
const server = new ApolloServer({
typeDefs, // 直接传递模式字符串
resolvers,
});
// ... 启动服务器

方法 2:从 .graphql 文件加载(推荐)

Section titled “方法 2:从 .graphql 文件加载(推荐)”

为了可维护性,最好将模式保存在单独的 .graphql 文件中。

  • 创建一个包含 SDL 内容的 schema.graphql 文件(如上面的示例所示)。
  • 在服务器设置中加载文件内容:
// server.js
import { ApolloServer } from '@apollo/server';
import { readFileSync } from 'fs'; // Node.js 文件系统模块
import path from 'path'; // Node.js path 模块
import { fileURLToPath } from 'url'; // 处理 ES 模块路径
// ... 其他导入
// 加载解析器(假设 resolvers.js 存在)
import resolvers from './resolvers.js';
// 在 ES 模块中获取目录名
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 从文件加载模式
const typeDefs = readFileSync(path.join(__dirname, 'schema.graphql'), {
encoding: 'utf-8',
});
const server = new ApolloServer({
typeDefs, // 传递已加载的模式字符串
resolvers,
});
// ... 启动服务器

(注意:如果使用 CommonJS 的 require 而非 ES 模块的 import,文件加载方式可能略有不同。)

模式实现了一个强大的功能,即内省(Introspection)。GraphQL 服务器会暴露一组特殊字段(前缀为 __,例如 __schema、__type),允许客户端查询模式本身。Apollo Sandbox、GraphiQL 和 Apollo Client DevTools 等工具利用内省提供了自动补全、文档浏览和模式可视化等功能,无需单独的文档接口(endpoint)。

内省默认在开发环境中启用,出于安全考虑,通常在生产环境中禁用,不过这是可配置的。

这里展示的方法是先使用 SDL 定义模式,然后编写相应的解析器与之匹配,这称为 schema-first(模式优先)开发。由于 SDL 的清晰性和语言无关性,这种方法非常流行。

另一种方法是 code-first(代码优先),在这种方法中,你使用编程语言结构(例如在 TypeScript 中使用类或装饰器,配合 TypeGraphQL 或 NestJS GraphQL 等库)定义类型和解析器,然后 SDL 模式会从你的代码自动生成。这两种方法都有效,选择哪种通常取决于团队偏好和项目需求。