GraphQL 简介

前端调用 GraphQL

学会在前端项目中用 fetch 发起 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 服务不在同一个端口(比如前端 3000、后端 4000),浏览器会按跨域处理。standalone 模式默认允许跨域;Express 集成模式下,需要给 /graphql 路径加上 cors 中间件。

🛠 用 fetch 封装请求函数

前端项目里,建议先把「发 GraphQL 请求」封装成一个通用函数,业务代码只关心查询语句和变量:

api.js
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 页面中使用

用一个完整的小页面把流程串起来:进入页面时加载用户列表,渲染出来;点击按钮新增一个用户后重新加载。

UserList.vue
<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\"." }
    ]
}

还有更常见的情况:部分成功。比如用户列表查出来了,但某个字段取数失败,这时返回里 dataerrors 会同时存在。所以前端判断错误的标准做法是看返回体里的 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,返回单个用户对象。

api.js
export async function getUserById(id) {
    // 请在这里编写代码
}

练习二:给 UserList.vue 加一个「删除」按钮,点击后调用 deleteUser mutation,成功删除后刷新列表。

UserList.vue
<script setup>
async function removeUser(id) {
    // 请在这里编写代码
}
</script>

🎉 恭喜你已经掌握从前端到 GraphQL 服务的完整调用链路啦!至此,从 Schema 设计、查询、增删改到 Express 集成和前端调用,你已经具备了用 GraphQL 开发完整功能的能力,去实际项目里试试吧!