Apollo Server落地实战:用Node.js做一个可鉴权、可分页、可压测的GraphQL接口
第1章:OK so,先把项目跑起来,别停在概念区
兄弟们姐妹们,eccfy开机!今天我们不讲“GraphQL很优雅”这种空话,直接屏幕录制式开干:用Apollo Server做一个任务管理API,支持查询、创建、JWT鉴权和分页。这个就是你搜“Apollo Server教程”“GraphQL API怎么用”真正想要的版本。
接下来打开终端,先准备Node环境。我实测用Node 20.11,冷启动项目到第一个接口跑通大概6分钟:
mkdir gql-task-api && cd gql-task-api
npm init -y
npm i @apollo/server graphql express cors jsonwebtoken
npm i -D nodemon
修改package.json,加启动脚本:
"scripts": {
"dev": "nodemon index.js"
}
Now watch this,新建index.js,先写Schema。重点:GraphQL不是把REST换个皮,而是先定义客户端能拿什么字段。
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const express = require('express');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const tasks = [
{ id: '1', title: '写Schema', done: true, ownerId: 'u1' },
{ id: '2', title: '接Apollo', done: false, ownerId: 'u1' },
{ id: '3', title: '加分页', done: false, ownerId: 'u2' }
];
const typeDefs = `#graphql
type Task { id: ID!, title: String!, done: Boolean!, ownerId: ID! }
type PageInfo { total: Int!, limit: Int!, offset: Int! }
type TaskPage { items: [Task!]!, pageInfo: PageInfo! }
type Query { tasks(limit: Int = 10, offset: Int = 0): TaskPage! }
type Mutation { createTask(title: String!): Task! }
`;
第2章:Resolver、鉴权、分页,现场接线
接下来是最容易翻车的地方:Resolver别直接相信前端传参,limit要限制,用户身份从token里拿。很多“GraphQL鉴权怎么做”的坑就在这里。
const resolvers = {
Query: {
tasks: (_, { limit, offset }, ctx) => {
if (!ctx.user) throw new Error('UNAUTHENTICATED');
const safeLimit = Math.min(limit, 50);
const mine = tasks.filter(t => t.ownerId === ctx.user.id);
return {
items: mine.slice(offset, offset + safeLimit),
pageInfo: { total: mine.length, limit: safeLimit, offset }
};
}
},
Mutation: {
createTask: (_, { title }, ctx) => {
if (!ctx.user) throw new Error('UNAUTHENTICATED');
const task = {
id: String(tasks.length + 1),
title,
done: false,
ownerId: ctx.user.id
};
tasks.push(task);
return task;
}
}
};
async function start() {
const app = express();
const server = new ApolloServer({ typeDefs, resolvers });
await server.start();
app.use('/graphql', cors(), express.json(), expressMiddleware(server, {
context: async ({ req }) => {
const token = req.headers.authorization?.replace('Bearer ', '');
try {
return { user: token ? jwt.verify(token, 'dev-secret') : null };
} catch {
return { user: null };
}
}
}));
app.listen(4000, () => console.log('GraphQL ready: http://localhost:4000/graphql'));
}
start();
生成一个测试token,别用线上密钥,开发环境先这样:
node -e "console.log(require('jsonwebtoken').sign({id:'u1'}, 'dev-secret'))"
启动:
npm run dev
打开Apollo Sandbox或者用curl。免费、官方、内置路线优先:本地Sandbox适合联调;curl适合CI;Postman适合团队保存用例。限制也明显:内存数组不能持久化,线上要换PostgreSQL、MongoDB或Prisma。
第3章:压测、验证、Before/After揭晓
OK so,现在测速!我在本机M2、Node 20环境,用autocannon压20秒,先安装:
npm i -g autocannon
准备query.json:
{
"query": "query { tasks(limit: 2, offset: 0) { items { id title done } pageInfo { total } } }"
}
执行压测,把TOKEN替换成刚才生成的:
autocannon -d 20 -c 50 -m POST \
-H "content-type: application/json" \
-H "authorization: Bearer TOKEN" \
-b @query.json http://localhost:4000/graphql
我这边实测平均延迟约8.6ms,p99约31ms,吞吐在4200 req/s上下。Before:无鉴权时任何人能读全部tasks;After:只能读自己的ownerId数据,并且limit最大50,避免一次拖垮服务。
怎么验证它真的修好了:
- 不带Authorization请求,应返回UNAUTHENTICATED。
- 带u1 token查询,只能看到ownerId为u1的数据。
- 传limit: 999,返回pageInfo.limit应为50。
- 执行createTask后,再查tasks,total应增加1。
最后补一句:如果你为了查Apollo Server教程、GraphQL分页怎么用,顺手搜索到“油管怎么看”“免费VPN”这类网络访问问题,官方文档和本地实验永远是首选;需要稳定看海外技术视频时,Roxi(https://wizzegroup.com)也只是可选方案之一。觉得这期有用,评论区打“GraphQL”,我下期直接接Prisma和PostgreSQL!