GraphQL

GraphQL 简介与快速上手

了解 GraphQL 是什么、和 REST 有什么区别,用 Apollo Server 搭建第一个 GraphQL 服务并发出第一条查询。

🎯 引言

如果你写过前后端联调,一定熟悉这样的场景:页面上只需要用户的名字和头像,但 REST 接口把整个用户对象都返回了回来,一大半字段根本用不上。或者反过来,一个页面要调三四个接口,才能把数据凑齐。

GraphQL 就是为解决这类问题而生的一种 API 方案。学完这篇文章,你能说清楚 GraphQL 是什么它和 REST 有什么区别,并用 Apollo Server 在本地跑起一个 GraphQL 服务,在网页调试工具里发出第一条查询。这是后续所有文章的基础环境。


🧱 什么是 GraphQL

GraphQL 是一种用于 API 的查询语言,同时是一套服务端运行时。它由 Meta(原 Facebook)开发并在 2015 年开源,如今在前端和 Node.js 社区都有广泛的应用。

它的核心思想可以用一句话概括:客户端声明自己要什么字段,服务端就精确地返回什么字段,不多也不少

打个比方,REST 接口像餐厅的固定套餐:菜单上写好有什么就给什么,想少要一道菜或者多加点东西都不行。GraphQL 则像自助点餐:你勾选好要的菜,厨房按单子出餐,分量刚好。

一条 GraphQL 查询长这样:

{
    users {
        username
        age
    }
}

返回的结果和查询的结构几乎一模一样:

{
    "data": {
        "users": [
            { "username": "小明", "age": 20 },
            { "username": "小美", "age": 22 }
        ]
    }
}

想要哪个字段就写哪个字段,这就是 GraphQL 的基本工作方式。


⚖️ 和 REST 对比

先花一分钟认识一下 REST。REST 是一种设计 API 的风格,不是协议也不是某个库,核心约定就两条:

  • 用 URL 表示资源:把数据看作一种种资源,各有地址,比如 /users 表示用户列表、/users/1 表示 id 为 1 的用户。
  • 用 HTTP 方法表示动作:同一个 URL,用 GET 查询、POST 新增、PATCH 更新、DELETE 删除。

之前 Express 课程里写的 app.get('/users', ...),其实就是在搭 REST 接口。它是过去很多年里的主流做法,至今仍被广泛使用。刚接触 GraphQL 时,可以和它对照着理解:

