← 返回资讯
陈默
AI 行业分析师
已审核

GraphQL vs REST:我们为什么把 API 从 REST 迁移到 GraphQL

我们的 SaaS 产品原来用 REST API,前端经常抱怨数据不够灵活、请求太多。

GraphQL vs REST:我们为什么把 API 从 REST 迁移到 GraphQL

GraphQL vs REST:我们为什么把 API 从 REST 迁移到 GraphQL

我们的 SaaS 产品原来用 REST API,前端经常抱怨数据不够灵活、请求太多。

迁移到 GraphQL 后,前端开发效率提升 40%,API 请求减少 60%。

问题背景

REST API 的痛点

痛点 1:过度获取(Over-fetching)

JAVASCRIPT
// 前端只需要用户名和头像
// 但 REST API 返回了所有字段
GET /api/users/123

// 响应
{
  "id": 123,
  "username": "john",
  "email": "john@example.com",
  "avatar": "https://...",
  "bio": "...",
  "location": "...",
  "website": "...",
  "created_at": "...",
  "updated_at": "...",
  // ... 还有 20 个字段
}

问题:

痛点 2:获取不足(Under-fetching)

JAVASCRIPT
// 显示用户主页需要:
// 1. 用户信息
GET /api/users/123

// 2. 用户的文章
GET /api/users/123/posts

// 3. 每篇文章的评论
GET /api/posts/1/comments
GET /api/posts/2/comments
GET /api/posts/3/comments

// 4. 用户的关注者
GET /api/users/123/followers

问题:

痛点 3:版本管理困难

JAVASCRIPT
// v1 返回简单数据
GET /api/v1/users/123
{ "name": "John", "age": 30 }

// v2 返回更多数据
GET /api/v2/users/123
{ "name": "John", "age": 30, "email": "...", "avatar": "..." }

// 需要维护多个版本

GraphQL 解决方案

解决方案 1:精确获取需要的数据

GRAPHQL
# 只需要用户名和头像
query {
  user(id: 123) {
    username
    avatar
  }
}

# 响应
{
  "data": {
    "user": {
      "username": "john",
      "avatar": "https://..."
    }
  }
}

优势:

解决方案 2:一次请求获取所有数据

GRAPHQL
# 一次请求获取用户主页所有数据
query {
  user(id: 123) {
    username
    avatar
    posts(limit: 10) {
      title
      content
      comments(limit: 5) {
        author {
          username
        }
        content
      }
    }
    followers(limit: 10) {
      username
      avatar
    }
  }
}

优势:

解决方案 3:无需版本管理

GRAPHQL
# 旧客户端继续使用旧字段
query {
  user(id: 123) {
    name  # 旧字段
    age
  }
}

# 新客户端使用新字段
query {
  user(id: 123) {
    username  # 新字段
    email
    avatar
  }
}

优势:

迁移过程

阶段 1:Schema 设计

GRAPHQL
# schema.graphql

type User {
  id: ID!
  username: String!
  email: String!
  avatar: String
  bio: String
  posts(limit: Int = 10, offset: Int = 0): [Post!]!
  followers(limit: Int = 10): [User!]!
  following(limit: Int = 10): [User!]!
  createdAt: DateTime!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments(limit: Int = 10): [Comment!]!
  tags: [String!]!
  createdAt: DateTime!
  updatedAt: DateTime!
}

type Comment {
  id: ID!
  content: String!
  author: User!
  post: Post!
  createdAt: DateTime!
}

type Query {
  user(id: ID!): User
  users(limit: Int = 10, offset: Int = 0): [User!]!
  post(id: ID!): Post
  posts(limit: Int = 10, offset: Int = 0): [Post!]!
}

type Mutation {
  createPost(input: CreatePostInput!): Post!
  updatePost(id: ID!, input: UpdatePostInput!): Post!
  deletePost(id: ID!): Boolean!
  addComment(postId: ID!, content: String!): Comment!
}

input CreatePostInput {
  title: String!
  content: String!
  tags: [String!]
}

input UpdatePostInput {
  title: String
  content: String
  tags: [String!]
}

阶段 2:Resolver 实现

JAVASCRIPT
// resolvers.js

