如何为Apollo Server配置不同GraphQL端点区分公私有查询?
嘿,这个问题其实没你想的那么复杂——Apollo Server本身就可以和Express这类Node.js HTTP框架配合,轻松实现多端点的配置,不用被那些单端点示例限制住。我给你一步步拆解实现方案:
核心思路
我们的目标是:
/graphql:无需认证,直接处理公共查询/auth/graphql:必须先通过认证,才能处理私有查询
实现的关键是利用HTTP框架的路由中间件能力:给私有端点前置一个认证校验中间件,再分别把Apollo Server挂载到两个不同的路由上。你甚至可以复用同一个Schema和Resolver,只通过上下文(Context)区分用户是否已认证。
具体实现步骤
1. 安装必要依赖
首先确保你装了基础的依赖包:
npm install express apollo-server-express jsonwebtoken
(这里用JWT作为认证方式,你可以根据实际需求换成session或其他方案)
2. 编写认证中间件
先写一个校验用户身份的中间件,比如验证请求头里的JWT令牌:
const jwt = require('jsonwebtoken'); // 认证中间件:验证JWT并将用户信息挂载到请求对象 const authenticateUser = (req, res, next) => { const authHeader = req.headers.authorization; // 检查请求头是否包含有效的Bearer令牌 if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: '请提供有效的认证令牌' }); } const token = authHeader.split(' ')[1]; try { // 验证令牌并解析用户信息 const user = jwt.verify(token, process.env.JWT_SECRET); req.user = user; // 将用户信息存入req,后续Apollo的Context可以获取 next(); // 验证通过,继续执行后续中间件 } catch (err) { return res.status(401).json({ error: '令牌无效或已过期' }); } };
3. 配置Express服务器与Apollo Server
接下来初始化Express,并把Apollo Server分别挂载到两个路由上。这里有两种方案:
方案一:复用同一个Apollo Server实例(推荐)
这种方式更节省资源,因为不需要创建两个Apollo Server实例:
const express = require('express'); const { ApolloServer } = require('apollo-server-express'); // 导入你的Schema和Resolver(假设你已经定义好了) const { typeDefs, resolvers } = require('./schema'); const app = express(); // 创建Apollo Server实例,上下文里传入请求对象的用户信息 const apolloServer = new ApolloServer({ typeDefs, resolvers, context: ({ req }) => ({ user: req.user, // 私有路由会有req.user,公共路由则为undefined }), }); // 启动Apollo Server(必须在挂载前调用start()) await apolloServer.start(); // 配置公共端点:直接挂载,不需要认证 app.use('/graphql', apolloServer.getMiddleware()); // 配置私有端点:先经过认证中间件,再挂载Apollo app.use('/auth/graphql', authenticateUser, apolloServer.getMiddleware()); // 启动服务器 app.listen({ port: 4000 }, () => { console.log('✅ 公共端点运行在 http://localhost:4000/graphql'); console.log('✅ 私有端点运行在 http://localhost:4000/auth/graphql'); });
方案二:创建两个独立的Apollo Server实例
如果你需要给两个端点配置完全不同的Schema或插件,可以用这种方式:
// 公共Apollo Server(无认证) const publicApollo = new ApolloServer({ typeDefs: publicTypeDefs, // 可以单独定义公共Schema resolvers: publicResolvers, context: () => ({ /* 公共上下文,无需用户信息 */ }), }); // 私有Apollo Server(依赖认证) const privateApollo = new ApolloServer({ typeDefs: privateTypeDefs, // 单独定义私有Schema resolvers: privateResolvers, context: ({ req }) => ({ user: req.user }), }); // 启动并挂载 await publicApollo.start(); await privateApollo.start(); publicApollo.applyMiddleware({ app, path: '/graphql' }); app.use('/auth/graphql', authenticateUser); privateApollo.applyMiddleware({ app, path: '/auth/graphql' });
4. 在Resolver中区分认证状态
在Resolver里,你可以通过Context里的user字段判断用户是否已认证,从而控制数据访问:
const resolvers = { Query: { // 公共查询:所有人都能访问 publicPosts: () => { return ['公共文章1', '公共文章2']; }, // 私有查询:只有认证用户能访问 userProfile: (_, __, { user }) => { if (!user) { throw new Error('未认证用户无法访问私有数据'); } return { id: user.id, username: user.username, email: user.email, }; }, }, };
额外优化建议
- 可以用GraphQL的Directive(比如
@auth)来标记需要认证的字段,配合Apollo Server的插件自动校验,让代码更简洁。 - 如果用TypeScript,给Context定义类型,能获得更好的类型提示。
- 可以在私有端点的Apollo Server配置里关闭Playground(如果不需要的话),或者给Playground配置默认的认证令牌,方便调试:
const privateApollo = new ApolloServer({ // ...其他配置 playground: { settings: { 'request.credentials': 'include', }, headers: { Authorization: 'Bearer YOUR_TEST_TOKEN', }, }, });
注意事项
- 确保认证中间件在Apollo的中间件之前执行,否则
req.user不会被正确设置。 - 如果用的是Fastify、Koa等其他HTTP框架,思路完全一致:先给私有路由加认证中间件,再挂载Apollo Server。
- 生产环境中,记得把JWT密钥等敏感信息放在环境变量里,不要硬编码。
内容的提问来源于stack exchange,提问作者timbo
相关产品推荐
相关产品推荐

