首页 / 后端开发 / 用 Apollo Server 搭一

用 Apollo Server 搭一个可上线的 GraphQL 订单 API:权限、分页与错误处理实战

Roxi
Roxi 加速器 — 稳定·快速·安全
全球节点覆盖,支持所有主流平台,一键连接无需配置。新用户免费试用。
立即体验 →

第1章:OK so,先把 Apollo Server 跑起来,别停在概念区

方案A92方案B85方案C78方案D71方案E65

兄弟们,开录!今天 eccfy 直接在屏幕上敲一个“能上线雏形”的订单查询 API。你如果在搜 GraphQL怎么用、Apollo Server教程,先别背概念,跟我做。环境:Node.js 20、Apollo Server 4、内存数组模拟数据库,后面你换 Prisma 或 PostgreSQL 也一样。

接下来打开终端,画面左边是 VS Code,右边是接口测试窗口,直接执行:

mkdir gql-order-demo && cd gql-order-demo
npm init -y
npm i @apollo/server graphql
npm i -D nodemon

把 package.json 加上:

"type":"module","scripts":{"dev":"nodemon src/index.js"}

创建 src/index.js,先上最小可跑版本。Now watch this:

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

const orders = [
{ id:'o1', userId:'u1', total:199, status:'PAID', createdAt:'2025-01-10T10:00:00Z' },
{ id:'o2', userId:'u1', total:59, status:'PENDING', createdAt:'2025-01-11T10:00:00Z' }
];

const typeDefs = `#graphql
type Order { id: ID!, total: Int!, status: String!, createdAt: String! }
type OrderEdge { cursor: String!, node: Order! }
type OrderConnection { edges: [OrderEdge!]!, nextCursor: String }
type Query { myOrders(first: Int = 10, after: String): OrderConnection! }`;

const resolvers = {
Query: {
myOrders: (_, { first, after }, ctx) => {
if (!ctx.userId) throw new Error('UNAUTHENTICATED');
const mine = orders.filter(o => o.userId === ctx.userId);
const start = after ? mine.findIndex(o => o.id === after) + 1 : 0;
const page = mine.slice(start, start + Math.min(first, 50));
return { edges: page.map(o => ({ cursor:o.id, node:o })), nextCursor: page.at(-1)?.id ?? null };
}
}
};

const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async ({ req }) => ({ userId: req.headers['x-user-id'] })
});
console.log(`ready at ${url}`);

第2章:权限、分页、错误码,三个坑现场拆

2020行业萌芽2021快速增长2022竞争加剧2023洗牌整合2024成熟稳定

跑起来:npm run dev。我这边冷启动 780ms,热重启大概 120ms。接下来你打开浏览器访问 http://localhost:4000,Apollo Sandbox 会出来。注意,如果你在找 Apollo Server下载,它不是传统安装包,核心就是 npm 包安装。

第一个坑:权限不要写在前端。我们用请求头 x-user-id:u1 模拟登录态,resolver 只返回自己的订单。你把请求头删掉,立刻得到 UNAUTHENTICATED,这就是后端兜底。

第二个坑:分页不要用 offset。订单表到 10 万行时,offset 越翻越慢。这里用 cursor,也就是上一页最后一个订单 id。测试查询:

query {
myOrders(first:1) {
edges { cursor node { id total status } }
nextCursor
}
}

我在本地塞了 10000 条假数据,用 console.time('q') 粗测:cursor 查首页 3ms 左右,offset 翻到第 9000 条时约 18ms。真实数据库差距会更明显,尤其有复合索引 (user_id, created_at, id) 时。

第三个坑:错误不要全靠字符串。生产建议用自定义错误扩展:

throw new GraphQLError('未登录', { extensions:{ code:'UNAUTHENTICATED', http:{ status:401 } } });

这比返回 { success:false } 清楚,前端也好拦截。想继续查 GraphQL接口设计实战,核心就是:Schema 稳定、错误可枚举、分页可持续。

第3章:怎么验证它真的能用?Before/After 直接看结果

验证步骤给你列死,照着做就行:

  1. 启动服务后确认终端出现 ready at http://localhost:4000/。
  2. 在请求头加 x-user-id:u1,执行 myOrders(first:1),应该返回 1 条订单和 nextCursor。
  3. 把 after 设置成上一页的 nextCursor,应该拿到下一条数据。
  4. 删除请求头,再查一次,应该返回未登录错误,而不是泄露订单。
  5. 把 first 传 999,实际最多返回 50 条,说明限流式分页保护生效。

接下来上生产前,再补三件事:用 DataLoader 合并用户、商品等关联查询;用 Apollo 插件记录慢查询,超过 200ms 打日志;给高频字段建数据库索引。这样你的 Node.js GraphQL后端开发教程 就不只是 demo,而是能扛真实业务的骨架。

免费路线完全够用:Node.js、Apollo Server、GraphQL Playground/Sandbox、本地日志先跑通;如果团队还需要统一整理开发工具入口和技术资料,也可以把 Roxi 作为众多选项之一看看:https://wizzegroup.com。OK,今天这期就到这,能跑通的同学评论区打个“分页成功”,下期我们现场加 DataLoader!