
准备工作:5分钟搭好开发环境
先别急着写代码,咱们得把工具准备好。我用的是Node.js,你电脑上要是没有,直接去下载LTS版本””>Node.js官网下LTS版本(长期支持版更稳定),安装时记得勾选“Add to PATH”,不然命令行里输命令会提示“找不到”。安装完打开终端(Windows用PowerShell,Mac/Linux用Terminal),输入node -v
,能看到v18.x.x这样的版本号就说明装好了。
接着建个项目文件夹,比如叫my-graphql-api
,终端里cd进去,输入npm init -y
,会自动生成一个package.json
文件——这就像咱们的“项目说明书”,记录依赖包和脚本命令。我之前忘了加-y
,结果弹出来一堆配置项,填了半天还填错了,加上-y
能直接用默认配置,省事儿。
然后是核心依赖包。GraphQL只是个规范,咱们得用具体的服务器工具来实现它。我对比过几个主流工具,最后选了Apollo Server,这是目前最火的GraphQL服务器,和React、Vue这些前端框架兼容性都很好,文档也写得清楚(后面会放官网链接)。你在终端输入npm install @apollo/server graphql
,等它跑完,package.json
里的dependencies
就会多出这两个包。
这里给你整理了不同GraphQL服务器工具的对比,你可以根据项目选:
工具名称 | 优点 | 缺点 | 适合场景 |
---|---|---|---|
Apollo Server | 生态完善、支持订阅、文档友好 | 包体积稍大 | 中大型项目、需要团队协作 |
Express GraphQL | 轻量、适合Express生态 | 功能较少、需手动配很多东西 | 小型项目、已有Express后端 |
Relay | Facebook官方、优化React性能 | 学习曲线陡、配置复杂 | 大型React项目、性能要求高 |
表格数据参考自工具对比””>Apollo官网工具对比页面
环境准备到这儿就差不多了。你可能会问:“需不需要数据库?”新手阶段不用急,咱们先用假数据跑通流程,后面再连MongoDB或MySQL都很简单。
核心步骤:从定义Schema到实现接口
这部分是重点,我会拆成“定义数据结构”“写处理逻辑”“启动服务”“测试接口”四个小步骤,每个步骤我都标了“坑点提醒”——这些都是我之前踩过的雷,你照着做能少走弯路。
步骤1:定义Schema——告诉GraphQL你有什么数据
Schema(模式)就像“数据菜单”,告诉前端“你能要什么数据,每个数据长什么样”。比如你要做个商品接口,就得告诉前端:“我有商品数据,每个商品有id(数字)、name(字符串)、price(数字)这三个字段,你可以查单个商品或商品列表。”
咱们来写个简单的商品Schema。在项目根目录建个schema.js
文件,输入这段代码:
const { gql } = require('graphql-tag');
;const typeDefs = gql
# 定义商品类型
type Product {
id: ID! # 商品ID,!表示必填
name: String! # 商品名称
price: Float! # 商品价格
stock: Int # 库存数量,没有!表示可选
}
# 定义查询类型(获取数据)
type Query {
# 获取单个商品,传id参数,返回Product类型
getProduct(id: ID!): Product
# 获取商品列表,返回Product数组
getProducts: [Product]
}
module.exports = typeDefs;
坑点提醒
:别漏写ID!
后面的感叹号!我第一次写时忘了加,结果前端传id参数时GraphQL一直报错“参数类型不匹配”。感叹号在GraphQL里表示“非空”——字段加!
表示“这个字段一定有值,不会返回null”;参数加!
表示“前端必须传这个参数,不然请求失败”。
这里的gql
是个标签函数,用来把字符串转成GraphQL能识别的Schema对象,所以记得从graphql-tag
里引入。如果你用VS Code,可以装个GraphQL
插件,写Schema时会有语法高亮和提示,比纯文本方便多了。
步骤2:写Resolvers——告诉GraphQL怎么拿数据
有了“菜单”(Schema),还得有“厨师”(Resolvers)——告诉GraphQL“怎么拿到这些数据”。Resolvers是个对象,键名要和Schema里的Query/Mutation字段对应,值是函数,函数返回的数据就是前端最终拿到的结果。
咱们接着在根目录建resolvers.js
文件,写处理逻辑:
// 假数据(后面换成数据库查询就行)
const products = [
{ id: '1', name: '无线耳机', price: 299, stock: 100 },
{ id: '2', name: '机械键盘', price: 399, stock: 50 },
{ id: '3', name: '鼠标', price: 99 } // 故意不加stock,测试可选字段
];
const resolvers = {
Query: {
// 获取单个商品:从products数组里找id匹配的项
getProduct: (parent, args) => {
return products.find(item => item.id === args.id);
},
// 获取商品列表:直接返回整个数组
getProducts: () => {
return products;
}
}
};
module.exports = resolvers;
亲测技巧
:Resolvers函数有四个参数(parent, args, context, info),新手阶段重点记args
——这是前端传过来的参数,比如getProduct(id: "1")
里的id
就存在args.id
里。parent
和context
后面处理关联数据(比如“查商品时顺便查分类”)才会用到,现在不用管。
我之前试过把Resolvers和Schema写在一个文件里,结果接口一多就乱成一锅粥。 你像我这样分开写,后面维护时一眼就能找到“数据定义”和“处理逻辑”在哪,尤其是团队协作时,别人接手你的代码也会轻松很多。
步骤3:启动服务器——让接口跑起来
现在“菜单”和“厨师”都有了,该搭个“餐厅”(服务器)让用户能访问接口了。在根目录建index.js
(入口文件),输入启动代码:
const { ApolloServer } = require('@apollo/server');
const { startStandaloneServer } = require('@apollo/server/standalone');
const typeDefs = require('./schema');
const resolvers = require('./resolvers');
// 创建Apollo服务器实例
const server = new ApolloServer({
typeDefs, // 传入Schema
resolvers // 传入Resolvers
});
// 启动服务器
async function startServer() {
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 } // 监听4000端口
});
console.log(服务器启动成功!访问地址:${url}
);
}
startServer();
写完后打开终端,输入node index.js
,如果看到“服务器启动成功!访问地址:http://localhost:4000”,就说明成功了。这时候打开浏览器访问这个地址,会看到Apollo自带的GraphQL Playground——一个可视化的接口测试工具,比Postman方便多了。
常见问题
:如果启动时报“端口被占用”,把port: 4000
改成别的数字(比如3001)就行;如果报“Cannot find module”,检查一下是不是require
的文件路径写错了,比如把./schema
写成./Schema
(文件名大小写要匹配)。
步骤4:测试接口——用Playground验证功能
打开GraphQL Playground,左边输入查询语句,点“运行”按钮,右边就能看到返回结果。咱们来测两个接口:
查商品列表
:
query {
getProducts {
id
name
price
}
}
点运行后,右边会返回咱们定义的三个商品数据,每个商品只有id、name、price三个字段——这就是GraphQL的“按需获取”:前端要什么字段,后端就返回什么字段,不会多也不会少。
查单个商品
:
query {
getProduct(id: "2") {
name
price
stock
}
}
这次咱们要了stock字段,返回的机械键盘会显示stock: 50
,而如果查id为3的鼠标,因为咱们没给stock字段,会返回stock: null
(因为Schema里stock没有!
,允许为空)。
小技巧
:Playground顶部有个“DOCS”按钮,点进去能看到你定义的所有Schema信息,前端同事看这个就能知道怎么调接口,不用你再写接口文档了——我之前做项目时,光接口文档就写了两小时,用GraphQL后直接甩给前端这个地址,省了超多沟通时间。
现在你已经实现了基础的GraphQL查询接口,接下来可以试试加个Mutation(修改数据)实现“添加商品”,或者把假数据换成MongoDB数据库(用Mongoose连库,Resolvers里写Product.find()
就行)。如果你按这些步骤做了,欢迎在评论区告诉我你的第一个GraphQL接口是什么功能,遇到问题也可以问我——比如“怎么处理权限验证”“怎么连MySQL”,我会把我的解决方案分享给你!
你是不是也遇到过这种情况:写Resolvers的时候,对着parent、args、context这几个参数发呆,尤其是args,总搞不清它到底装了什么东西?其实你把它想成“快递包裹”就好理解了——前端调接口时填的“收货地址”“联系方式”,都会打包成这个包裹,送到Resolvers手里。比如你在前端写query { getProduct(id: "2") { name } }
,这里的id: "2"
就像快递单上的“收件人编号”,会被GraphQL自动塞进args里,所以在Resolvers的getProduct函数里,你只要打开这个“包裹”(也就是写args.id
),就能拿到这个编号,再用它去数据里找对应的商品。
我之前帮一个电商项目写接口时,就踩过args的坑。当时要实现“按价格区间筛选商品”,前端传了minPrice=100和maxPrice=500,结果Resolvers里怎么都拿不到这两个值,排查了半小时才发现,是我在Schema的Query里只定义了getProducts,忘了加参数!后来在Schema里补上getProducts(minPrice: Float, maxPrice: Float): [Product]
,Resolvers里就能通过args.minPrice和args.maxPrice拿到值了,再用这两个值去过滤数据——比如遍历商品数组,只留下price大于minPrice且小于maxPrice的项。 args就是“前端给后端的小纸条”,你想让前端传什么条件(比如筛选、分页、排序),就在Schema里提前“告诉”GraphQL“我要收这些纸条”,然后在Resolvers里拆开纸条(读args),按纸条上的要求处理数据就行。
再举个更具体的例子,你要做个“分页查询商品列表”的功能,前端需要传page(页码)和limit(每页条数),那第一步就得在Schema的Query里定义:getProducts(page: Int, limit: Int): [Product]
——这里page和limit就是你让前端传的“纸条内容”。然后在Resolvers里,用args.page和args.limit来算起始位置,比如page=1、limit=10时,起始索引就是0,截取数组的0到10项;page=2时,起始索引就是10,截取10到20项。我之前写分页时,忘了在Schema里加page和limit参数,结果前端传了值,Resolvers里args一直是空对象,还以为是GraphQL出了bug,后来才发现是自己漏写了“收纸条的地址”,白白浪费了20分钟排查时间。所以你写Resolvers前,先在Schema里想清楚:前端要传什么条件?每个条件叫什么名字?类型是什么?把这些都定义好,args里自然就能拿到对应的值了。
为什么选择Apollo Server而不是其他GraphQL工具?
主要是考虑到零基础友好度和生态完善度。Apollo Server自带可视化的Playground测试工具,不用额外配Postman;文档里有大量中文示例,遇到问题搜“Apollo Server + 你的报错”基本都能找到解决方案。另外它和前端框架兼容性好,比如用React的话,直接配Apollo Client就能无缝对接,省得自己写数据请求逻辑。我之前试过Express GraphQL,虽然轻量,但得手动配CORS、错误处理这些,对新手不太友好,Apollo Server开箱即用,更适合快速上手。
开发GraphQL接口必须用Node.js吗?
不是必须的,GraphQL是跨语言的规范,很多语言都有实现工具。比如Java可以用GraphQL Java,Python有Graphene,PHP有Lighthouse。但零基础的话,Node.js生态最成熟,教程和社区支持最多,npm上各种现成的依赖包(比如连接数据库的插件)也多,能少走很多弯路。如果你的项目已经用了其他后端语言,比如Python,直接用对应语言的GraphQL工具就行,核心逻辑(定义Schema、写Resolvers)是相通的。
Schema里的感叹号(!)经常忘写,到底有什么用?
感叹号在GraphQL里表示“非空约束”,分两种情况:字段后加!,表示“这个字段一定有值,不会返回null”,比如Product类型的name: String!,意味着查询商品时name字段绝对不会空;参数后加!,表示“前端必须传这个参数”,比如getProduct(id: ID!),前端调接口时如果不传id,GraphQL会直接报错“缺少必填参数”。我 刚开始写Schema时,先给所有字段和关键参数都加上!,后面根据实际需求(比如某些字段确实可能为空)再去掉,比漏写导致bug后排查更高效。
Resolvers里的args参数总是搞混,它到底存的是什么?
args就是前端调接口时传过来的参数,比如前端想查id=2的商品,写query { getProduct(id: “2”) { name } },这里的id: “2”就会存在args里,所以Resolvers里可以通过args.id拿到这个值。简单说,args是“前后端数据传递的桥梁”,你想让前端传什么条件(比如筛选、分页、排序),就在Schema的字段里定义参数,然后在Resolvers里通过args获取并处理。比如实现“按价格筛选商品”,就可以在getProducts里加个price参数,Resolvers里用args.price过滤数据。
接口写完后,怎么确定它能正常工作?
最直接的就是用Apollo Server自带的Playground。启动服务后访问localhost:4000,左边输入查询语句,比如query { getProducts { name price } },点运行按钮,右边如果能返回你定义的商品数据,就说明接口没问题。如果报错,看“Errors”栏的提示,比如“Cannot query field ‘xxx’ on type ‘Query’”,通常是Schema里没定义这个字段,或者Resolvers里漏写了对应的处理函数。另外可以用curl命令测试,比如终端输入curl -X POST -H “Content-Type: application/json” -d ‘{“query”:”query { getProducts { name } }”}’ http://localhost:4000,能返回JSON数据就说明接口能被外部调用。