前端调用 GraphQL
🎯 引言
前面五篇我们都在服务端打转:定义 Schema、写 resolver、集成 Express。最后还差一环:前端页面怎么把这些接口用起来。
学完这篇文章,你能理解 GraphQL 请求的本质就是一个 POST 请求,用浏览器自带的 fetch 封装出通用的请求函数,在 Vue3 页面里查询数据并渲染,还能正确处理返回中的错误信息。打通这一环,从浏览器到 GraphQL 服务的完整链路就齐了。
🧱 GraphQL 请求的本质
不管语法多特别,GraphQL 请求落到网络上,其实就是一个普通的 POST 请求:
- 地址:服务的入口,比如
http://localhost:4000/graphql。 - 请求头:
Content-Type: application/json。 - 请求体:一个 JSON,包含
query(查询语句)和variables(变量,可选)。
先用 curl 直观感受一下,向我们的服务查询用户列表:
curl http://localhost:4000/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ users { username age } }"}'
返回:
{
"data": {
"users": [
{ "username": "小明", "age": 20 },
{ "username": "小美", "age": 22 }
]
}
}
看明白这一点,前端调用就没有任何神秘之处了:不用装任何专门的库,一个 fetch 就能干活。
/graphql 路径加上 cors 中间件。🛠 用 fetch 封装请求函数
前端项目里,建议先把「发 GraphQL 请求」封装成一个通用函数,业务代码只关心查询语句和变量:
const API_URL = 'http://localhost:4000/graphql';
export async function graphqlRequest(query, variables) {
const res = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ query, variables }),
});
const result = await res.json();
if (result.errors) {
throw new Error(result.errors[0].message);
}
return result.data;
}
这个函数做了三件事:按固定格式发 POST 请求、解析 JSON、把 GraphQL 返回的错误转成异常抛出。之后任何页面要用数据,一行就能调:
const data = await graphqlRequest('{ users { username age } }');
console.log(data.users);
✨ 在 Vue3 页面中使用
用一个完整的小页面把流程串起来:进入页面时加载用户列表,渲染出来;点击按钮新增一个用户后重新加载。
<script setup>
import { ref, onMounted } from 'vue';
import { graphqlRequest } from './api';
const users = ref([]);
async function loadUsers() {
const data = await graphqlRequest(`
{
users {
id
username
age
}
}
`);
users.value = data.users;
}
async function addUser() {
await graphqlRequest(
`
mutation Create($input: CreateUserInput!) {
createUser(input: $input) {
id
}
}
`,
{
input: { username: '新用户', age: 18 },
}
);
await loadUsers();
}
onMounted(() => {
loadUsers();
});
</script>
<template>
<ul>
<li v-for="item in users" :key="item.id">
{{ item.username }}({{ item.age }} 岁)
</li>
</ul>
<button @click="addUser">新增用户</button>
</template>
注意 addUser 里的写法:查询语句固定不动,动态值全部走 variables,这正是第三篇讲的变量机制在前端的落地。改数据和查数据用的是同一个函数,只是查询语句换成了 mutation。
💡 错误处理与请求库选型
GraphQL 的错误处理有个特别之处:业务错误不一定体现为 HTTP 错误码。
比如查询了一个不存在的字段,HTTP 状态码可能依然是 200,但返回体里没有 data,而是有 errors 数组:
{
"errors": [
{ "message": "Cannot query field \"hobbies\" on type \"User\"." }
]
}
还有更常见的情况:部分成功。比如用户列表查出来了,但某个字段取数失败,这时返回里 data 和 errors 会同时存在。所以前端判断错误的标准做法是看返回体里的 errors 字段,而不是只看 HTTP 状态码,我们封装的 graphqlRequest 就是这么做的。
至于请求库,日常项目可以这样选:
- 直接用 fetch:像本文这样封装几十行代码,零依赖,中小项目完全够用。
- Apollo Client:专业的 GraphQL 客户端,自带缓存、加载状态管理等能力,查询量大、对缓存有要求的项目可以考虑引入,配合 Vue 有对应的组合式 API 封装。
建议先用 fetch 把请求流程理解透,等真的遇到缓存之类的需求,再上专业客户端也不迟。
🧾 小节总结
- GraphQL 请求本质是 POST 请求,请求体是
{ query, variables }的 JSON。 - 封装一个
graphqlRequest函数统一发请求,业务代码只写查询语句和变量。 - 查询和修改用同一个函数,区别只是语句写
query还是mutation。 - 判断错误看返回体的
errors字段,不能只看 HTTP 状态码;存在「部分成功」的情况。 - 中小项目用 fetch 即可,有缓存等需求时再考虑 Apollo Client。
❓ 知识问答
Q1:能用 GET 请求发 GraphQL 吗?
规范上允许把 query 拼在 URL 上用 GET 查询,但实际开发中几乎都统一用 POST:语义清晰(Mutation 本来就不该用 GET),也不用担心 URL 长度限制。
Q2:为什么不用 axios?
完全可以,axios 发 POST 一样能调 GraphQL。本文用 fetch 是因为它零依赖、浏览器和 Node.js 都内置,项目里已经装了 axios 的话直接用它即可。
Q3:页面切换时重复请求相同数据,怎么办?
这正是 Apollo Client 这类客户端要解决的缓存问题。用 fetch 的话,可以自己用简单的键值缓存存查询结果,或者在 Pinia 里管理数据,按需选择。
Q4:variables 可以不传吗?
可以。查询语句里没有用到变量时,graphqlRequest(query) 直接省略第二个参数即可,请求体里的 variables 会是 undefined,JSON.stringify 会自动忽略它。
Q5:token 怎么带到请求头里?
在 graphqlRequest 的 headers 里加上 Authorization 字段,从 localStorage 或 Pinia 里取 token 拼进去即可,和 REST 项目的做法一致。
🧪 小练习
练习一:用封装好的 graphqlRequest 写一个 getUserById(id) 函数,通过变量传 id,返回单个用户对象。
export async function getUserById(id) {
// 请在这里编写代码
}
练习二:给 UserList.vue 加一个「删除」按钮,点击后调用 deleteUser mutation,成功删除后刷新列表。
<script setup>
async function removeUser(id) {
// 请在这里编写代码
}
</script>
🎉 恭喜你已经掌握从前端到 GraphQL 服务的完整调用链路啦!至此,从 Schema 设计、查询、增删改到 Express 集成和前端调用,你已经具备了用 GraphQL 开发完整功能的能力,去实际项目里试试吧!
