1. 什么是GraphQL?
与 REST API 相比,GraphQL 有其自身的优势,例如
- 只请求所需的内容,而不是所有内容。
- 防止为获取所需数据而进行的级联调用。
- 客户端不需要选择 REST 路径来获取不同的资源数据。
- 它有助于减少传输的数据量。
1.1 对比
-
数据获取方式
- REST API:
GET /api/users/123 // 获取用户信息 GET /api/users/123/posts // 获取用户的文章 - GraphQL:
query { user(id: "123") { name email posts { title content } } }
- REST API:
-
接口设计
-
REST API:
- 多个端点(endpoints)
- 每个资源一个端点
- 固定的返回格式
/api/users // 用户相关 /api/posts // 文章相关 /api/comments // 评论相关 -
GraphQL:
- 单一端点(通常是
/graphql) - 灵活的查询结构
- 客户端决定返回数据的结构
type Query { user(id: ID!): User posts: [Post] comments: [Comment] } - 单一端点(通常是
-
-
数据过度获取/获取不足问题
-
REST API:
GET /api/users/123 // 返回所有用户字段,即使只需要 name { "id": "123", "name": "张三", "email": "...", "age": 25, "address": "...", "phone": "...", // 更多不需要的数据 } -
GraphQL:
query { user(id: "123") { name // 只获取需要的字段 } } // 返回 { "data": { "user": { "name": "张三" } } }
-
-
请求方法
-
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") }
-
-
实际应用场景
-
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 }) });
-
-
优缺点对比
-
REST API:
- 优点:
- 简单直观
- 易于理解和使用
- 适合简单应用
- 缺点:
- 多个请求才能获取关联数据
- 可能返回冗余数据
- 接口版本管理复杂
- 优点:
-
GraphQL:
- 优点:
- 单一请求获取多个资源
- 按需获取数据
- 强类型系统
- 自带文档
- 缺点:
- 学习曲线较陡
- 服务端实现复杂
- 缓存策略复杂
- 优点:
-
总的来说:
- REST API 是一种基于资源的 API 设计规范
- GraphQL 是一种查询语言和运行时,更注重数据查询的灵活性和效率
- 两者可以共存,根据具体需求选择使用
1.2 实例
以下是使用mongodb + express + GraphQL 实现的基本案例 Github 可以通过http://localhost:4000/graphql
- 首先更新 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"
}
}
- 创建环境配置文件:
MONGODB_URI=mongodb://localhost:27017/test
JWT_SECRET=your-secret-key
PORT=4000
- 创建用户模型文件: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);
- 创建 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
}
`);
- 创建解析器文件: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;
- 创建认证中间件:
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;
- 最后,更新主文件:
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`);
});
使用说明:
- 安装依赖:
npm install
- 启动服务器:
npm run dev
- 测试接口(在 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,这是复习的时候遇到的一个全新的知识进行简单的记录一下