GraphQL vs REST:一场本应昨天就开始的决斗
\n\n熟悉的情景:你正在用 JavaScript(或 Node.js)编写一个新项目,突然面临一个问题——前端与后端通信应该用什么?经典的 REST 还是时髦的 GraphQL?关于这个话题的争论从未停止,每个开发者都有自己的“铁打”观点。
\n\n老实说:REST 是一个忠实可靠的老朋友,从不会让你失望。GraphQL 则是一个大胆的新手,承诺能解决你所有的问题。但如何选择?我将向你展示的不仅仅是枯燥的对比表格,而是 JavaScript 中真实的实战示例。你将看到代码是什么样的,陷阱在哪里,最重要的是——何时该用哪种技术。
\n\n顺便说一句,如果你在用 Python 开发(别忘了这个网站是关于 Python 的),那么选择这些技术的原则非常相似。但今天我们讨论的是 JavaScript——这种在客户端和服务器端都占主导地位的语言。
\n\n快速入门:60 秒了解 REST 和 GraphQL
\n\nREST(表述性状态传递)
\n这是一种架构风格,其中每个资源(用户、帖子、评论)都有其自己的 URL。你向 /api/users/1 发送 GET 请求——获取用户信息。想要他的帖子?去 /api/users/1/posts。一切简单、可预测,但……有时过于冗长。
GraphQL
\n这是一种查询语言和运行时环境。你只有一个端点(通常是 /graphql),并且你自己决定想要获取哪些数据。没有过度获取(返回 50 个字段,但只需要 3 个)和获取不足(需要发送 5 个请求才能拼凑出一个页面)。
实战对比:用 JavaScript 编写代码
\n\n让我们设想一个典型任务:需要在页面上显示用户资料及其最近的三篇帖子。我们来比较两种方法。
\n\n基于 Express 的 REST API 示例(Node.js)
\n\n// 基于 Express 的 REST 服务器\nconst express = require('express');\nconst app = express();\n\napp.get('/api/users/:id', (req, res) => {\n // 返回用户的所有数据\n const user = users.find(u => u.id === req.params.id);\n res.json(user); // { id, name, email, phone, address, ... }\n});\n\napp.get('/api/users/:id/posts', (req, res) => {\n // 返回用户的所有帖子\n const posts = posts.filter(p => p.userId === req.params.id);\n res.json(posts); // [{ id, title, body, createdAt, ... }]\n});\n\n// 客户端代码(基于 fetch 的前端)\nasync function loadProfile(userId) {\n // 第一次请求——获取用户信息(返回很多多余数据)\n const userResponse = await fetch(`/api/users/${userId}`);\n const user = await userResponse.json();\n \n // 第二次请求——获取帖子(再次返回多余数据)\n const postsResponse = await fetch(`/api/users/${userId}/posts`);\n const allPosts = await postsResponse.json();\n \n // 只取最近 3 篇帖子\n const latestPosts = allPosts.slice(0, 3);\n \n return { user, posts: latestPosts };\n}\n\n问题很明显:我们获取了 大量多余数据(过度获取)。如果我们只显示姓名和头像,为什么需要 email、phone、address?而帖子可能有 100 篇,我们只需要 3 篇。
\n\n基于 Apollo Server 的 GraphQL 示例(Node.js)
\n\n// GraphQL 模式\nconst typeDefs = `\n type User {\n id: ID!\n name: String\n avatar: String\n posts(limit: Int): [Post]\n }\n\n type Post {\n id: ID!\n title: String\n body: String\n }\n\n type Query {\n user(id: ID!): User\n }\n`;\n\n// 解析器(数据获取逻辑)\nconst resolvers = {\n Query: {\n user: (parent, { id }) => users.find(u => u.id === id),\n },\n User: {\n posts: (parent, { limit }) => {\n let result = posts.filter(p => p.userId === parent.id);\n if (limit) result = result.slice(0, limit);\n