const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources }) => {
      return dataSources.userAPI.getUserById(id);
    },
    
    users: async (_, { limit, offset }, { dataSources }) => {
      return dataSources.userAPI.getUsers({ limit, offset });
    },
    
    post: async (_, { id }, { dataSources }) => {
      return dataSources.postAPI.getPostById(id);
    },
    
    posts: async (_, { limit, offset }, { dataSources }) => {
      return dataSources.postAPI.getPosts({ limit, offset });
    }
  },
  
  User: {
    posts: async (user, { limit, offset }, { dataSources }) => {
      return dataSources.postAPI.getPostsByAuthor(user.id, { limit, offset });
    },
    
    followers: async (user, { limit }, { dataSources }) => {
      return dataSources.userAPI.getFollowers(user.id, { limit });
    },
    
    following: async (user, { limit }, { dataSources }) => {
      return dataSources.userAPI.getFollowing(user.id, { limit });
    }
  },
  
  Post: {
    author: async (post, _, { dataSources }) => {
      return dataSources.userAPI.getUserById(post.authorId);
    },
    
    comments: async (post, { limit }, { dataSources }) => {
      return dataSources.commentAPI.getCommentsByPost(post.id, { limit });
    }
  },
  
  Comment: {
    author: async (comment, _, { dataSources }) => {
      return dataSources.userAPI.getUserById(comment.authorId);
    },
    
    post: async (comment, _, { dataSources }) => {
      return dataSources.postAPI.getPostById(comment.postId);
    }
  },
  
  Mutation: {
    createPost: async (_, { input }, { dataSources, user }) => {
      if (!user) throw new AuthenticationError('Must be logged in');
      return dataSources.postAPI.createPost({ ...input, authorId: user.id });
    },
    
    updatePost: async (_, { id, input }, { dataSources, user }) => {
      if (!user) throw new AuthenticationError('Must be logged in');
      const post = await dataSources.postAPI.getPostById(id);
      if (post.authorId !== user.id) {
        throw new ForbiddenError('Not authorized');
      }
      return dataSources.postAPI.updatePost(id, input);
    },
    
    deletePost: async (_, { id }, { dataSources, user }) => {
      if (!user) throw new AuthenticationError('Must be logged in');
      const post = await dataSources.postAPI.getPostById(id);
      if (post.authorId !== user.id) {
        throw new ForbiddenError('Not authorized');
      }
      return dataSources.postAPI.deletePost(id);
    },
    
    addComment: async (_, { postId, content }, { dataSources, user }) => {
      if (!user) throw new AuthenticationError('Must be logged in');
      return dataSources.commentAPI.addComment({
        postId,
        authorId: user.id,
        content
      });
    }
  }
};

阶段 3:DataLoader 优化

JAVASCRIPT
// dataloaders.js
const DataLoader = require('dataloader');

function createLoaders(dataSources) {
  return {
    userLoader: new DataLoader(async (userIds) => {
      const users = await dataSources.userAPI.getUsersByIds(userIds);
      // 确保顺序一致
      const userMap = new Map(users.map(u => [u.id, u]));
      return userIds.map(id => userMap.get(id));
    }),
    
    postLoader: new DataLoader(async (postIds) => {
      const posts = await dataSources.postAPI.getPostsByIds(postIds);
      const postMap = new Map(posts.map(p => [p.id, p]));
      return postIds.map(id => postMap.get(id));
    }),
    
    commentsByPostLoader: new DataLoader(async (postIds) => {
      const comments = await dataSources.commentAPI.getCommentsByPostIds(postIds);
      // 按 postId 分组
      const commentsMap = new Map();
      postIds.forEach(id => commentsMap.set(id, []));
      comments.forEach(c => {
        commentsMap.get(c.postId).push(c);
      });
      return postIds.map(id => commentsMap.get(id));
    })
  };
}

// 在 context 中使用
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: async ({ req }) => {
    const user = await getUserFromToken(req.headers.authorization);
    const loaders = createLoaders(dataSources);
    return { user, loaders, dataSources };
  }
});

阶段 4:渐进式迁移

JAVASCRIPT
// 同时保留 REST 和 GraphQL
// REST API 调用 GraphQL

app.get('/api/users/:id', async (req, res) => {
  const result = await graphql(schema, `
    query GetUser($id: ID!) {
      user(id: $id) {
        id
        username
        email
        avatar
      }
    }
  `, null, context, { id: req.params.id });
  
  res.json(result.data.user);
});

性能对比

| 指标 | REST | GraphQL | 改进 |

|------|------|---------|------|

| 平均请求数 | 5.2 次/页 | 1 次/页 | 81% |

| 平均响应大小 | 45KB | 12KB | 73% |

| 首屏加载时间 | 2.8s | 1.2s | 57% |

| 前端开发时间 | 5 天/功能 | 3 天/功能 | 40% |

最佳实践

1. 查询复杂度限制

JAVASCRIPT
// 防止恶意查询
const depthLimit = require('graphql-depth-limit');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    depthLimit(5)  // 最大嵌套深度 5
  ]
});

2. 查询成本分析

JAVASCRIPT
// 限制查询复杂度
const costLimit = require('graphql-cost-analysis');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    costLimit({
      maxCost: 1000,
      variables: {
        limit: 10
      }
    })
  ]
});

3. 缓存策略

JAVASCRIPT
// 使用 Apollo Client 缓存
const client = new ApolloClient({
  cache: new InMemoryCache({
    typePolicies: {
      User: {
        keyFields: ['id'],
        fields: {
          posts: {
            merge(existing, incoming) {
              return incoming;
            }
          }
        }
      }
    }
  })
});

总结

GraphQL vs REST 选择指南:

选择 GraphQL 当:

选择 REST 当:

我们的选择是正确的,GraphQL 让前端开发更高效。


迁移时间:2026年6月

项目规模:SaaS 产品,50+ API

效率提升:40%

#GraphQL #REST #API设计 #前端开发

611
10196 阅读
2 评论
分享
链接已复制
编辑说明

本文由 MakeSense 编辑团队撰写并审核。文中引用的数据和观点均经过交叉验证,如有疏漏欢迎在评论区指正。最后更新:2026年07月10日 18:03

陈默

AI 行业分析师

前某大厂 AI 实验室研究员,关注大模型技术演进和商业化落地。写过 200+ 篇行业分析,擅长从产品视角拆解技术趋势。

读者评论 2

数据分析师 3天前
数据引用很扎实,建议补充一下近三个月的最新数据。
回复 点赞 (9)
产品经理阿杰 6天前
从产品角度看,这个方向确实有机会,但商业化路径还需要验证。
回复 点赞 (15)