GraphQL 简介

与 Express 集成

学会把 Apollo Server 接入 Express 项目,让 GraphQL 和 REST 接口共存,并用 context 向 resolver 传递请求信息。

🎯 引言

前面我们一直用 startStandaloneServer 启动独立服务,适合学习和快速验证。但真实项目往往已经有一个 Express 服务在跑:里面有登录鉴权、日志中间件,还有一堆现成的 REST 接口。这时更合理的做法是把 GraphQL 作为一个路由挂载进去,而不是另起炉灶。

学完这篇文章,你能把 Apollo Server 集成到 Express 项目中,让 GraphQL 和 REST 接口共用一个端口,并学会用 context 把请求信息(比如请求头)传递给每个 resolver。这是把 GraphQL 用进真实项目的关键一步。


🧱 为什么用 expressMiddleware

Apollo Server 提供两种运行方式:

  • 独立模式startStandaloneServer):自带 HTTP 服务,开箱即用,但没法加自己的路由和中间件。
  • 中间件模式expressMiddleware):把 GraphQL 变成 Express 的一个中间件,挂在你指定的路径上,项目里其他逻辑照常工作。

从 Apollo Server 5 开始,Express 集成被拆成了独立的包,Express 5 对应 @as-integrations/express5。本文基于 @apollo/server 5.xexpress 5.x@as-integrations/express5 1.x 编写和验证,先安装:

npm install express @as-integrations/express5

🛠 把 GraphQL 挂到 Express 上

改造分三步:创建 Express 应用、启动 Apollo Server、把中间件挂到 /graphql 路径。完整代码如下:

server.js
import express from 'express';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@as-integrations/express5';

const users = [
    { id: '1', username: '小明', age: 20 },
    { id: '2', username: '小美', age: 22 },
];

const typeDefs = `#graphql
    type User {
        id: ID!
        username: String!
        age: Int
    }

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

const resolvers = {
    Query: {
        users: () => users,
        user: (parent, args) => users.find((item) => item.id === args.id),
    },
};

const app = express();

// 1. 创建并启动 Apollo Server
const server = new ApolloServer({ typeDefs, resolvers });
await server.start();

// 2. 把 GraphQL 挂载到 /graphql 路径
app.use('/graphql', express.json(), expressMiddleware(server));

// 3. 原有的 REST 接口照常工作
app.get('/hello', (req, res) => {
    res.json({ message: 'hello' });
});

app.listen(4000, () => {
    console.log('服务已启动:http://localhost:4000/graphql');
});

几个关键细节:

  • express.json():Express 的 JSON 解析中间件,必须放在 expressMiddleware 前面,GraphQL 请求体是 JSON,不解析就读不到内容。
  • app.use('/graphql', ...):GraphQL 的入口变成了 /graphql,调试工具地址也跟着变成 http://localhost:4000/graphql
  • 同一个 Express 实例上,REST 路由和 GraphQL 互不干扰,各走各的路径。

运行 node server.js 后验证:访问 http://localhost:4000/hello 返回 JSON,打开 http://localhost:4000/graphql 进入调试工具写查询,两条路都通,集成就完成了。


✨ 用 context 传递请求信息

实际项目里,resolver 经常需要「这次请求是谁发的」这类信息,最典型的就是登录态:从请求头里取出 token,判断当前用户身份。

GraphQL 用 **context(上下文)**解决这个问题。它是在 expressMiddleware 里定义的一个函数,每次请求都会执行一次,返回的对象会传给所有 resolver:

server.js
app.use(
    '/graphql',
    express.json(),
    expressMiddleware(server, {
        context: async ({ req }) => {
            return {
                token: req.headers.authorization || '',
            };
        },
    })
);

resolver 通过第三个参数拿到 context:

const resolvers = {
    Query: {
        // 第三个参数 context 就是上面函数返回的对象
        currentUser: (parent, args, context) => {
            if (!context.token) {
                return null;
            }
            // 真实项目在这里校验 token、查出当前用户
            return users[0];
        },
    },
};

配合 Schema 加一个入口:

type Query {
    users: [User!]!
    currentUser: User
}

这样,无论多少个 resolver 需要登录态,都通过 context 统一获取,不用每个接口重复解析请求头。以后接数据库时,把数据库连接也放进 context,resolver 就能统一访问了。

context 是每个请求独立创建的,不同请求之间互不影响。放心在里面放和当前请求相关的数据,不用担心串号。

🧾 小节总结

  • 真实项目用中间件模式把 GraphQL 挂进 Express,和 REST 接口共用端口。
  • Apollo Server 5 的 Express 集成在独立包里,Express 5 使用 @as-integrations/express5
  • app.use('/graphql', express.json(), expressMiddleware(server))express.json() 必须在前。
  • context 函数每次请求执行一次,返回值传给所有 resolver 的第三个参数。
  • 登录态、数据库连接等「每次请求都需要」的东西,都适合放进 context。

❓ 知识问答

Q1:忘了加 express.json() 会怎样?

Apollo Server 读不到解析后的请求体,会报请求格式相关的错误。只要看到 GraphQL 请求全部失败,先检查 JSON 解析中间件的位置。

Q2:能把 GraphQL 挂在根路径 / 吗?

可以,app.use('/', ...) 即可,但不推荐。挂成 /graphql 这样的独立路径,和 REST 路由界限清晰,也方便后续加鉴权等中间件。

Q3:REST 接口要全部迁移到 GraphQL 吗?

不用。两者可以长期共存,新功能用 GraphQL,旧的 REST 保持稳定运行,逐步过渡即可。

Q4:context 函数里能做异步操作吗?

可以,它支持 async,比如在里面查数据库验证 token。注意它每个请求都会执行,耗时操作会影响所有接口的响应速度。

Q5:standalone 模式和中间件模式的代码差别大吗?

typeDefs 和 resolvers 完全一样,只是启动部分从 startStandaloneServer 换成 Express 挂载。业务逻辑零改动,可以放心迁移。


🧪 小练习

练习一:在 Express 服务上加一个 REST 接口 GET /api/users 返回用户数组,确认它和 /graphql 的查询能同时正常工作。

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

练习二:扩展 context,把请求头里的 x-user-id 传进去,然后实现 currentUser 查询:按这个 id 返回对应用户,没传或找不到时返回 null。

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

🎉 恭喜你已经能把 GraphQL 集成进 Express 项目啦!最后一篇我们换个视角,看看前端怎么发起 GraphQL 请求,把整条链路彻底打通。