4. 搭建第一个GraphQL服务

在理解了 GraphQL 的基本概念之后,接下来要真正把服务跑起来。本节的目标是完成一个可运行、可查询、可验证的最小 GraphQL 服务:先选运行时与框架,再初始化项目,然后定义 Schema 与解析器,最后启动服务并用查询验证结果。整个过程围绕一个具体示例展开——一个提供书籍查询的迷你 API。

一、运行时与框架的选择

GraphQL 本身只是一套规范,它规定了查询语言、类型系统和执行语义,但并没有规定用什么语言实现。因此第一步是选择运行时(JavaScript/Node.js、Go、Java、Python 等)和对应的服务框架。对于本课程,我们以 Node.js 运行时为主,同时对比 Go 生态,便于理解不同技术栈的差异。

在 Node.js 生态中,最常用的两个框架是 Apollo Server 和 GraphQL Yoga。Apollo Server 功能完整,生态成熟,内置对缓存、联邦、订阅、错误处理的支持,适合中大型项目;GraphQL Yoga 更轻量,基于 WHATWG Fetch 标准,能直接运行在 Node、Bun、Deno、Cloudflare Workers 等环境,配置简单,适合快速起步和边缘部署。如果使用 Go,则通常选择 gqlgen,它采用 Schema First 方式,通过代码生成保证类型安全,性能好,适合高并发后端服务。

选择时可以按三个维度判断:一是团队已有技术栈,二是部署环境(传统服务器还是 Serverless/边缘),三是是否需要联邦、订阅等高级能力。对于本节的最小示例,我们选择 Apollo Server,因为它文档丰富、启动快,便于观察完整流程。

GraphQL 服务启动链路与框架选型 运行时选择 Node.js / Go / Python 框架选择 Apollo / Yoga / gqlgen 项目初始化 npm init + 依赖安装 定义 Schema type Query / type Book 编写解析器 resolver 返回数据 启动服务 listen :4000 测试验证:Apollo Sandbox / curl 发送查询 query { books { title author } } → { "data": { "books": [...] } } 判断依据:HTTP 200 + data 字段存在 + 无 errors 数组 选型要点 • 团队技术栈 • 部署环境 • 联邦/订阅需求 • 类型安全要求 • 社区与文档 本节选择 Apollo Server Node.js 运行时 快速起步验证

二、项目初始化

选定 Apollo Server 后,先创建一个空目录并初始化 Node 项目。打开终端执行:

mkdir book-graphql
cd book-graphql
npm init -y
npm install @apollo/server graphql

这里安装了两个包:@apollo/server 是 Apollo Server 的核心实现,graphql 是 GraphQL 的 JavaScript 参考实现,Apollo Server 依赖它来解析和执行查询。安装完成后,在 package.json 中把 "type" 设为 "module",这样可以使用 ES Module 语法。接着创建入口文件 server.js。

三、定义 Schema 与解析器

Schema 是 GraphQL 服务的契约,它声明了客户端可以查询哪些类型、哪些字段。我们定义一个 Book 类型和一个 Query 根类型:

const typeDefs = `#graphql
  type Book {
    id: ID!
    title: String!
    author: String!
    year: Int
  }

  type Query {
    books: [Book!]!
    book(id: ID!): Book
  }
`;

其中 ID! 表示非空 ID,[Book!]! 表示非空数组且元素也非空。解析器负责为每个字段提供数据。对于内存示例,我们准备一个数组作为数据源:

const books = [
  { id: '1', title: 'GraphQL 实战', author: '张三', year: 2023 },
  { id: '2', title: 'API 设计之道', author: '李四', year: 2022 },
  { id: '3', title: 'Node.js 进阶', author: '王五', year: 2024 },
];

const resolvers = {
  Query: {
    books: () => books,
    book: (_, { id }) => books.find(b => b.id === id),
  },
};

注意 book 解析器接收两个参数:父级返回值(此处为 _)和查询参数 { id }。当客户端传入 id: "2" 时,解析器返回对应书籍对象;若找不到则返回 undefined,GraphQL 会在结果中把该字段置为 null。

四、启动服务并测试

把 Schema 和解析器交给 Apollo Server,并监听端口:

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const server = new ApolloServer({ typeDefs, resolvers });

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`🚀 服务已启动:${url}`);

执行 node server.js,终端输出服务地址 http://localhost:4000/。在浏览器打开该地址,会进入 Apollo Sandbox 界面,可以直接输入查询并执行。例如输入:

query {
  books {
    title
    author
  }
}

预期返回:

{
  "data": {
    "books": [
      { "title": "GraphQL 实战", "author": "张三" },
      { "title": "API 设计之道", "author": "李四" },
      { "title": "Node.js 进阶", "author": "王五" }
    ]
  }
}

也可以用 curl 在命令行验证:

curl -X POST http://localhost:4000/ \
  -H "Content-Type: application/json" \
  -d '{"query":"{ book(id: \"2\") { title year } }"}'

如果返回的 JSON 中 data.book.title 为 "API 设计之道",且没有 errors 字段,说明 Schema、解析器和 HTTP 层都已正确工作。若出现 errors,常见原因包括:字段名拼写与 Schema 不一致、解析器返回类型与声明类型不匹配、端口被占用等。此时应优先检查终端报错信息和 Sandbox 中的错误提示。

Apollo Sandbox 是开发阶段的调试利器,它提供 Schema 文档浏览、查询自动补全和错误定位。生产环境通常关闭 Sandbox,改用只读的 introspection 或完全禁用。

五、从最小服务到工程化