pin_drop当前位置:知识文库 ❯ 图文
后端通信:GraphQL基础 - 从入门到实践详解
教程简介
GraphQL是Facebook于2015年开源的查询语言和运行时,为API提供了一种更高效、更灵活的替代REST的方案。与REST的多个端点不同,GraphQL使用单一端点,客户端可以精确地声明需要的数据,避免了过度获取和获取不足的问题。GraphQL正在被越来越多的公司采用,成为现代Web应用的重要技术选择。
本教程将全面讲解GraphQL与REST的对比、Schema定义、Query查询、Mutation变更、Subscription订阅、变量与参数、片段(Fragment)、内省(Introspection)以及GraphQL客户端的使用。
核心概念
1. GraphQL vs REST
过度获取示例:
代码示例
// REST:获取用户列表,返回所有字段
GET /api/users
// 返回:[{id, name, email, phone, address, birthday, avatar, ...}]
// GraphQL:只获取需要的字段
query {
users {
id
name
}
}
// 返回:[{"id":1,"name":"张三"},{"id":2,"name":"李四"}]获取不足示例:
代码示例
// REST:需要两次请求
GET /api/users/1 // 获取用户
GET /api/users/1/orders // 获取用户订单
// GraphQL:一次请求
query {
user(id: 1) {
name
email
orders {
id
total
status
}
}
}2. Schema定义
Schema是GraphQL的核心,定义了API的类型系统:
代码示例
# 标量类型
scalar DateTime
# 枚举类型
enum UserRole {
ADMIN
USER
GUEST
}
enum OrderStatus {
PENDING
PAID
SHIPPED
CANCELLED
}
# 对象类型
type User {
id: ID!
name: String!
email: String!
role: UserRole!
createdAt: DateTime!
orders: [Order!]!
}
type Order {
id: ID!
user: User!
items: [OrderItem!]!
total: Float!
status: OrderStatus!
createdAt: DateTime!
}
type OrderItem {
id: ID!
product: Product!
quantity: Int!
price: Float!
}
type Product {
id: ID!
name: String!
price: Float!
stock: Int!
category: Category!
}
type Category {
id: ID!
name: String!
products: [Product!]!
}
# 查询入口
type Query {
user(id: ID!): User
users(page: Int, size: Int, role: UserRole): [User!]!
order(id: ID!): Order
orders(status: OrderStatus): [Order!]!
product(id: ID!): Product
products(categoryId: ID): [Product!]!
}
# 变更入口
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createOrder(input: CreateOrderInput!): Order!
cancelOrder(id: ID!): Order!
}
# 订阅入口
type Subscription {
orderStatusChanged(orderId: ID!): Order!
newOrder: Order!
}
# 输入类型
input CreateUserInput {
name: String!
email: String!
role: UserRole = USER
}
input UpdateUserInput {
name: String
email: String
role: UserRole
}
input CreateOrderInput {
userId: ID!
items: [OrderItemInput!]!
}
input OrderItemInput {
productId: ID!
quantity: Int!
}类型修饰符:
3. Query查询
代码示例
# 基本查询
query {
user(id: 1) {
id
name
email
}
}
# 嵌套查询
query {
user(id: 1) {
name
orders {
id
total
status
items {
product {
name
price
}
quantity
}
}
}
}
# 列表查询
query {
users(page: 1, size: 10) {
id
name
role
}
}
# 多个查询
query {
user(id: 1) { name email }
products(categoryId: 1) { id name price }
orders(status: PAID) { id total }
}4. Mutation变更
代码示例
# 创建用户
mutation {
createUser(input: {
name: "张三"
email: "zhangsan@example.com"
role: USER
}) {
id
name
email
role
createdAt
}
}
# 更新用户
mutation {
updateUser(id: 1, input: {
email: "new@example.com"
}) {
id
name
email
}
}
# 删除用户
mutation {
deleteUser(id: 1)
}
# 创建订单
mutation {
createOrder(input: {
userId: 1
items: [
{ productId: 1, quantity: 2 }
{ productId: 3, quantity: 1 }
]
}) {
id
total
status
items {
product { name }
quantity
price
}
}
}5. Subscription订阅
代码示例
# 订阅订单状态变化
subscription {
orderStatusChanged(orderId: 101) {
id
status
}
}
# 订阅新订单
subscription {
newOrder {
id
user { name }
total
createdAt
}
}6. 变量与参数
代码示例
# 使用变量
query GetUser($userId: ID!, $withOrders: Boolean!) {
user(id: $userId) {
id
name
email
orders @include(if: $withOrders) {
id
total
}
}
}
# 变量值
{
"userId": 1,
"withOrders": true
}指令:
7. 片段(Fragment)
代码示例
# 定义片段
fragment UserFields on User {
id
name
email
role
}
fragment OrderFields on Order {
id
total
status
createdAt
}
# 使用片段
query {
user(id: 1) {
...UserFields
orders {
...OrderFields
items {
product { name }
quantity
}
}
}
users {
...UserFields
}
}8. 内省(Introspection)
GraphQL支持查询自身Schema信息:
代码示例
# 查询所有类型
query {
__schema {
types {
name
kind
}
}
}
# 查询特定类型的字段
query {
__type(name: "User") {
name
fields {
name
type {
name
kind
}
}
}
}9. GraphQL客户端
Apollo Client
代码示例
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://api.example.com/graphql',
cache: new InMemoryCache()
});
// 查询
const { data } = await client.query({
query: gql`
query GetUsers {
users {
id name email
}
}
`
});
// 变更
const { data } = await client.mutate({
mutation: gql`
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id name email
}
}
`,
variables: {
input: { name: '张三', email: 'zhangsan@example.com' }
}
});使用Fetch调用GraphQL
代码示例
async function graphqlRequest(query, variables = {}) {
const response = await fetch('/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables })
});
const result = await response.json();
if (result.errors) {
throw new Error(result.errors[0].message);
}
return result.data;
}
// 使用
const data = await graphqlRequest(`
query GetUser($id: ID!) {
user(id: $id) { id name email }
}
`, { id: 1 });代码示例
示例1:GraphQL查询构建器
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GraphQL查询构建器</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f0f2f5; padding: 20px; color: #333; }
.container { max-width: 1000px; margin: 0 auto; }
h1 { text-align: center; color: #e535ab; margin-bottom: 24px; font-size: 28px; }
.card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.card h2 { font-size: 18px; color: #e535ab; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #fce4ec; }
.type-tabs { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
.type-tab { padding: 8px 16px; border: 2px solid #e8eaed; border-radius: 8px; background: #fff; cursor: pointer; font-weight: 600; font-size: 13px; transition: all 0.2s; }
.type-tab.active { background: #e535ab; color: #fff; border-color: #e535ab; }
.field-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 8px; margin-bottom: 16px; }
.field-item { padding: 10px; border: 1px solid #e8eaed; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s; display: flex; align-items: center; gap: 6px; }
.field-item:hover { border-color: #e535ab; }
.field-item.selected { background: #fce4ec; border-color: #e535ab; }
.field-name { font-family: 'Consolas', monospace; font-weight: 600; }
.field-type { font-size: 11px; color: #888; }
.query-output { background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 13px; line-height: 1.7; white-space: pre-wrap; min-height: 150px; }
.operation-select { display: flex; gap: 8px; margin-bottom: 16px; }
.op-btn { padding: 8px 16px; border: none; border-radius: 6px; font-weight: 700; cursor: pointer; font-size: 13px; color: #fff; }
.op-query { background: #34a853; }
.op-mutation { background: #ea4335; }
.op-subscription { background: #1a73e8; }
.op-btn.active { box-shadow: 0 0 0 3px rgba(0,0,0,0.2); }
.args-area { margin-bottom: 16px; }
.arg-row { display: flex; gap: 8px; align-items: center; margin-bottom: 6px; }
.arg-row label { font-weight: 600; font-size: 13px; min-width: 80px; }
.arg-row input { padding: 6px 10px; border: 1px solid #dadce0; border-radius: 4px; font-size: 13px; flex: 1; }
</style>
</head>
<body>
<div class="container">
<h1>GraphQL查询构建器</h1>
<div class="card">
<h2>选择操作类型</h2>
<div class="operation-select">
<div class="op-btn op-query active" onclick="setOp('query')">Query</div>
<div class="op-btn op-mutation" onclick="setOp('mutation')">Mutation</div>
</div>
</div>
<div class="card">
<h2>选择查询和字段</h2>
<div class="type-tabs" id="queryTabs"></div>
<div class="args-area" id="argsArea"></div>
<h3 style="font-size:14px;margin-bottom:8px;color:#555;">选择返回字段</h3>
<div class="field-grid" id="fieldGrid"></div>
</div>
<div class="card">
<h2>生成的GraphQL查询</h2>
<div class="query-output" id="queryOutput">选择查询和字段后自动生成...</div>
</div>
</div>
<script>
const schema = {
queries: {
user: { args: { id: 'ID!' }, fields: { id: 'ID!', name: 'String!', email: 'String!', role: 'UserRole!', createdAt: 'DateTime!', orders: '[Order!]!' } },
users: { args: { page: 'Int', size: 'Int', role: 'UserRole' }, fields: { id: 'ID!', name: 'String!', email: 'String!', role: 'UserRole!' } },
order: { args: { id: 'ID!' }, fields: { id: 'ID!', total: 'Float!', status: 'OrderStatus!', createdAt: 'DateTime!', items: '[OrderItem!]!' } },
product: { args: { id: 'ID!' }, fields: { id: 'ID!', name: 'String!', price: 'Float!', stock: 'Int!' } },
products: { args: { categoryId: 'ID' }, fields: { id: 'ID!', name: 'String!', price: 'Float!', stock: 'Int!' } }
},
mutations: {
createUser: { args: { name: 'String!', email: 'String!', role: 'UserRole' }, fields: { id: 'ID!', name: 'String!', email: 'String!', role: 'UserRole!', createdAt: 'DateTime!' } },
updateUser: { args: { id: 'ID!', name: 'String', email: 'String' }, fields: { id: 'ID!', name: 'String!', email: 'String!' } },
deleteUser: { args: { id: 'ID!' }, fields: { success: 'Boolean!' } },
createOrder: { args: { userId: 'ID!', items: '[OrderItemInput!]!' }, fields: { id: 'ID!', total: 'Float!', status: 'OrderStatus!' } }
}
};
let currentOp = 'query';
let currentQuery = 'user';
let selectedFields = new Set();
function setOp(op) {
currentOp = op;
document.querySelectorAll('.op-btn').forEach(b => b.classList.remove('active'));
event.currentTarget.classList.add('active');
currentQuery = Object.keys(schema[op + 's'])[0];
selectedFields.clear();
renderQueryTabs();
renderFields();
generateQuery();
}
function renderQueryTabs() {
const ops = schema[currentOp + 's'];
document.getElementById('queryTabs').innerHTML = Object.keys(ops).map(q =>
`<div class="type-tab ${q === currentQuery ? 'active' : ''}" onclick="selectQuery('${q}')">${q}</div>`
).join('');
renderArgs();
}
function selectQuery(q) {
currentQuery = q;
selectedFields.clear();
renderQueryTabs();
renderFields();
generateQuery();
}
function renderArgs() {
const op = schema[currentOp + 's'][currentQuery];
const args = op.args || {};
document.getElementById('argsArea').innerHTML = Object.entries(args).map(([name, type]) => {
const isRequired = type.includes('!');
return `<div class="arg-row"><label>${name}${isRequired ? ' *' : ''}</label><input type="text" id="arg-${name}" placeholder="${type}" oninput="generateQuery()"></div>`;
}).join('');
}
function renderFields() {
const op = schema[currentOp + 's'][currentQuery];
const fields = op.fields || {};
document.getElementById('fieldGrid').innerHTML = Object.entries(fields).map(([name, type]) =>
`<div class="field-item ${selectedFields.has(name) ? 'selected' : ''}" onclick="toggleField('${name}')">
<span class="field-name">${name}</span><span class="field-type">${type}</span>
</div>`
).join('');
}
function toggleField(name) {
if (selectedFields.has(name)) selectedFields.delete(name);
else selectedFields.add(name);
renderFields();
generateQuery();
}
function generateQuery() {
if (selectedFields.size === 0) {
document.getElementById('queryOutput').textContent = '请选择至少一个字段';
return;
}
const op = schema[currentOp + 's'][currentQuery];
const args = op.args || {};
const argEntries = Object.entries(args);
let argStr = '';
let varStr = '';
if (argEntries.length > 0) {
const vars = [];
const argsList = [];
argEntries.forEach(([name, type]) => {
const input = document.getElementById('arg-' + name);
const value = input ? input.value : '';
vars.push(`$${name}: ${type}`);
if (value) {
argsList.push(`${name}: ${value.match(/^\d+$/) ? value : `"${value}"`}`);
} else {
argsList.push(`${name}: $${name}`);
}
});
varStr = `(${vars.join(', ')})`;
argStr = `(${argsList.join(', ')})`;
}
const fieldsStr = Array.from(selectedFields).join('\n ');
const query = `${currentOp} ${currentQuery.charAt(0).toUpperCase() + currentQuery.slice(1)}${varStr} {\n ${currentQuery}${argStr} {\n ${fieldsStr}\n }\n}`;
document.getElementById('queryOutput').textContent = query;
}
renderQueryTabs();
renderFields();
</script>
</body>
</html>示例2:GraphQL与REST对比演示
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GraphQL与REST对比演示</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f0f2f5; padding: 20px; color: #333; }
.container { max-width: 1000px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.card h2 { font-size: 18px; color: #1a73e8; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #e8eaed; }
.comparison { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.panel { padding: 16px; border-radius: 8px; border: 2px solid #e8eaed; }
.panel-rest { border-color: #34a853; }
.panel-graphql { border-color: #e535ab; }
.panel-title { font-weight: 800; font-size: 16px; margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
.rest-color { color: #34a853; }
.graphql-color { color: #e535ab; }
.code-block { background: #1e1e1e; color: #d4d4d4; padding: 12px; border-radius: 6px; font-family: 'Consolas', monospace; font-size: 12px; line-height: 1.6; white-space: pre-wrap; margin-bottom: 10px; }
.scenario-btns { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
.scenario-btn { padding: 8px 16px; border: 2px solid #e8eaed; border-radius: 8px; background: #fff; cursor: pointer; font-weight: 600; font-size: 13px; transition: all 0.2s; }
.scenario-btn.active { background: #1a73e8; color: #fff; border-color: #1a73e8; }
.metric { display: flex; gap: 16px; margin-top: 8px; }
.metric-item { padding: 6px 12px; border-radius: 6px; font-size: 12px; font-weight: 600; }
.metric-good { background: #e8f5e9; color: #2e7d32; }
.metric-bad { background: #ffebee; color: #c62828; }
</style>
</head>
<body>
<div class="container">
<h1>GraphQL与REST对比演示</h1>
<div class="card">
<h2>选择场景</h2>
<div class="scenario-btns">
<div class="scenario-btn active" onclick="showScenario(0)">获取用户基本信息</div>
<div class="scenario-btn" onclick="showScenario(1)">获取用户及关联数据</div>
<div class="scenario-btn" onclick="showScenario(2)">创建资源</div>
<div class="scenario-btn" onclick="showScenario(3)">多资源查询</div>
</div>
</div>
<div class="card">
<div class="comparison" id="comparisonArea"></div>
</div>
</div>
<script>
const scenarios = [
{
title: '获取用户基本信息',
rest: {
request: 'GET /api/v1/users/1',
response: '{\n "id": 1,\n "name": "张三",\n "email": "zhangsan@example.com",\n "phone": "13800138000",\n "address": "北京市...",\n "birthday": "1995-01-01",\n "avatar": "https://...",\n "bio": "这是个人简介...",\n "createdAt": "2026-01-01"\n}',
requests: 1, overfetch: '高(返回9个字段,只需2个)'
},
graphql: {
request: 'query {\n user(id: 1) {\n name\n email\n }\n}',
response: '{\n "data": {\n "user": {\n "name": "张三",\n "email": "zhangsan@example.com"\n }\n }\n}',
requests: 1, overfetch: '无(精确获取2个字段)'
}
},
{
title: '获取用户及关联数据',
rest: {
request: 'GET /api/v1/users/1\nGET /api/v1/users/1/orders\nGET /api/v1/orders/101/items',
response: '需要3次请求,逐个获取:\n1. 用户信息\n2. 用户订单列表\n3. 订单商品详情',
requests: 3, overfetch: '高'
},
graphql: {
request: 'query {\n user(id: 1) {\n name\n orders {\n id total status\n items {\n product { name price }\n quantity\n }\n }\n }\n}',
response: '{\n "data": {\n "user": {\n "name": "张三",\n "orders": [{\n "id": 101,\n "total": 299.9,\n "status": "PAID",\n "items": [{\n "product": { "name": "商品A", "price": 149.95 },\n "quantity": 2\n }]\n }]\n }\n }\n}',
requests: 1, overfetch: '无'
}
},
{
title: '创建资源',
rest: {
request: 'POST /api/v1/users\nContent-Type: application/json\n\n{\n "name": "张三",\n "email": "zhangsan@example.com"\n}',
response: 'HTTP/1.1 201 Created\nLocation: /api/v1/users/3\n\n{\n "id": 3,\n "name": "张三",\n "email": "zhangsan@example.com",\n "role": "user",\n "status": "active",\n "createdAt": "2026-04-22T12:00:00Z"\n}',
requests: 1, overfetch: '中'
},
graphql: {
request: 'mutation {\n createUser(input: {\n name: "张三",\n email: "zhangsan@example.com"\n }) {\n id\n name\n email\n }\n}',
response: '{\n "data": {\n "createUser": {\n "id": 3,\n "name": "张三",\n "email": "zhangsan@example.com"\n }\n }\n}',
requests: 1, overfetch: '无'
}
},
{
title: '多资源查询',
rest: {
request: 'GET /api/v1/users?page=1\nGET /api/v1/orders?status=paid\nGET /api/v1/products?category=electronics',
response: '需要3次独立请求,无法在一个请求中获取',
requests: 3, overfetch: '高'
},
graphql: {
request: 'query {\n users(page: 1) { id name }\n orders(status: PAID) { id total }\n products(categoryId: 1) { id name price }\n}',
response: '{\n "data": {\n "users": [{"id":1,"name":"张三"}],\n "orders": [{"id":101,"total":299.9}],\n "products": [{"id":1,"name":"商品A","price":99.9}]\n }\n}',
requests: 1, overfetch: '无'
}
}
];
function showScenario(index) {
document.querySelectorAll('.scenario-btn').forEach(b => b.classList.remove('active'));
event.currentTarget.classList.add('active');
const s = scenarios[index];
document.getElementById('comparisonArea').innerHTML = `
<div class="panel panel-rest">
<div class="panel-title"><span class="rest-color">REST</span> ${s.title}</div>
<div class="code-block">${s.rest.request}</div>
<div class="code-block">${s.rest.response}</div>
<div class="metric">
<span class="metric-item ${s.rest.requests > 1 ? 'metric-bad' : 'metric-good'}">请求次数: ${s.rest.requests}</span>
<span class="metric-item metric-bad">过度获取: ${s.rest.overfetch}</span>
</div>
</div>
<div class="panel panel-graphql">
<div class="panel-title"><span class="graphql-color">GraphQL</span> ${s.title}</div>
<div class="code-block">${s.graphql.request}</div>
<div class="code-block">${s.graphql.response}</div>
<div class="metric">
<span class="metric-item metric-good">请求次数: ${s.graphql.requests}</span>
<span class="metric-item metric-good">过度获取: ${s.graphql.overfetch}</span>
</div>
</div>
`;
}
showScenario(0);
</script>
</body>
</html>示例3:GraphQL模拟客户端
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GraphQL模拟客户端</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f0f2f5; padding: 20px; color: #333; }
.container { max-width: 900px; margin: 0 auto; }
h1 { text-align: center; color: #e535ab; margin-bottom: 24px; font-size: 28px; }
.card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.card h2 { font-size: 18px; color: #e535ab; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #fce4ec; }
textarea { width: 100%; padding: 14px; border: 1px solid #dadce0; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 13px; line-height: 1.6; resize: vertical; min-height: 120px; }
textarea:focus { outline: none; border-color: #e535ab; }
.btn-row { display: flex; gap: 8px; margin-top: 12px; }
button { padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; }
.btn-primary { background: #e535ab; color: #fff; }
.btn-primary:hover { background: #c7309a; }
.btn-secondary { background: #5f6368; color: #fff; }
.result-area { background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 13px; line-height: 1.7; max-height: 400px; overflow-y: auto; white-space: pre-wrap; }
.preset-btns { display: flex; gap: 6px; margin-bottom: 12px; flex-wrap: wrap; }
.preset-btn { padding: 6px 12px; border: 1px solid #e8eaed; border-radius: 6px; background: #fff; cursor: pointer; font-size: 12px; }
.preset-btn:hover { border-color: #e535ab; color: #e535ab; }
</style>
</head>
<body>
<div class="container">
<h1>GraphQL模拟客户端</h1>
<div class="card">
<h2>预设查询</h2>
<div class="preset-btns">
<div class="preset-btn" onclick="loadPreset('getUser')">获取用户</div>
<div class="preset-btn" onclick="loadPreset('getUsers')">用户列表</div>
<div class="preset-btn" onclick="loadPreset('getUserOrders')">用户订单</div>
<div class="preset-btn" onclick="loadPreset('createUser')">创建用户</div>
<div class="preset-btn" onclick="loadPreset('introspect')">内省查询</div>
</div>
</div>
<div class="card">
<h2>GraphQL查询</h2>
<textarea id="queryInput">query {
user(id: 1) {
id
name
email
role
}
}</textarea>
<div class="btn-row">
<button class="btn-primary" onclick="executeQuery()">执行查询</button>
<button class="btn-secondary" onclick="document.getElementById('queryInput').value=''">清空</button>
</div>
</div>
<div class="card">
<h2>查询结果</h2>
<div class="result-area" id="resultArea">执行查询查看结果</div>
</div>
</div>
<script>
const mockData = {
users: [
{ id: 1, name: '张三', email: 'zhangsan@example.com', role: 'ADMIN', createdAt: '2026-01-15T08:00:00Z' },
{ id: 2, name: '李四', email: 'lisi@example.com', role: 'USER', createdAt: '2026-02-20T10:30:00Z' },
{ id: 3, name: '王五', email: 'wangwu@example.com', role: 'USER', createdAt: '2026-03-10T14:00:00Z' }
],
orders: [
{ id: 101, userId: 1, total: 299.9, status: 'PAID', createdAt: '2026-04-01T09:00:00Z', items: [{ id: 1, productId: 1, quantity: 2, price: 149.95 }] },
{ id: 102, userId: 1, total: 599.0, status: 'SHIPPED', createdAt: '2026-04-10T11:00:00Z', items: [{ id: 2, productId: 2, quantity: 1, price: 599.0 }] },
{ id: 103, userId: 2, total: 89.9, status: 'PENDING', createdAt: '2026-04-20T16:00:00Z', items: [{ id: 3, productId: 3, quantity: 1, price: 89.9 }] }
],
products: [
{ id: 1, name: '商品A', price: 149.95, stock: 100, categoryId: 1 },
{ id: 2, name: '商品B', price: 599.0, stock: 50, categoryId: 1 },
{ id: 3, name: '商品C', price: 89.9, stock: 200, categoryId: 2 }
]
};
const presets = {
getUser: `query {\n user(id: 1) {\n id\n name\n email\n role\n }\n}`,
getUsers: `query {\n users {\n id\n name\n email\n role\n }\n}`,
getUserOrders: `query {\n user(id: 1) {\n name\n email\n orders {\n id\n total\n status\n items {\n product {\n name\n price\n }\n quantity\n }\n }\n }\n}`,
createUser: `mutation {\n createUser(input: {\n name: "赵六"\n email: "zhaoliu@example.com"\n }) {\n id\n name\n email\n role\n }\n}`,
introspect: `query {\n __type(name: "User") {\n name\n fields {\n name\n type {\n name\n kind\n }\n }\n }\n}`
};
function loadPreset(name) {
document.getElementById('queryInput').value = presets[name];
}
function executeQuery() {
const query = document.getElementById('queryInput').value;
const result = document.getElementById('resultArea');
result.textContent = '执行中...\n';
setTimeout(() => {
try {
let data = {};
if (query.includes('user(id:') && query.includes('orders')) {
const user = { ...mockData.users[0] };
user.orders = mockData.orders.filter(o => o.userId === user.id).map(o => ({
...o,
items: o.items.map(item => ({
...item,
product: mockData.products.find(p => p.id === item.productId)
}))
}));
data = { user };
} else if (query.includes('user(id:')) {
data = { user: mockData.users[0] };
} else if (query.includes('users')) {
data = { users: mockData.users };
} else if (query.includes('createUser')) {
const newUser = { id: 4, name: '赵六', email: 'zhaoliu@example.com', role: 'USER', createdAt: new Date().toISOString() };
data = { createUser: newUser };
} else if (query.includes('__type')) {
data = { __type: { name: 'User', fields: [{ name: 'id', type: { name: 'ID', kind: 'SCALAR' } }, { name: 'name', type: { name: 'String', kind: 'SCALAR' } }, { name: 'email', type: { name: 'String', kind: 'SCALAR' } }, { name: 'role', type: { name: 'UserRole', kind: 'ENUM' } }, { name: 'orders', type: { name: null, kind: 'LIST' } }] } };
}
result.textContent = `// 查询执行成功\n// 耗时: ${Math.floor(Math.random() * 50 + 10)}ms\n\n${JSON.stringify({ data }, null, 2)}`;
} catch (error) {
result.textContent = `// 查询执行失败\n{\n "errors": [{\n "message": "${error.message}"\n }]\n}`;
}
}, 300);
}
</script>
</body>
</html>浏览器兼容性
注意事项与最佳实践
1. 查询设计
只请求需要的字段,避免过度获取
使用Fragment复用字段选择
使用变量而非字符串拼接
使用别名处理同名字段
2. 性能优化
实现查询深度限制
设置查询复杂度限制
使用DataLoader解决N+1问题
实现查询缓存
3. 安全考虑
禁用生产环境的内省查询
实现查询白名单(持久化查询)
限制查询深度和复杂度
实现认证和授权
4. 错误处理
GraphQL返回200状态码,即使有错误
错误信息在errors数组中
区分网络错误和GraphQL错误
代码规范示例
GraphQL客户端封装
代码示例
class GraphQLClient {
constructor(url, options = {}) {
this.url = url;
this.headers = { 'Content-Type': 'application/json', ...options.headers };
}
async request(query, variables = {}) {
const response = await fetch(this.url, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({ query, variables })
});
const result = await response.json();
if (result.errors && result.errors.length > 0) {
const error = result.errors[0];
throw new GraphQLError(error.message, error.extensions?.code);
}
return result.data;
}
setHeader(name, value) {
this.headers[name] = value;
}
}
class GraphQLError extends Error {
constructor(message, code) {
super(message);
this.code = code;
}
}
// 使用
const client = new GraphQLClient('/graphql');
client.setHeader('Authorization', 'Bearer token123');
const data = await client.request(`
query GetUser($id: ID!) {
user(id: $id) { id name email }
}
`, { id: 1 });常见问题与解决方案
问题1:N+1查询问题
解决方案:使用DataLoader批量加载数据:
代码示例
const DataLoader = require('dataloader');
const userLoader = new DataLoader(async (ids) => {
const users = await User.find({ _id: { $in: ids } });
return ids.map(id => users.find(u => u.id === id));
});问题2:查询深度攻击
解决方案:限制查询深度:
代码示例
// 限制最大深度为10
const depthLimit = require('graphql-depth-limit');
app.use('/graphql', depthLimit(10));问题3:缓存困难
解决方案:使用Apollo Client的缓存机制,或实现持久化查询。
问题4:文件上传
解决方案:使用graphql-multipart-request-spec规范,通过multipart/form-data上传。
总结
GraphQL是REST的有力替代方案,提供了更灵活的数据查询方式:
精确查询:客户端声明需要的数据,避免过度获取和获取不足
单一端点:所有操作通过/graphql端点完成
强类型Schema:Schema即文档,类型安全
三种操作:Query查询、Mutation变更、Subscription订阅
高级特性:变量、片段、指令、内省
客户端生态:Apollo Client、Relay等成熟方案
注意事项:N+1问题、查询复杂度、缓存、安全
GraphQL适合数据关系复杂、前端需求多样的场景,但REST在简单场景下仍有优势。根据项目需求选择合适的方案。
常见问题
什么是GraphQL基础?
GraphQL基础是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。
如何学习GraphQL基础的实际应用?
教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。
GraphQL基础有哪些注意事项?
常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。
GraphQL基础适合初学者吗?
本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。
GraphQL基础的核心要点是什么?
核心要点包括教程简介等内容,建议按教程顺序逐步学习。
本文由小确幸生活整理发布,转载请注明出处
本文涉及AI创作
内容由AI创作,请仔细甄别