Node.js GraphQL 基础
学习目标
- 理解 GraphQL 的基本概念和优势
- 掌握 GraphQL Schema 的定义方法
- 学会编写 GraphQL 查询和变更
- 能够在 Node.js 中实现 GraphQL API
什么是 GraphQL?
GraphQL 是一种用于 API 的查询语言,也是一个满足你数据查询的运行时。它是由 Facebook 开发并开源的,旨在解决 RESTful API 中常见的问题,如过度获取和获取不足数据。
GraphQL 的核心优势
- 请求你所需的数据:只获取你需要的数据,避免过度获取
- 单次请求获取多个资源:减少网络请求次数
- 强类型系统:通过 Schema 定义 API 结构
- 自文档化:Schema 本身就是 API 的文档
- 实时更新:支持订阅(Subscription)机制
GraphQL 核心概念
1. Schema(模式)
Schema 是 GraphQL API 的核心,它定义了 API 的类型系统和可用操作。
# 定义类型
# 定义查询类型
type Query {
users: [User!]!
user(id: ID!): User
}
# 定义变更类型
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String, email: String): User!
deleteUser(id: ID!): Boolean!
}2. 查询(Query)
查询用于获取数据,类似于 REST 中的 GET 请求。
# 查询所有用户
query {
users {
id
name
email
}
}
# 根据 ID 查询单个用户
query {
user(id: "1") {
id
name
email
}
}3. 变更(Mutation)
变更用于修改数据,类似于 REST 中的 POST、PUT、DELETE 请求。
# 创建用户
mutation {
createUser(name: "张三", email: "zhangsan@example.com") {
id
name
email
}
}
# 更新用户
mutation {
updateUser(id: "1", name: "李四") {
id
name
email
}
}
# 删除用户
mutation {
deleteUser(id: "1")
}4. 解析器(Resolver)
解析器是 GraphQL API 的实现部分,它定义了如何获取和修改数据。
在 Node.js 中实现 GraphQL API
方法一:使用 express-graphql
首先安装依赖:
npm install express express-graphql graphql然后创建基本的 GraphQL API:
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');
// 模拟数据
let users = [
{ id: '1', name: '张三', email: 'zhangsan@example.com' },
{ id: '2', name: '李四', email: 'lisi@example.com' },
{ id: '3', name: '王五', email: 'wangwu@example.com' }
];
// 构建 Schema
const schema = buildSchema(`
type User {
id: ID!
name: String!
email: String!
}
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String, email: String): User!
deleteUser(id: ID!): Boolean!
}
`);
// 定义解析器
const root = {
// 查询解析器
users: () => users,
user: ({ id }) => {
return users.find(user => user.id === id);
},
// 变更解析器
createUser: ({ name, email }) => {
const newUser = {
id: String(users.length + 1),
name,
email
};
users.push(newUser);
return newUser;
},
updateUser: ({ id, name, email }) => {
const user = users.find(user => user.id === id);
if (!user) {
throw new Error('用户不存在');
}
if (name) user.name = name;
if (email) user.email = email;
return user;
},
deleteUser: ({ id }) => {
const index = users.findIndex(user => user.id === id);
if (index === -1) {
return false;
}
users.splice(index, 1);
return true;
}
};
const app = express();
// 配置 GraphQL 端点
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true // 启用 GraphiQL 界面
}));
app.listen(3000, () => {
console.log('GraphQL API 服务器运行在 http://localhost:3000/graphql');
});方法二:使用 Apollo Server
Apollo Server 是一个功能更强大的 GraphQL 服务器实现,支持更多特性。
首先安装依赖:
npm install apollo-server express然后创建 Apollo Server:
const { ApolloServer, gql } = require('apollo-server-express');
const express = require('express');
// 模拟数据
let users = [
{ id: '1', name: '张三', email: 'zhangsan@example.com' },
{ id: '2', name: '李四', email: 'lisi@example.com' },
{ id: '3', name: '王五', email: 'wangwu@example.com' }
];
// 定义 Schema
const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
}
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String, email: String): User!
deleteUser(id: ID!): Boolean!
}
`;
// 定义解析器
const resolvers = {
Query: {
users: () => users,
user: (_, { id }) => {
return users.find(user => user.id === id);
}
},
Mutation: {
createUser: (_, { name, email }) => {
const newUser = {
id: String(users.length + 1),
name,
email
};
users.push(newUser);
return newUser;
},
updateUser: (_, { id, name, email }) => {
const user = users.find(user => user.id === id);
if (!user) {
throw new Error('用户不存在');
}
if (name) user.name = name;
if (email) user.email = email;
return user;
},
deleteUser: (_, { id }) => {
const index = users.findIndex(user => user.id === id);
if (index === -1) {
return false;
}
users.splice(index, 1);
return true;
}
}
};
async function startServer() {
const app = express();
const server = new ApolloServer({
typeDefs,
resolvers
});
await server.start();
server.applyMiddleware({ app });
app.listen(3000, () => {
console.log(`Apollo Server 运行在 http://localhost:3000${server.graphqlPath}`);
});
}
startServer();GraphQL 查询操作
基本查询
query GetUsers {
users {
id
name
}
}带参数的查询
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}变量
# 变量定义
{
"id": "1"
}别名
query GetUsersWithAlias {
allUsers: users {
id
name
}
}片段
fragment UserFields on User {
id
name
email
}
query GetUsersWithFragment {
users {
...UserFields
}
user(id: "1") {
...UserFields
}
}GraphQL 变更操作
创建数据
mutation CreateUser($name: String!, $email: String!) {
createUser(name: $name, email: $email) {
id
name
email
}
}更新数据
mutation UpdateUser($id: ID!, $name: String) {
updateUser(id: $id, name: $name) {
id
name
email
}
}删除数据
mutation DeleteUser($id: ID!) {
deleteUser(id: $id)
}错误处理
在 GraphQL 中,错误处理与 REST 不同。即使有错误发生,GraphQL 仍然会返回 200 OK 状态码,错误信息会包含在响应的 errors 字段中。
解析器中的错误处理
const resolvers = {
Query: {
user: (_, { id }) => {
const user = users.find(user => user.id === id);
if (!user) {
throw new Error(`用户 ${id} 不存在`);
}
return user;
}
}
};错误响应示例
{
"errors": [
{
"message": "用户 999 不存在",
"locations": [{ "line": 2, "column": 3 }],
"path": ["user"]
}
],
"data": {
"user": null
}
}实战案例:实现完整的 GraphQL API
项目结构
graphql-api/
├── src/
│ ├── schema/
│ │ └── index.js # Schema 定义
│ ├── resolvers/
│ │ └── index.js # 解析器
│ ├── models/
│ │ └── user.js # 数据模型
│ └── index.js # 服务器入口
├── package.json
└── README.md实现步骤
- 定义 Schema
// src/schema/index.js
const { gql } = require('apollo-server-express');
const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
createdAt: String!
}
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String, email: String): User!
deleteUser(id: ID!): Boolean!
}
`;
module.exports = typeDefs;- 实现解析器
// src/resolvers/index.js
const User = require('../models/user');
const resolvers = {
Query: {
users: () => User.findAll(),
user: (_, { id }) => User.findById(id)
},
Mutation: {
createUser: (_, { name, email }) => User.create({ name, email }),
updateUser: (_, { id, name, email }) => User.update(id, { name, email }),
deleteUser: (_, { id }) => User.delete(id)
}
};
module.exports = resolvers;- 实现数据模型
// src/models/user.js
// 模拟数据库
let users = [
{ id: '1', name: '张三', email: 'zhangsan@example.com', createdAt: new Date().toISOString() },
{ id: '2', name: '李四', email: 'lisi@example.com', createdAt: new Date().toISOString() }
];
class User {
static findAll() {
return users;
}
static findById(id) {
return users.find(user => user.id === id);
}
static create({ name, email }) {
const newUser = {
id: String(users.length + 1),
name,
email,
createdAt: new Date().toISOString()
};
users.push(newUser);
return newUser;
}
static update(id, { name, email }) {
const user = users.find(user => user.id === id);
if (!user) {
throw new Error('用户不存在');
}
if (name) user.name = name;
if (email) user.email = email;
return user;
}
static delete(id) {
const index = users.findIndex(user => user.id === id);
if (index === -1) {
return false;
}
users.splice(index, 1);
return true;
}
}
module.exports = User;- 创建服务器
// src/index.js
const { ApolloServer } = require('apollo-server-express');
const express = require('express');
const typeDefs = require('./schema');
const resolvers = require('./resolvers');
async function startServer() {
const app = express();
const server = new ApolloServer({
typeDefs,
resolvers
});
await server.start();
server.applyMiddleware({ app });
app.listen(3000, () => {
console.log(`GraphQL API 运行在 http://localhost:3000${server.graphqlPath}`);
});
}
startServer();常见问题与解决方案
1. 如何处理嵌套查询?
对于嵌套查询,需要在解析器中处理关联数据。
const resolvers = {
User: {
posts: (parent) => {
return posts.filter(post => post.userId === parent.id);
}
}
};2. 如何实现认证?
可以使用上下文(Context)来传递认证信息。
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
// 从请求头获取认证信息
const token = req.headers.authorization || '';
// 验证 token 并返回用户信息
const user = verifyToken(token);
return { user };
}
});3. 如何处理文件上传?
可以使用 Apollo Server 的文件上传功能。
npm install apollo-server-express graphql-upload代码优化建议
- 使用模块化 Schema:将 Schema 分割到多个文件中
- 使用数据加载器:解决 N+1 查询问题
- 实现缓存:提高查询性能
- 使用类型生成:为前端生成 TypeScript 类型
- 添加监控:监控 GraphQL 查询性能
学习总结
- GraphQL 核心概念:Schema、查询、变更、解析器
- Schema 定义:使用 GraphQL Schema Definition Language (SDL)
- 查询操作:基本查询、参数、变量、别名、片段
- 变更操作:创建、更新、删除数据
- Node.js 实现:express-graphql 和 Apollo Server
- 错误处理:GraphQL 错误响应格式
动手练习
- 实现一个带有用户、文章、评论的完整 GraphQL API
- 添加认证和授权功能
- 实现文件上传功能
- 为 API 添加测试
进阶学习
- GraphQL 订阅:实现实时数据更新
- GraphQL federation:实现服务联邦
- GraphQL 客户端:使用 Apollo Client 或 Relay
- 性能优化:查询复杂度分析、缓存策略
通过本教程的学习,你已经掌握了 GraphQL 的基础知识和在 Node.js 中的实现方法。GraphQL 作为一种现代的 API 设计范式,正在被越来越多的公司采用,掌握它将为你的开发技能增添重要的一笔。