Node.js GraphQL 基础

学习目标

  • 理解 GraphQL 的基本概念和优势
  • 掌握 GraphQL Schema 的定义方法
  • 学会编写 GraphQL 查询和变更
  • 能够在 Node.js 中实现 GraphQL API

什么是 GraphQL?

GraphQL 是一种用于 API 的查询语言,也是一个满足你数据查询的运行时。它是由 Facebook 开发并开源的,旨在解决 RESTful API 中常见的问题,如过度获取和获取不足数据。

GraphQL 的核心优势

  1. 请求你所需的数据:只获取你需要的数据,避免过度获取
  2. 单次请求获取多个资源:减少网络请求次数
  3. 强类型系统:通过 Schema 定义 API 结构
  4. 自文档化:Schema 本身就是 API 的文档
  5. 实时更新:支持订阅(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

实现步骤

  1. 定义 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;
  1. 实现解析器
// 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;
  1. 实现数据模型
// 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;
  1. 创建服务器
// 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

代码优化建议

  1. 使用模块化 Schema:将 Schema 分割到多个文件中
  2. 使用数据加载器:解决 N+1 查询问题
  3. 实现缓存:提高查询性能
  4. 使用类型生成:为前端生成 TypeScript 类型
  5. 添加监控:监控 GraphQL 查询性能

学习总结

  1. GraphQL 核心概念:Schema、查询、变更、解析器
  2. Schema 定义:使用 GraphQL Schema Definition Language (SDL)
  3. 查询操作:基本查询、参数、变量、别名、片段
  4. 变更操作:创建、更新、删除数据
  5. Node.js 实现:express-graphql 和 Apollo Server
  6. 错误处理:GraphQL 错误响应格式

动手练习

  1. 实现一个带有用户、文章、评论的完整 GraphQL API
  2. 添加认证和授权功能
  3. 实现文件上传功能
  4. 为 API 添加测试

进阶学习

  • GraphQL 订阅:实现实时数据更新
  • GraphQL federation:实现服务联邦
  • GraphQL 客户端:使用 Apollo Client 或 Relay
  • 性能优化:查询复杂度分析、缓存策略

通过本教程的学习,你已经掌握了 GraphQL 的基础知识和在 Node.js 中的实现方法。GraphQL 作为一种现代的 API 设计范式,正在被越来越多的公司采用,掌握它将为你的开发技能增添重要的一笔。

« 上一篇 Node.js RESTful API 设计 下一篇 » Node.js 错误处理与调试