GraphQL 到底是干什么用的?迷

230 阅读5分钟

1. 什么是GraphQL?

与 REST API 相比,GraphQL 有其自身的优势,例如

  • 只请求所需的内容,而不是所有内容。
  • 防止为获取所需数据而进行的级联调用。
  • 客户端不需要选择 REST 路径来获取不同的资源数据。
  • 它有助于减少传输的数据量。

1.1 对比

  1. 数据获取方式

    • REST API:
      GET /api/users/123        // 获取用户信息
      GET /api/users/123/posts  // 获取用户的文章
      
    • GraphQL:
      query {
        user(id: "123") {
          name
          email
          posts {
            title
            content
          }
        }
      }
      
  2. 接口设计

    • REST API:

      • 多个端点(endpoints)
      • 每个资源一个端点
      • 固定的返回格式
      /api/users      // 用户相关
      /api/posts      // 文章相关
      /api/comments   // 评论相关
      
    • GraphQL:

      • 单一端点(通常是 /graphql
      • 灵活的查询结构
      • 客户端决定返回数据的结构
      type Query {
        user(id: ID!): User
        posts: [Post]
        comments: [Comment]
      }
      
  3. 数据过度获取/获取不足问题

    • REST API:

      GET /api/users/123
      // 返回所有用户字段,即使只需要 name
      {
        "id": "123",
        "name": "张三",
        "email": "...",
        "age": 25,
        "address": "...",
        "phone": "...",
        // 更多不需要的数据
      }
      
    • GraphQL:

      query {
        user(id: "123") {
          name  // 只获取需要的字段
        }
      }
      // 返回
      {
        "data": {
          "user": {
            "name": "张三"
          }
        }
      }
      
  4. 请求方法

    • REST API:

      GET    /api/users        // 获取列表
      POST   /api/users        // 创建
      PUT    /api/users/123    // 更新
      DELETE /api/users/123    // 删除
      
    • GraphQL:

      # 查询
      query {
        users { ... }
      }
      
      # 修改(创建、更新、删除都用 mutation)
      mutation {
        createUser(input: { ... })
        updateUser(id: "123", input: { ... })
        deleteUser(id: "123")
      }
      
  5. 实际应用场景

    • REST API:

      • 简单的 CRUD 操作
      • 资源之间关系简单
      • 客户端需求相对固定
      // 前端调用示例
      fetch('/api/users/123')
        .then(res => res.json())
      
    • GraphQL:

      • 复杂的数据关系
      • 多样化的客户端需求
      • 需要优化网络请求
      // 前端调用示例
      const query = `
        query {
          user(id: "123") {
            name
            posts {
              title
            }
          }
        }
      `;
      fetch('/graphql', {
        method: 'POST',
        body: JSON.stringify({ query })
      });
      
  6. 优缺点对比

    • REST API:

      • 优点:
        • 简单直观
        • 易于理解和使用
        • 适合简单应用
      • 缺点:
        • 多个请求才能获取关联数据
        • 可能返回冗余数据
        • 接口版本管理复杂
    • GraphQL:

      • 优点:
        • 单一请求获取多个资源
        • 按需获取数据
        • 强类型系统
        • 自带文档
      • 缺点:
        • 学习曲线较陡
        • 服务端实现复杂
        • 缓存策略复杂

总的来说:

  • REST API 是一种基于资源的 API 设计规范
  • GraphQL 是一种查询语言和运行时,更注重数据查询的灵活性和效率
  • 两者可以共存,根据具体需求选择使用

1.2 实例

以下是使用mongodb + express + GraphQL 实现的基本案例 Github 可以通过http://localhost:4000/graphql

  1. 首先更新 package.json:
{
  "name": "GraphQL",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.21.2",
    "express-graphql": "^0.12.0",
    "graphql": "^16.10.0",
    "mongoose": "^8.9.2",
    "jsonwebtoken": "^9.0.0",
    "bcryptjs": "^2.4.3",
    "dotenv": "^16.0.3"
  },
  "devDependencies": {
    "nodemon": "^2.0.22"
  }
}
  1. 创建环境配置文件:
MONGODB_URI=mongodb://localhost:27017/test
JWT_SECRET=your-secret-key
PORT=4000
  1. 创建用户模型文件:models/user.model.js
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
        trim: true
    },
    email: {
        type: String,
        required: true,
        unique: true,
        trim: true,
        lowercase: true
    },
    password: {
        type: String,
        required: true
    },
    age: {
        type: Number,
        required: true
    },
    createdAt: {
        type: Date,
        default: Date.now
    }
});

module.exports = mongoose.model('User', userSchema);
  1. 创建 GraphQL schema 文件:graphql/schema.js
const { buildSchema } = require('graphql');

module.exports = buildSchema(`
    type User {
        id: ID!
        name: String!
        email: String!
        age: Int!
        createdAt: String!
    }

    type AuthPayload {
        token: String!
        user: User!
    }

    type Query {
        me: User
        user(id: ID!): User
        users: [User]
    }

    type Mutation {
        register(
            name: String!
            email: String!
            password: String!
            age: Int!
        ): AuthPayload

        login(
            email: String!
            password: String!
        ): AuthPayload

        updateUser(
            name: String
            age: Int
            email: String
        ): User

        deleteUser: Boolean
    }
`);
  1. 创建解析器文件:graphql/resolvers.js
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/user.model');