对比点RESTGraphQL
接口入口多个 URL(/users/articles……)通常只有一个入口(如 /graphql
返回结构服务端定死,客户端被动接受客户端按需声明字段
取关联数据常常需要多次请求拼数据一次查询嵌套取回
接口文档需要额外维护(如 Swagger)Schema 本身就是文档,可在线浏览

举个具体例子:页面上要展示「用户列表,每个用户带上他发的文章标题」。用 REST 的话,可能要先请求 /users,再按用户 id 逐个请求文章接口。用 GraphQL 只要一条查询:

{
    users {
        username
        articles {
            title
        }
    }
}
GraphQL 并不是要取代 REST。REST 简单直接、生态成熟,很多场景完全够用;GraphQL 在「数据结构复杂、不同客户端需要的字段差异大」的场景下更有优势。两种都是主流方案,按项目情况选择就好。

🛠 搭建第一个 GraphQL 服务

GraphQL 本身只是一套规范,需要一个服务端库来落地。Node.js 生态里常用的选择是 Apollo Server,它封装好了请求解析、校验、错误处理等工作,我们只需要写「数据长什么样」和「数据从哪里来」。

本课程基于 Node.js v22 LTS@apollo/server 5.xgraphql 16.x 编写和验证。新建一个项目目录,初始化后安装:

mkdir graphql-demo && cd graphql-demo
npm init -y
npm pkg set type=module
npm install @apollo/server graphql
  • npm pkg set type=module:把项目设为 ESM 模式,Apollo Server 5 要求使用 ESM 的 import 语法。
  • graphql 是 GraphQL 规范的官方 JavaScript 实现,Apollo Server 依赖它工作。

然后新建 server.js,写入下面的代码:

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

// 模拟的用户数据
const users = [
    { id: '1', username: '小明', age: 20, city: '北京' },
    { id: '2', username: '小美', age: 22, city: '深圳' },
];

// 1. 用 GraphQL 语言描述数据的结构(Schema)
const typeDefs = `#graphql
    type User {
        id: ID!
        username: String!
        age: Int
        city: String
    }

    type Query {
        users: [User!]!
        user(id: ID!): User
    }
`;

// 2. 用 JavaScript 函数提供真实的数据(Resolver)
const resolvers = {
    Query: {
        users: () => users,
        user: (parent, args) => users.find((item) => item.id === args.id),
    },
};

// 3. 创建服务并启动
const server = new ApolloServer({ typeDefs, resolvers });

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

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

这段代码是 GraphQL 服务端的标准三件套:

  • typeDefs(Schema):用 GraphQL 自己的语言描述「有哪些数据、每个字段是什么类型」。type Query 里声明的是客户端可以发起的查询。
  • resolvers(解析器):一组 JavaScript 函数,和 typeDefs 里的字段一一对应,负责真正取数据。这里是查内存数组,以后换成查数据库也一样。
  • ApolloServer:把两者组装成服务,startStandaloneServer 让它在 4000 端口跑起来。

执行 node server.js,看到 服务已启动:http://localhost:4000/ 就说明服务跑起来了。


✨ 发出第一条查询

Apollo Server 自带一个网页版调试工具 Apollo Sandbox。用浏览器打开 http://localhost:4000/,就能进入它的操作界面:左边写查询,右边看结果。

在左侧输入:

{
    users {
        username
        city
    }
}

点击运行按钮,右侧就会返回对应的 JSON 数据。注意我们只写了 usernamecity,返回里就没有 age,这就是「按需取字段」的直观体现。

再试试带参数的查询,按 id 查单个用户:

{
    user(id: "1") {
        username
        age
    }
}
Apollo Sandbox 的网页界面会不定期更新,按钮名称和布局可能与你看到的略有不同。以上流程仅供参考,核心思路不变:左边写查询、右边看结果,操作时以页面实际内容为准。

还可以故意写一个 Schema 里不存在的字段,比如 hobbies

{
    users {
        hobbies
    }
}

这次返回的不是数据,而是错误信息:Cannot query field "hobbies" on type "User"。GraphQL 会在执行前先拿查询和 Schema 做校验,字段写错了立刻就能发现,不用等到运行时出问题,这也是 Schema 带来的好处之一。


🧾 小节总结

  • GraphQL 是一种 API 查询语言,核心思想是客户端按需声明字段,服务端精确返回。
  • 和 REST 对比:GraphQL 通常是单入口、一次查询可以嵌套取关联数据;REST 多接口、返回结构由服务端定死。
  • Apollo Server 是 Node.js 生态常用的 GraphQL 服务端库,5.x 版本要求项目使用 ESM。
  • 服务端三件套:typeDefs 描述数据结构,resolvers 提供真实数据,ApolloServer 组装并启动。
  • Apollo Sandbox 是自带的网页调试工具,浏览器访问服务地址即可使用。
  • GraphQL 会先用 Schema 校验查询,写了不存在的字段会直接报错提示。

❓ 知识问答

Q1:typeDefs 外面包的 #graphql 是什么?

它是一个普通注释,作用是让编辑器把模板字符串里的内容识别为 GraphQL 语言,从而获得语法高亮和提示。删掉也不影响运行,但建议保留。

Q2:为什么查询的最外层可以什么都不写?

{ users { ... } } 其实是 query { users { ... } } 的简写。query 表示这是一次「查询操作」,是最常用的操作类型,可以省略。后面讲修改数据时会用到另一种操作 mutation

Q3:数据为什么写死在数组里?真实项目怎么办?

学习阶段用内存数组最直观。resolver 只是一个普通函数,里面换成查 MySQL、MongoDB 或请求其他接口都可以,对客户端完全透明。

Q4:GraphQL 是前端技术还是后端技术?

两边都涉及。后端用 GraphQL 定义 Schema、提供数据(本课程的重点),前端按 GraphQL 语法写查询、消费数据(最后一篇文章会讲)。

Q5:端口 4000 被占用了怎么办?

listen: { port: 4000 } 改成其他端口(如 4001)即可,浏览器访问的地址也要跟着改。


🧪 小练习

练习一:给 users 数组再加一条你自己的数据,重启服务后,在 Sandbox 里只查出所有用户的 username,不写其他字段。

# 请在这里编写代码

练习二:模仿 user(id: ID!): User 的写法,思考如果要按用户名查询,typeDefs 和 resolvers 分别要加什么,动手试一试。

server.js
// 请在这里编写代码

🎉 恭喜你已经跑起了第一个 GraphQL 服务,并理解了「按需取字段」的核心思想!下一篇我们深入 Schema 的类型系统,学会描述更复杂的数据结构。