const resolvers = {
    // 查询解析器
    me: async (args, context) => {
        if (!context.user) {
            throw new Error('未认证');
        }
        return context.user;
    },

    user: async ({ id }) => {
        try {
            const user = await User.findById(id);
            if (!user) {
                throw new Error('用户不存在');
            }
            return user;
        } catch (err) {
            throw new Error('获取用户失败');
        }
    },

    users: async () => {
        try {
            return await User.find({});
        } catch (err) {
            throw new Error('获取用户列表失败');
        }
    },

    // 变更解析器
    register: async ({ name, email, password, age }) => {
        try {
            // 检查邮箱是否已存在
            const existingUser = await User.findOne({ email });
            if (existingUser) {
                throw new Error('邮箱已被注册');
            }

            // 加密密码
            const hashedPassword = await bcrypt.hash(password, 12);

            // 创建新用户
            const user = new User({
                name,
                email,
                password: hashedPassword,
                age
            });

            await user.save();

            // 生成 token
            const token = jwt.sign(
                { userId: user.id },
                process.env.JWT_SECRET,
                { expiresIn: '24h' }
            );

            return {
                token,
                user
            };
        } catch (err) {
            throw new Error('注册失败: ' + err.message);
        }
    },

    login: async ({ email, password }) => {
        try {
            const user = await User.findOne({ email });
            if (!user) {
                throw new Error('用户不存在');
            }

            const isValid = await bcrypt.compare(password, user.password);
            if (!isValid) {
                throw new Error('密码错误');
            }

            const token = jwt.sign(
                { userId: user.id },
                process.env.JWT_SECRET,
                { expiresIn: '24h' }
            );

            return {
                token,
                user
            };
        } catch (err) {
            throw new Error('登录失败: ' + err.message);
        }
    },

    updateUser: async (args, context) => {
        if (!context.user) {
            throw new Error('未认证');
        }

        try {
            const updates = {};
            if (args.name) updates.name = args.name;
            if (args.age) updates.age = args.age;
            if (args.email) updates.email = args.email;

            const user = await User.findByIdAndUpdate(
                context.user.id,
                updates,
                { new: true }
            );

            return user;
        } catch (err) {
            throw new Error('更新用户失败');
        }
    },

    deleteUser: async (args, context) => {
        if (!context.user) {
            throw new Error('未认证');
        }

        try {
            await User.findByIdAndDelete(context.user.id);
            return true;
        } catch (err) {
            throw new Error('删除用户失败');
        }
    }
};

module.exports = resolvers;
  1. 创建认证中间件:
const jwt = require('jsonwebtoken');
const User = require('../models/user.model');

const auth = async (req) => {
    const authHeader = req.headers.authorization;
    if (!authHeader) {
        return null;
    }

    try {
        const token = authHeader.replace('Bearer ', '');
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        const user = await User.findById(decoded.userId);
        return user;
    } catch (err) {
        return null;
    }
};

module.exports = auth;
  1. 最后,更新主文件:
require('dotenv').config();
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const mongoose = require('mongoose');
const schema = require('./graphql/schema');
const resolvers = require('./graphql/resolvers');
const auth = require('./middleware/auth');

// 连接数据库
mongoose.connect(process.env.MONGODB_URI)
    .then(() => console.log('MongoDB connected'))
    .catch(err => console.error('MongoDB connection error:', err));

const app = express();

// GraphQL 中间件
app.use('/graphql', graphqlHTTP(async (req) => ({
    schema: schema,
    rootValue: resolvers,
    graphiql: true,
    context: {
        user: await auth(req)
    }
})));

// 启动服务器
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
    console.log(`Server running on http://localhost:${PORT}/graphql`);
});

使用说明:

  1. 安装依赖:
npm install
  1. 启动服务器:
npm run dev
  1. 测试接口(在 GraphiQL 界面):http://localhost:4000/graphql

注册新用户:

mutation {
  register(
    name: "张三"
    email: "zhangsan@example.com"
    password: "123456"
    age: 25
  ) {
    token
    user {
      id
      name
      email
    }
  }
}

登录:

mutation {
  login(
    email: "zhangsan@example.com"
    password: "123456"
  ) {
    token
    user {
      id
      name
      email
    }
  }
}

获取当前用户信息:

query {
  me {
    id
    name
    email
    age
    createdAt
  }
}

注意:

  • 需要在 HTTP 请求头中添加 token:Authorization: Bearer <your-token>
  • 确保 MongoDB 服务已启动
  • 修改 .env 文件中的配置以匹配你的环境
  • 所有敏感操作都需要认证

这个实现提供了完整的用户认证系统,包括:

  • 用户注册
  • 用户登录
  • 获取用户信息
  • 更新用户信息
  • 删除用户
  • JWT 认证
  • 密码加密存储
  • 错误处理

这样你就基本了解了什么时GraphQL了 over,这是复习的时候遇到的一个全新的知识进行简单的记录一下