{"id":10021411,"date":"2026-07-25T00:13:27","date_gmt":"2026-07-24T16:13:27","guid":{"rendered":"https:\/\/itmax.cn\/?p=10021411"},"modified":"2026-07-25T00:13:27","modified_gmt":"2026-07-24T16:13:27","slug":"%e6%b7%b1%e5%85%a5%e7%90%86%e8%a7%a3-graphql%ef%bc%9a%e4%b8%80%e6%9d%a1%e6%9f%a5%e8%af%a2%e5%88%b0%e5%ba%95%e6%98%af%e6%80%8e%e4%b9%88%e6%89%a7%e8%a1%8c%e7%9a%84","status":"publish","type":"post","link":"https:\/\/itmax.cn\/?p=10021411","title":{"rendered":"\u6df1\u5165\u7406\u89e3 GraphQL\uff1a\u4e00\u6761\u67e5\u8be2\u5230\u5e95\u662f\u600e\u4e48\u6267\u884c\u7684"},"content":{"rendered":"<p>\u5927\u591a\u6570\u540e\u7aef\u5f00\u53d1\u8005\u5bf9 GraphQL \u7684\u7b2c\u4e00\u5370\u8c61\u662f\u300c\u4e00\u4e2a\u66f4\u7075\u6d3b\u7684 REST\u300d\u2014\u2014\u5ba2\u6237\u7aef\u80fd\u81ea\u9009\u5b57\u6bb5\u3001\u907f\u514d\u5197\u4f59\u3002\u4f46\u8fd9\u53ea\u662f\u5165\u53e3\u3002GraphQL \u66f4\u5173\u952e\u7684\u53d8\u5316\uff0c\u662f\u8ba9\u670d\u52a1\u7aef\u901a\u8fc7 Schema \u58f0\u660e\u53ef\u7528\u80fd\u529b\uff0c\u8ba9\u5ba2\u6237\u7aef\u7528 selection set \u7ec4\u5408\u672c\u6b21\u54cd\u5e94\uff1b\u6267\u884c\u5668\u518d\u6309\u8fd9\u68f5\u5b57\u6bb5\u6811\u8c03\u5ea6 Resolver\u3002<\/p>\n<p>\u6cbf\u7740\u8fd9\u6761\u4e3b\u7ebf\uff0c\u7247\u6bb5\uff08Fragment\uff09\u3001\u6307\u4ee4\uff08Directive\uff09\u3001\u8ba2\u9605\uff08Subscription\uff09\u4ee5\u53ca DataLoader \u7b49\u673a\u5236\u5c31\u4e0d\u518d\u662f\u5b64\u7acb\u6982\u5ff5\uff0c\u800c\u662f\u5206\u522b\u4f5c\u7528\u4e8e\u5b57\u6bb5\u6536\u96c6\u3001\u6267\u884c\u548c\u6570\u636e\u8bbf\u95ee\u7684\u5de5\u7a0b\u80fd\u529b\u3002<\/p>\n<blockquote><p>\u672c\u6587\u793a\u4f8b\u57fa\u4e8e <code>graphql-js<\/code> 16.x \u4e0e Node.js 20+\u3002\u6838\u5fc3\u6982\u5ff5\u5bf9\u4efb\u4f55\u8bed\u8a00\u7684\u5b9e\u73b0\uff08graphql-go \/ graphql-java \/ async-graphql \u7b49\uff09\u901a\u7528\u3002<\/p><\/blockquote>\n<hr>\n<h2>\u4e00\u3001\u5148\u5efa\u7acb\u5fc3\u667a\u6a21\u578b\uff1aSchema \u662f\u5951\u7ea6\uff0cQuery \u662f\u54cd\u5e94\u7684\u5f62\u72b6<\/h2>\n<p>\u5728\u5178\u578b REST API \u91cc\uff0c\u8bf7\u6c42\u65b9\u6cd5\u4e0e URL \u8868\u8fbe\u64cd\u4f5c\uff0c\u54cd\u5e94\u5b57\u6bb5\u4e3b\u8981\u7531\u670d\u52a1\u7aef\u51b3\u5b9a\u3002GraphQL \u5219\u628a\u5b57\u6bb5\u9009\u62e9\u4ea4\u7ed9\u5ba2\u6237\u7aef\uff1a<\/p>\n<ul>\n<li><strong>\u670d\u52a1\u7aef\u58f0\u660e\u300c\u80fd\u67e5\u4ec0\u4e48\u300d<\/strong>\u2014\u2014\u901a\u8fc7 Schema \u5b9a\u4e49\u7c7b\u578b\u3001\u5b57\u6bb5\u548c\u5173\u7cfb\uff0c\u5e76\u5b9e\u73b0\u53d6\u6570\u3001\u6743\u9650\u7b49\u89c4\u5219\u3002<\/li>\n<li><strong>\u5ba2\u6237\u7aef\u51b3\u5b9a\u300c\u5b9e\u9645\u8981\u4ec0\u4e48\u300d<\/strong>\u2014\u2014\u901a\u8fc7 Query \u6311\u9009\u5b57\u6bb5\u6811\u3002<\/li>\n<li><strong>\u6210\u529f\u54cd\u5e94\u91cc <code>data<\/code> \u7684\u7ed3\u6784\u4e0e\u5b9e\u9645\u6267\u884c\u7684 selection set \u5bf9\u5e94<\/strong>\u3002<\/li>\n<\/ul>\n<p>\u66f4\u7cbe\u786e\u5730\u8bf4\uff0c<code>data<\/code> \u4f1a\u6309 Query \u7684 selection set \u7ec4\u7ec7\uff1b\u5982\u679c Resolver \u629b\u9519\u6216\u89e6\u53d1 <code>Non-Null<\/code> \u5192\u6ce1\uff0c\u76f8\u5173\u5206\u652f\u53ef\u80fd\u53d8\u6210 <code>null<\/code>\uff0c\u5177\u4f53\u5931\u8d25\u4f4d\u7f6e\u4f1a\u51fa\u73b0\u5728 <code>errors[].path<\/code> \u91cc\u3002<\/p>\n<pre style=\"display:none;\"><code class=\"mermaid\">flowchart LR\n    subgraph Client[&quot;\u5ba2\u6237\u7aef&quot;]\n        Q[&quot;Query&lt;br\/&gt;\u5b57\u6bb5\u6811&quot;]\n    end\n    subgraph Server[&quot;GraphQL \u670d\u52a1\u7aef&quot;]\n        S[&quot;Schema&lt;br\/&gt;\u7c7b\u578b\u5951\u7ea6&quot;]\n        P[&quot;Parser&lt;br\/&gt;\u8bcd\u6cd5\/\u8bed\u6cd5&quot;]\n        V[&quot;Validator&lt;br\/&gt;\u7c7b\u578b\u6821\u9a8c&quot;]\n        E[&quot;Executor&lt;br\/&gt;\u9012\u5f52 Resolver&quot;]\n        R[(&quot;Resolvers&lt;br\/&gt;\u5b57\u6bb5\u5230\u6570\u636e&quot;)]\n    end\n    subgraph Response[&quot;\u54cd\u5e94&quot;]\n        J[&quot;JSON&lt;br\/&gt;data \u5f62\u72b6\u5bf9\u5e94 Query&quot;]\n    end\n\n    Q --&gt; P\n    P --&gt; V\n    S -. \u5951\u7ea6 .-&gt; V\n    V --&gt; E\n    E --&gt; R\n    R --&gt; E\n    E --&gt; J\n    J --&gt; Q<\/code><\/pre>\n<p>\u4e00\u53e5\u8bdd\u6982\u62ec\uff1a<strong>GraphQL \u5f15\u64ce\u662f\u4e00\u4e2a\u300c\u7531 selection set \u9a71\u52a8\u3001\u53d7 Schema \u7ea6\u675f\u7684\u5b57\u6bb5\u6267\u884c\u5668\u300d<\/strong>\u3002\u6570\u636e\u53ef\u4ee5\u6765\u81ea MySQL\u3001Redis\u3001\u5fae\u670d\u52a1\u6216\u6587\u4ef6\uff0c\u5177\u4f53\u8bbf\u95ee\u65b9\u5f0f\u7531 Resolver \u548c\u6570\u636e\u8bbf\u95ee\u5c42\u8d1f\u8d23\u3002<\/p>\n<hr>\n<h2>\u4e8c\u3001\u67e5\u8be2\u5904\u7406\u7684\u56db\u9636\u6bb5\u6d41\u6c34\u7ebf<\/h2>\n<p>\u4efb\u4f55\u4e00\u6761 GraphQL \u67e5\u8be2\uff0c\u5728\u670d\u52a1\u7aef\u901a\u5e38\u90fd\u4f1a\u8d70\u5b8c\u8fd9\u56db\u6b65\uff1a<\/p>\n<pre style=\"display:none;\"><code class=\"mermaid\">flowchart LR\n    A[&quot;Query String&lt;br\/&gt;\u7eaf\u6587\u672c&quot;] --&gt;|\u8bcd\u6cd5 + \u8bed\u6cd5| B[&quot;AST&lt;br\/&gt;\u62bd\u8c61\u8bed\u6cd5\u6811&quot;]\n    B --&gt;|\u5bf9\u7167 Schema| C[&quot;Validated AST&quot;]\n    C --&gt;|\u9012\u5f52\u6c42\u503c| D[&quot;Data Tree&quot;]\n    D --&gt;|\u6309 Query \u5c42\u7ea7\u5e8f\u5217\u5316| E[&quot;JSON Response&quot;]\n\n    B -.-&gt; B1[&quot;Parse&quot;]\n    C -.-&gt; C1[&quot;Validate&quot;]\n    D -.-&gt; D1[&quot;Execute&quot;]\n    E -.-&gt; E1[&quot;Format&quot;]<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th>\u9636\u6bb5<\/th>\n<th>\u8f93\u5165<\/th>\n<th>\u8f93\u51fa<\/th>\n<th>\u6838\u5fc3\u5de5\u4f5c<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>Parse<\/strong><\/td>\n<td>Query \u5b57\u7b26\u4e32<\/td>\n<td>AST<\/td>\n<td>\u8bcd\u6cd5\u5206\u6790\uff08Lexer\uff09+ \u8bed\u6cd5\u5206\u6790\uff08Parser\uff09<\/td>\n<\/tr>\n<tr>\n<td><strong>Validate<\/strong><\/td>\n<td>AST + Schema<\/td>\n<td>\u5df2\u6821\u9a8c AST<\/td>\n<td>\u5b57\u6bb5\u662f\u5426\u5b58\u5728\u3001\u53c2\u6570\u7c7b\u578b\u662f\u5426\u5339\u914d\u3001\u7247\u6bb5\u5faa\u73af\u68c0\u6d4b<\/td>\n<\/tr>\n<tr>\n<td><strong>Execute<\/strong><\/td>\n<td>\u5df2\u6821\u9a8c AST + Resolvers<\/td>\n<td>\u6570\u636e\u6811<\/td>\n<td>\u6309\u5b57\u6bb5\u6811\u9012\u5f52\u6c42\u503c\uff1b\u540c\u5c42 query \u5b57\u6bb5\u901a\u5e38\u5e76\u884c\uff0cmutation \u6839\u5b57\u6bb5\u4e32\u884c<\/td>\n<\/tr>\n<tr>\n<td><strong>Format<\/strong><\/td>\n<td>\u6267\u884c\u7ed3\u679c<\/td>\n<td>JSON<\/td>\n<td>\u5e8f\u5217\u5316 <code>data<\/code> \/ <code>errors<\/code> \/ <code>extensions<\/code>\uff0c\u4fdd\u6301\u54cd\u5e94\u5c42\u7ea7<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>\u4e25\u683c\u8bf4\uff0cGraphQL \u89c4\u8303\u7684\u6838\u5fc3\u7b97\u6cd5\u662f Parse \/ Validate \/ Execute\u3002\u8fd9\u91cc\u628a Format \u5355\u72ec\u5217\u51fa\u6765\uff0c\u662f\u4e3a\u4e86\u628a\u670d\u52a1\u7aef\u6846\u67b6\u6700\u540e\u751f\u6210 HTTP\/JSON \u54cd\u5e94\u7684\u52a8\u4f5c\u4e5f\u7eb3\u5165\u6d41\u6c34\u7ebf\u3002<\/p>\n<p>\u7406\u89e3\u8fd9\u56db\u6b65\u540e\uff0c\u518d\u9605\u8bfb\u4e0d\u540c GraphQL \u670d\u52a1\u7aef\u6846\u67b6\u7684\u6267\u884c\u5165\u53e3\u4f1a\u5bb9\u6613\u5f97\u591a\u3002<\/p>\n<hr>\n<h2>\u4e09\u3001\u6700\u5c0f\u53ef\u8fd0\u884c\u793a\u4f8b\uff1a\u4e00\u4e2a\u5b8c\u6574\u95ed\u73af<\/h2>\n<p>\u5148\u52a8\u624b\u8dd1\u4e00\u904d\u518d\u8bb2\u539f\u7406\uff0c\u6bd4\u53cd\u8fc7\u6765\u5b66\u5f97\u5feb\u5f97\u591a\u3002<\/p>\n<pre><code class=\"bash\">mkdir gql-demo &amp;&amp; cd gql-demo\nnpm init -y\nnpm install graphql\nnpm pkg set type=module<\/code><\/pre>\n<pre><code class=\"javascript\">\/\/ server.mjs \u2014\u2014 GraphQL \u67e5\u8be2\u5904\u7406\u7684\u6700\u5c0f\u95ed\u73af\nimport { graphql, buildSchema } from 'graphql';\n\n\/**\n * 1) \u5b9a\u4e49 Schema\n *    Schema \u662f GraphQL \u7684\u300c\u5951\u7ea6\u300d\uff1a\u5ba2\u6237\u7aef\u80fd\u770b\u5230\u4ec0\u4e48\u3001\u670d\u52a1\u7aef\u80fd\u63d0\u4f9b\u4ec0\u4e48\uff0c\n *    \u90fd\u4ee5\u5b83\u4e3a\u552f\u4e00\u771f\u6e90\uff08Single Source of Truth\uff09\u3002\n *\/\nconst schema = buildSchema(`\n  type User {\n    id: ID!\n    name: String!\n    posts: [Post!]!\n  }\n  type Post {\n    id: ID!\n    title: String!\n    author: User!\n  }\n  type Query {\n    user(id: ID!): User\n    posts: [Post!]!\n  }\n`);\n\n\/**\n * 2) \u6a21\u62df\u6570\u636e\u6e90\n *    \u771f\u5b9e\u573a\u666f\u53ef\u80fd\u662f MySQL \/ Redis \/ \u8fdc\u7aef RPC \/ \u751a\u81f3\u591a\u4e2a\u4e0a\u6e38\u805a\u5408\u3002\n *    GraphQL \u5f15\u64ce\u5b8c\u5168\u4e0d\u5173\u5fc3\u6570\u636e\u4ece\u54ea\u6765\u3002\n *\/\nconst db = {\n  users: {\n    '1': { id: '1', name: 'Alice' },\n    '2': { id: '2', name: 'Bob' },\n  },\n  posts: [\n    { id: 'p1', authorId: '1', title: 'GraphQL \u5165\u95e8' },\n    { id: 'p2', authorId: '1', title: 'Resolver \u8be6\u89e3' },\n    { id: 'p3', authorId: '2', title: '\u7c7b\u578b\u7cfb\u7edf\u8bbe\u8ba1' },\n  ],\n};\n\n\/**\n * 3) Resolver: \u544a\u8bc9\u5f15\u64ce\u300c\u6bcf\u4e2a\u5b57\u6bb5\u600e\u4e48\u53d6\u300d\n *    \u5d4c\u5957\u5b57\u6bb5\u5e38\u7528\u51fd\u6570\uff08\u60f0\u6027\u6c42\u503c\uff09\u2014\u2014\u53ea\u6709\u5ba2\u6237\u7aef query \u5230\u65f6\u624d\u6267\u884c\u3002\n *\/\nconst rootValue = {\n  \/**\n   * \u6839\u5b57\u6bb5: \u6309 id \u67e5\u7528\u6237\n   * @param {{ id: string }} args - \u67e5\u8be2\u53c2\u6570\n   * @returns {object|null} \u7528\u6237\u5bf9\u8c61\uff1bposts \u5b57\u6bb5\u662f\u60f0\u6027\u51fd\u6570\n   *\/\n  user: ({ id }) =&gt; {\n    const u = db.users[id];\n    if (!u) return null;\n    return {\n      ...u,\n      posts: () =&gt; db.posts.filter(p =&gt; p.authorId === id),\n    };\n  },\n  \/**\n   * \u6839\u5b57\u6bb5: \u6240\u6709\u6587\u7ae0\n   * @returns {Array&lt;object&gt;} \u6587\u7ae0\u5217\u8868\uff0c\u6bcf\u7bc7\u9644\u5e26 author \u60f0\u6027\u89e3\u6790\n   *\/\n  posts: () =&gt; db.posts.map(p =&gt; ({\n    ...p,\n    author: () =&gt; db.users[p.authorId],\n  })),\n};\n\n\/**\n * 4) \u5ba2\u6237\u7aef\u67e5\u8be2: \u53ea\u7d22\u8981\u9700\u8981\u7684\u5b57\u6bb5\uff08\u51cf\u5c11 over-fetching \/ under-fetching\uff09\n *\/\nconst query = `\n  query GetUserWithPosts {\n    user(id: &quot;1&quot;) {\n      name\n      posts { title }\n    }\n  }\n`;\n\n\/**\n * 5) \u6267\u884c: graphql() \u5185\u90e8\u5b8c\u6210 parse \u2192 validate \u2192 execute\uff0c\u5e76\u8fd4\u56de\u53ef\u5e8f\u5217\u5316\u7684\u7ed3\u679c\n *\/\nconst result = await graphql({ schema, source: query, rootValue });\nconsole.log(JSON.stringify(result, null, 2));<\/code><\/pre>\n<p>\u8fd0\u884c <code>node server.mjs<\/code>\uff1a<\/p>\n<pre><code class=\"json\">{\n  &quot;data&quot;: {\n    &quot;user&quot;: {\n      &quot;name&quot;: &quot;Alice&quot;,\n      &quot;posts&quot;: [\n        { &quot;title&quot;: &quot;GraphQL \u5165\u95e8&quot; },\n        { &quot;title&quot;: &quot;Resolver \u8be6\u89e3&quot; }\n      ]\n    }\n  }\n}<\/code><\/pre>\n<p><strong>\u5173\u952e\u89c2\u5bdf<\/strong>\uff1a\u54cd\u5e94\u91cc\u6ca1\u6709 <code>id<\/code>\u3001\u6ca1\u6709 <code>author<\/code>\u2014\u2014\u56e0\u4e3a\u5ba2\u6237\u7aef\u6ca1\u8981\u3002\u54cd\u5e94\u7684\u5f62\u72b6\u4e0e Query \u7684\u5f62\u72b6<strong>\u9010\u5b57\u6bb5\u5bf9\u5e94<\/strong>\u3002<\/p>\n<hr>\n<h2>\u56db\u3001Parse \u9636\u6bb5\uff1a\u5b57\u7b26\u4e32\u5982\u4f55\u53d8\u6210 AST<\/h2>\n<p>Parse \u4e0e\u4f60\u719f\u6089\u7684 JS\/TS \u7f16\u8bd1\u5668\u524d\u7aef\u4e00\u6a21\u4e00\u6837\u2014\u2014\u5148\u8bcd\u6cd5\uff0c\u518d\u8bed\u6cd5\u3002<\/p>\n<p><strong>\u8bcd\u6cd5\uff08Lexer\uff09<\/strong> \u628a\u5b57\u7b26\u6d41\u5207\u6210 Token\uff1a<\/p>\n<pre><code>&quot;query { user(id: \/&quot;1\/&quot;) { name } }&quot;\n      \u2193\n[QUERY] [BRACE_L] [NAME:user] [PAREN_L] [NAME:id] [COLON] [STRING:1] [PAREN_R] [BRACE_L] [NAME:name] [BRACE_R] [BRACE_R]<\/code><\/pre>\n<p><strong>\u8bed\u6cd5\uff08Parser\uff09<\/strong> \u6309 GraphQL \u8bed\u6cd5\u89c4\u5219\u628a Token \u6d41\u6784\u9020\u6210 AST\uff1a<\/p>\n<pre><code class=\"javascript\">\/**\n * \u89c2\u5bdf Parse \u9636\u6bb5\u7684\u4ea7\u51fa\n * \u76f4\u63a5\u8c03\u7528 graphql-js \u5e95\u5c42\u7684 parse\uff0c\u4e0d\u6d89\u53ca Schema\n *\/\nimport { parse } from 'graphql';\n\nconst ast = parse(`\n  query GetUser {\n    user(id: &quot;1&quot;) { name }\n  }\n`);\n\nconsole.log(JSON.stringify(ast, null, 2));<\/code><\/pre>\n<p>\u8282\u9009\u8f93\u51fa\uff08\u7ed3\u6784\u975e\u5e38\u89c4\u6574\uff09\uff1a<\/p>\n<pre><code class=\"json\">{\n  &quot;kind&quot;: &quot;Document&quot;,\n  &quot;definitions&quot;: [{\n    &quot;kind&quot;: &quot;OperationDefinition&quot;,\n    &quot;operation&quot;: &quot;query&quot;,\n    &quot;name&quot;: { &quot;kind&quot;: &quot;Name&quot;, &quot;value&quot;: &quot;GetUser&quot; },\n    &quot;selectionSet&quot;: {\n      &quot;kind&quot;: &quot;SelectionSet&quot;,\n      &quot;selections&quot;: [{\n        &quot;kind&quot;: &quot;Field&quot;,\n        &quot;name&quot;: { &quot;value&quot;: &quot;user&quot; },\n        &quot;arguments&quot;: [{\n          &quot;kind&quot;: &quot;Argument&quot;,\n          &quot;name&quot;: { &quot;value&quot;: &quot;id&quot; },\n          &quot;value&quot;: { &quot;kind&quot;: &quot;StringValue&quot;, &quot;value&quot;: &quot;1&quot; }\n        }],\n        &quot;selectionSet&quot;: {\n          &quot;selections&quot;: [{ &quot;kind&quot;: &quot;Field&quot;, &quot;name&quot;: { &quot;value&quot;: &quot;name&quot; } }]\n        }\n      }]\n    }\n  }]\n}<\/code><\/pre>\n<p><strong>\u8981\u70b9<\/strong>\uff1aParse \u53ea\u5173\u5fc3\u8bed\u6cd5\u5408\u6cd5\u6027\uff0c<strong>\u5b8c\u5168\u4e0d\u770b Schema<\/strong>\u3002\u4f60\u5199 <code>user(id: &quot;1&quot;) { xxxNotExist }<\/code> \u4e5f\u80fd parse \u6210\u529f\u2014\u2014\u8fd9\u4e2a\u9519\u8bef\u7531\u4e0b\u4e00\u9636\u6bb5\u6293\u3002<\/p>\n<hr>\n<h2>\u4e94\u3001Validate \u9636\u6bb5\uff1aAST \u4e0e Schema \u7684\u7c7b\u578b\u63e1\u624b<\/h2>\n<p>Validate \u7528\u4e00\u7ec4 <strong>\u89c4\u5219\u96c6\uff08Validation Rules\uff09<\/strong> \u904d\u5386 AST\uff0c\u628a\u6bcf\u4e2a\u5b57\u6bb5\/\u53c2\u6570\/\u7247\u6bb5\u4e0e Schema \u5bf9\u9f50\uff1a<\/p>\n<pre><code class=\"javascript\">\/**\n * \u89c2\u5bdf Validate \u9636\u6bb5\n * \u4f20\u5165\u9519\u8bef\u7684\u5b57\u6bb5\u540d\uff0c\u770b\u5f15\u64ce\u8fd4\u56de\u4ec0\u4e48\n *\/\nimport { parse, validate, buildSchema } from 'graphql';\n\nconst schema = buildSchema(`\n  type Query { user(id: ID!): User }\n  type User { name: String! }\n`);\n\nconst badAst = parse(`{ user(id: &quot;1&quot;) { nam } }`); \/\/ nam \u662f\u62fc\u5199\u9519\u8bef\nconst errors = validate(schema, badAst);\nconsole.log(errors.map(e =&gt; e.message));\n\/\/ [ 'Cannot query field &quot;nam&quot; on type &quot;User&quot;. Did you mean &quot;name&quot;?' ]<\/code><\/pre>\n<p>\u5185\u7f6e\u89c4\u5219\u6db5\u76d6\uff1a<\/p>\n<ul>\n<li><strong>\u5b57\u6bb5\u5b58\u5728\u6027<\/strong>\uff1a<code>Cannot query field &quot;nam&quot; on type &quot;User&quot;<\/code><\/li>\n<li><strong>\u53c2\u6570\u7c7b\u578b<\/strong>\uff1a\u4f8b\u5982\u628a\u5e03\u5c14\u503c\u4f20\u7ed9 <code>ID!<\/code>\uff0c\u4f1a\u5f97\u5230 <code>ID cannot represent a non-string and non-integer value: true<\/code><\/li>\n<li><strong>\u5fc5\u586b\u53c2\u6570<\/strong>\uff1a<code>Field &quot;user&quot; argument &quot;id&quot; of type &quot;ID!&quot; is required<\/code><\/li>\n<li><strong>\u7247\u6bb5\u5faa\u73af<\/strong>\uff1a\u9632\u6b62 <code>fragment A on X { ...B }<\/code> + <code>fragment B on X { ...A }<\/code> \u65e0\u9650\u9012\u5f52<\/li>\n<li><strong>\u53d8\u91cf\u4f7f\u7528<\/strong>\uff1a\u58f0\u660e\u7684 <code>$foo<\/code> \u5fc5\u987b\u88ab\u7528\u5230\uff0c\u4e14\u7c7b\u578b\u5339\u914d<\/li>\n<\/ul>\n<p>\u9700\u8981\u6ce8\u610f\uff0cValidate \u53ea\u80fd\u68c0\u67e5\u6587\u6863\u672c\u8eab\u3002\u4e0b\u9762\u7684\u67e5\u8be2\u53ef\u4ee5\u901a\u8fc7\u9759\u6001\u6821\u9a8c\uff0c\u56e0\u4e3a <code>$id<\/code> \u7684\u58f0\u660e\u7c7b\u578b\u4e0e\u4f7f\u7528\u4f4d\u7f6e\u517c\u5bb9\uff1b\u8bf7\u6c42\u4f20\u5165\u7684\u5b9e\u9645\u53d8\u91cf\u503c\u8981\u5230\u6267\u884c\u51c6\u5907\u9636\u6bb5\u624d\u80fd\u8f6c\u6362\uff1a<\/p>\n<pre><code class=\"graphql\">query GetUser($id: ID!) {\n  user(id: $id) { name }\n}<\/code><\/pre>\n<p>\u5982\u679c <code>variables<\/code> \u662f <code>{ &quot;id&quot;: true }<\/code>\uff0c\u6267\u884c\u5b57\u6bb5\u524d\u5c31\u4f1a\u8fd4\u56de <code>Variable &quot;$id&quot; got invalid value true<\/code>\u3002\u56e0\u6b64\uff0c<strong>Validate \u901a\u8fc7\u53ea\u8868\u793a\u67e5\u8be2\u6587\u6863\u5728\u9759\u6001\u7ed3\u6784\u4e0a\u4e0e Schema \u5bf9\u9f50<\/strong>\uff0c\u4e0d\u4ee3\u8868\u672c\u6b21\u8bf7\u6c42\u7684\u8fd0\u884c\u65f6\u8f93\u5165\u4e00\u5b9a\u6709\u6548\u3002<\/p>\n<p>\u8fdb\u5165 Execute \u540e\uff0c\u5f15\u64ce\u8fd8\u8981\u9009\u62e9 operation\u3001\u8f6c\u6362\u53d8\u91cf\u548c\u53c2\u6570\u3001\u8c03\u7528 Resolver\u3001\u5e8f\u5217\u5316\u6807\u91cf\u3001\u5b8c\u6210 list \/ non-null \u503c\u3001\u89e3\u6790 interface \/ union \u7684\u5177\u4f53\u7c7b\u578b\uff0c\u5e76\u5728\u51fa\u9519\u65f6\u6309\u7a7a\u503c\u4f20\u64ad\u89c4\u5219\u751f\u6210 <code>data<\/code> \u548c <code>errors<\/code>\u3002<\/p>\n<hr>\n<h2>\u516d\u3001Execute \u9636\u6bb5\uff1a\u9012\u5f52\u8c03\u5ea6 Resolver<\/h2>\n<p>\u8fd9\u662f GraphQL \u7684\u5fc3\u810f\u3002\u4e0b\u9762\u7684\u8ff7\u4f60\u6267\u884c\u5668\u53ea\u4fdd\u7559\u300c\u904d\u5386\u5b57\u6bb5\u3001\u89e3\u6790\u5b57\u6bb5\u503c\u3001\u9012\u5f52\u5b8c\u6210\u5b50\u9009\u62e9\u96c6\u300d\u8fd9\u526f\u9aa8\u67b6\uff0c\u5e2e\u52a9\u7406\u89e3\u63a7\u5236\u6d41\uff1b\u5b83\u4e0d\u662f GraphQL \u89c4\u8303\u6267\u884c\u5668\u7684\u66ff\u4ee3\u5b9e\u73b0\u3002<\/p>\n<pre><code class=\"javascript\">\/**\n * \u8ff7\u4f60 GraphQL \u6267\u884c\u5668: \u6f14\u793a Execute \u9636\u6bb5\u7684\u6838\u5fc3\u9012\u5f52\n * @param {object} selection - \u5df2\u89e3\u6790\u7684\u5b57\u6bb5\u9009\u62e9(AST \u7b80\u5316\u7248)\n * @param {object} resolvers - \u5f53\u524d\u5c42\u7684\u5b57\u6bb5\u540d \u2192 resolver \u51fd\u6570\n * @param {*}      parent    - \u4e0a\u4e00\u5c42\u5b57\u6bb5\u8fd4\u56de\u7684\u5bf9\u8c61\uff0c\u4f5c\u4e3a\u4e0b\u5c42 resolver \u7684 source\n * @param {object} context   - \u8bf7\u6c42\u7ea7\u5171\u4eab\u4e0a\u4e0b\u6587\n * @returns {Promise&lt;object&gt;} \u6309 selection \u5f62\u72b6\u7ec4\u88c5\u51fa\u7684\u6570\u636e\n *\/\nasync function execute(selection, resolvers, parent = null, context = {}) {\n  const fields = Object.entries(selection).map(async ([responseKey, sub]) =&gt; {\n    const fieldName = sub.field ?? responseKey;\n\n    \/\/ \u6a21\u62df graphql-js \u7684\u9ed8\u8ba4\u5b57\u6bb5\u89e3\u6790\uff1a\u8bfb\u53d6\u540c\u540d\u5c5e\u6027\uff1b\u5982\u679c\u662f\u51fd\u6570\u5219\u8c03\u7528\u5b83\u3002\n    const defaultResolver = (source, args, ctx) =&gt; {\n      const property = source?.[fieldName];\n      return typeof property === 'function'\n        ? property.call(source, args, ctx)\n        : property;\n    };\n\n    const resolver = resolvers[fieldName] ?? defaultResolver;\n    const value = await resolver(parent, sub.args ?? {}, context);\n\n    if (!sub.children || value == null) {\n      return [responseKey, value];\n    }\n\n    const completed = Array.isArray(value)\n      ? await Promise.all(value.map(item =&gt;\n          item == null ? null : execute(sub.children, {}, item, context)))\n      : await execute(sub.children, {}, value, context);\n\n    return [responseKey, completed];\n  });\n\n  return Object.fromEntries(await Promise.all(fields));\n}\n\n\/**\n * \u6a21\u62df\u4e00\u6761\u5df2\u7ecf parse+validate \u5b8c\u7684 query:\n *   { user(id: &quot;1&quot;) { name, posts { title } } }\n *\/\nconst selection = {\n  user: {\n    args: { id: '1' },\n    children: {\n      name: {},\n      posts: { children: { title: {} } },\n    },\n  },\n};\n\nconst resolvers = {\n  user: (_parent, { id }) =&gt; ({\n    name: 'Alice',\n    posts: [{ title: 'GraphQL \u5165\u95e8' }, { title: 'Resolver \u8be6\u89e3' }],\n  }),\n};\n\nconsole.log(JSON.stringify(await execute(selection, resolvers), null, 2));<\/code><\/pre>\n<p>\u771f\u5b9e\u7684 <code>graphql-js<\/code> \u6267\u884c\u5668\u8fd8\u4f1a\u5904\u7406\uff1a<\/p>\n<ol>\n<li><strong>\u5b57\u6bb5\u6536\u96c6<\/strong>\uff1a\u5904\u7406 alias\u3001Fragment\u3001<code>@skip<\/code> \/ <code>@include<\/code>\uff0c\u5e76\u6309 response key \u5408\u5e76\u5b57\u6bb5\u3002<\/li>\n<li><strong>\u503c\u5b8c\u6210<\/strong>\uff1a\u7b49\u5f85 Promise\uff0c\u5e8f\u5217\u5316\u6807\u91cf\uff0c\u9012\u5f52\u5904\u7406\u5bf9\u8c61\u3001\u5217\u8868\u3001interface \u548c union\u3002<\/li>\n<li><strong>\u6267\u884c\u987a\u5e8f<\/strong>\uff1aQuery \u7684\u5144\u5f1f\u5b57\u6bb5\u53ef\u4ee5\u5e76\u53d1\u5b8c\u6210\uff1b\u53ea\u6709 Mutation \u7684\u9876\u5c42\u5b57\u6bb5\u6309\u6587\u6863\u987a\u5e8f\u4e32\u884c\u6267\u884c\uff0c\u5d4c\u5957\u5b57\u6bb5\u4ecd\u6309\u666e\u901a\u89c4\u5219\u6267\u884c\u3002<\/li>\n<li><strong>\u9519\u8bef\u4f20\u64ad<\/strong>\uff1a\u53ef\u7a7a\u5b57\u6bb5\u5931\u8d25\u65f6\u5728\u5f53\u524d\u4f4d\u7f6e\u8fd4\u56de <code>null<\/code>\uff1b\u975e\u7a7a\u5b57\u6bb5\u5931\u8d25\u65f6\u5411\u6700\u8fd1\u7684\u53ef\u7a7a\u7236\u7ea7\u5192\u6ce1\u3002<\/li>\n<li><strong>\u4e0a\u4e0b\u6587\u4f20\u9012<\/strong>\uff1a\u4f7f\u7528 <code>(parent, args, context, info)<\/code> \u7b7e\u540d\uff0c<code>context<\/code> \u901a\u5e38\u4fdd\u5b58\u8ba4\u8bc1\u4fe1\u606f\u548c DataLoader \u5b9e\u4f8b\u3002<\/li>\n<\/ol>\n<p>\u8fd9\u4e5f\u662f\u4e3a\u4ec0\u4e48 GraphQL \u7684\u9519\u8bef\u54cd\u5e94\u53ef\u80fd\u540c\u65f6\u5305\u542b <code>data<\/code> \u548c <code>errors<\/code>\uff1a\u4ecd\u7136\u6709\u6548\u7684\u5206\u652f\u53ef\u4ee5\u51fa\u73b0\u5728 <code>data<\/code> \u4e2d\uff0c\u5931\u8d25\u4f4d\u7f6e\u901a\u8fc7 <code>errors[].path<\/code> \u6807\u51fa\uff1b\u5982\u679c\u5931\u8d25\u5b57\u6bb5\u662f <code>Non-Null<\/code>\uff0c<code>null<\/code> \u4f1a\u7ee7\u7eed\u5411\u4e0a\u5192\u6ce1\u5230\u6700\u8fd1\u7684\u53ef\u7a7a\u7236\u5b57\u6bb5\u3002<\/p>\n<h3>Non-Null \u662f\u600e\u6837\u5192\u6ce1\u7684<\/h3>\n<p>\u5047\u8bbe <code>Query.user<\/code> \u53ef\u7a7a\uff0c\u800c <code>User.name<\/code> \u975e\u7a7a\uff1a<\/p>\n<pre><code class=\"graphql\">type Query { user: User }\ntype User { name: String! }<\/code><\/pre>\n<p>\u5982\u679c <code>user<\/code> Resolver \u8fd4\u56de <code>{ name: null }<\/code>\uff0c<code>name<\/code> \u4e0d\u80fd\u4fdd\u7559\u4e3a <code>null<\/code>\uff0c\u9519\u8bef\u4f1a\u4f7f\u6574\u4e2a <code>user<\/code> \u5206\u652f\u53d8\u6210 <code>null<\/code>\uff1a<\/p>\n<pre><code class=\"json\">{\n  &quot;data&quot;: { &quot;user&quot;: null },\n  &quot;errors&quot;: [{\n    &quot;message&quot;: &quot;Cannot return null for non-nullable field User.name.&quot;,\n    &quot;path&quot;: [&quot;user&quot;, &quot;name&quot;]\n  }]\n}<\/code><\/pre>\n<p>\u5982\u679c <code>Query.user<\/code> \u4e5f\u662f <code>User!<\/code>\uff0c\u5192\u6ce1\u4f1a\u7ee7\u7eed\u5230\u6839\u8282\u70b9\uff0c\u6b64\u65f6\u6574\u4e2a <code>data<\/code> \u4e3a <code>null<\/code>\u3002Non-Null \u8868\u8fbe\u7684\u4e0d\u53ea\u662f\u300c\u8fd9\u4e2a\u5b57\u6bb5\u901a\u5e38\u6709\u503c\u300d\uff0c\u8fd8\u51b3\u5b9a\u4e86\u8fd0\u884c\u65f6\u9519\u8bef\u53ef\u4ee5\u5f71\u54cd\u591a\u5927\u7684\u54cd\u5e94\u8303\u56f4\u3002<\/p>\n<hr>\n<h2>\u4e03\u3001Resolver \u7684\u5b8c\u6574\u7b7e\u540d\uff1a<code>(parent, args, context, info)<\/code><\/h2>\n<p>\u751f\u4ea7\u9879\u76ee\u901a\u5e38\u4f1a\u6309\u7c7b\u578b\u7ec4\u7ec7 Resolver\u3002\u4e0b\u9762\u662f Apollo Server\u3001GraphQL Yoga\u3001GraphQL Tools \u7b49\u5de5\u5177\u5e38\u89c1\u7684 Resolver Map \u5f62\u72b6\uff1b\u5b83\u9700\u8981\u7531\u76f8\u5e94\u6846\u67b6\u7ed1\u5b9a\u5230 Schema\uff0c\u5e76\u4e0d\u662f\u628a\u5bf9\u8c61\u58f0\u660e\u51fa\u6765\u5c31\u4f1a\u81ea\u52a8\u751f\u6548\u3002JavaScript \u51fd\u6570\u4e0d\u8981\u6c42\u663e\u5f0f\u58f0\u660e\u6240\u6709\u53c2\u6570\uff0c\u53ef\u4ee5\u53ea\u4fdd\u7559\u5b9e\u9645\u4f7f\u7528\u7684\u53c2\u6570\u3002<\/p>\n<pre><code class=\"javascript\">\/**\n * \u5b8c\u6574\u7b7e\u540d\u7684 Resolver\n * @param {*} parent - \u7236\u5b57\u6bb5\u8fd4\u56de\u7684\u5bf9\u8c61\uff08\u9876\u5c42\u5b57\u6bb5\u662f rootValue\uff09\n * @param {object} args - \u5ba2\u6237\u7aef\u4f20\u5165\u7684\u53c2\u6570\uff0c\u5df2\u6309 Schema \u7c7b\u578b\u8f6c\u6362\n * @param {object} context - \u6bcf\u6b21\u8bf7\u6c42\u7684\u4e0a\u4e0b\u6587\uff08\u8ba4\u8bc1\/DB \u8fde\u63a5\/DataLoader\uff09\n * @param {object} info - \u5f53\u524d\u5b57\u6bb5\u7684 AST \u4e0e\u6267\u884c\u5143\u4fe1\u606f\uff08\u9ad8\u7ea7\u7528\u6cd5\uff09\n *\/\nconst resolvers = {\n  Query: {\n    user: async (_parent, { id }, ctx, _info) =&gt; {\n      if (!ctx.user) throw new Error('Unauthorized');\n      return ctx.db.user.findUnique({ where: { id } });\n    },\n  },\n  User: {\n    \/\/ \u5d4c\u5957 Resolver: parent \u5c31\u662f User \u5bf9\u8c61\n    posts: async (user, _args, ctx) =&gt; {\n      return ctx.loaders.postsByUser.load(user.id); \/\/ DataLoader \u6279\u91cf\u53d6\n    },\n  },\n};<\/code><\/pre>\n<p><strong>context \u662f\u8de8 Resolver \u5171\u4eab\u8bf7\u6c42\u7ea7\u72b6\u6001\u7684\u6807\u51c6\u901a\u9053<\/strong>\u3002\u8ba4\u8bc1\u4fe1\u606f\u3001\u8bf7\u6c42\u7ea7 DataLoader \u548c\u8ffd\u8e2a\u4fe1\u606f\u9002\u5408\u653e\u5728\u8fd9\u91cc\uff1b\u4e0d\u8981\u628a\u8bf7\u6c42\u76f8\u5173\u7684\u53ef\u53d8\u72b6\u6001\u653e\u8fdb\u6a21\u5757\u7ea7\u5168\u5c40\u53d8\u91cf\uff0c\u5426\u5219\u5e76\u53d1\u8bf7\u6c42\u53ef\u80fd\u4e92\u76f8\u6c61\u67d3\u3002<\/p>\n<hr>\n<h2>\u516b\u3001N+1 \u95ee\u9898\u4e0e DataLoader<\/h2>\n<p>\u8fd9\u662f GraphQL \u751f\u4ea7\u73af\u5883\u5f88\u5e38\u89c1\u7684\u5751\u3002\u770b\u8fd9\u6761\u67e5\u8be2\uff1a<\/p>\n<pre><code class=\"graphql\">{\n  posts {         # 1 \u6b21 SQL: SELECT * FROM posts\n    title\n    author {      # N \u6b21 SQL: \u6bcf\u7bc7 post \u4e00\u6b21 SELECT * FROM users WHERE id=?\n      name\n    }\n  }\n}<\/code><\/pre>\n<p>100 \u7bc7\u6587\u7ae0 = <strong>1 + 100 = 101 \u6b21\u6570\u636e\u5e93\u67e5\u8be2<\/strong>\u3002\u8fd9\u5c31\u662f N+1 \u95ee\u9898\u7684\u7531\u6765\u2014\u2014GraphQL \u7684\u5b57\u6bb5\u9012\u5f52\u5982\u679c\u7167\u7740\u6bcf\u4e2a\u5173\u7cfb\u5b57\u6bb5\u5355\u70b9\u67e5\uff0c\u5f88\u5bb9\u6613\u5c55\u5f00\u6210\u5927\u91cf\u5c0f\u67e5\u8be2\u3002<\/p>\n<p>\u5e38\u89c1\u89e3\u51b3\u65b9\u6848\u662f <a href=\"https:\/\/link.segmentfault.com\/?enc=3wd0YQ19nifssvNRKNtR3A%3D%3D.vNX50jk7kYKO0SqelxWyczFFrgGreyvzrZWqBVQNv38Vc%2BuDSXwCFMN7gs7vaQYG\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">DataLoader<\/a>\uff1a<strong>\u6536\u96c6\u540c\u4e00\u6279\u6b21\u4e2d\u7684 <code>.load(id)<\/code> \u8c03\u7528\uff0c\u5408\u5e76\u6210\u4e00\u6b21\u6279\u91cf\u67e5\u8be2\uff0c\u5e76\u5728\u5f53\u524d\u8bf7\u6c42\u5185\u7f13\u5b58\u76f8\u540c key \u7684\u7ed3\u679c<\/strong>\u3002\u5b83\u4e0d\u662f\u552f\u4e00\u65b9\u6848\uff1bSQL join\u3001ORM relation preload\u3001\u67e5\u8be2\u89c4\u5212\u5668\u6216\u4e0a\u6e38\u6279\u91cf API \u4e5f\u53ef\u4ee5\u4ece\u6570\u636e\u8bbf\u95ee\u5c42\u6d88\u9664 N+1\u3002<\/p>\n<pre><code class=\"javascript\">import DataLoader from 'dataloader';\n\n\/**\n * \u4e3a\u6bcf\u6b21\u8bf7\u6c42\u521b\u5efa\u72ec\u7acb\u7684 DataLoader\uff08\u907f\u514d\u8de8\u8bf7\u6c42\u7f13\u5b58\u6c61\u67d3\uff09\n * @param {object} db - \u6570\u636e\u6e90\n * @returns {object} loader \u96c6\u5408\uff0c\u6302\u5230 context \u4e0a\n *\/\nfunction makeLoaders(db) {\n  return {\n    \/**\n     * \u6309\u7528\u6237 id \u6279\u91cf\u52a0\u8f7d\n     * DataLoader \u4fdd\u8bc1: \u540c\u4e00\u6279\u8c03\u5ea6\u5468\u671f\u5185\u591a\u6b21 load(id) \u4f1a\u5408\u5e76\u6210\u4e00\u6b21 batchFn \u8c03\u7528\n     * \u8fd4\u56de\u987a\u5e8f\u5fc5\u987b\u4e0e keys \u4e00\u4e00\u5bf9\u5e94\n     *\/\n    userById: new DataLoader(async (userIds) =&gt; {\n      const users = await db.users.findMany({ where: { id: { in: userIds } } });\n      const map = new Map(users.map(u =&gt; [u.id, u]));\n      return userIds.map(id =&gt; map.get(id) ?? null);\n    }),\n  };\n}\n\n\/\/ Resolver \u4e2d\u4f7f\u7528\nconst resolvers = {\n  Post: {\n    author: (post, _args, ctx) =&gt; ctx.loaders.userById.load(post.authorId),\n  },\n};\n\n\/\/ \u670d\u52a1\u7aef\u6846\u67b6\u5e94\u5728\u6bcf\u4e2a\u8bf7\u6c42\u5f00\u59cb\u65f6\u8c03\u7528\u4e00\u6b21\uff0c\u518d\u628a\u7ed3\u679c\u4f5c\u4e3a contextValue \u4f20\u5165\u3002\nfunction createContext(request) {\n  return {\n    db,\n    loaders: makeLoaders(db),\n    user: request.user,\n  };\n}<\/code><\/pre>\n<p>\u5728\u8fd9\u4e2a\u4f8b\u5b50\u91cc\uff0c101 \u6b21\u67e5\u8be2\u53ef\u4ee5\u964d\u4e3a <strong>1 + 1 = 2 \u6b21<\/strong>\u3002\u662f\u5426\u9700\u8981 DataLoader\uff0c\u53d6\u51b3\u4e8e Resolver \u4e0e\u6570\u636e\u6e90\u7684\u8bbf\u95ee\u65b9\u5f0f\uff1b\u9700\u8981\u4f7f\u7528\u65f6\uff0c\u5e94\u9ed8\u8ba4\u6309\u8bf7\u6c42\u521b\u5efa\u5b9e\u4f8b\uff0c\u907f\u514d\u7f13\u5b58\u8de8\u7528\u6237\u6216\u8de8\u8bf7\u6c42\u6cc4\u6f0f\u3002<\/p>\n<hr>\n<h2>\u4e5d\u3001Fragment \u4e0e Directive\uff1a\u590d\u7528\u4e0e\u6761\u4ef6<\/h2>\n<p><strong>Fragment\uff08\u7247\u6bb5\uff09<\/strong> \u7528\u4e8e\u590d\u7528\u5b57\u6bb5\u9009\u62e9\uff1a<\/p>\n<pre><code class=\"graphql\">fragment UserBasic on User {\n  id\n  name\n  avatar\n}\n\nquery {\n  me { ...UserBasic }\n  friends { ...UserBasic }\n}<\/code><\/pre>\n<p>Fragment \u66f4\u50cf\u53ef\u590d\u7528\u7684 selection set\u3002\u6267\u884c\u524d\u6536\u96c6\u5b57\u6bb5\u65f6\uff0c\u5f15\u64ce\u4f1a\u628a <code>...UserBasic<\/code> \u5c55\u5f00\uff0c\u5e76\u6309 response key \u5408\u5e76\u540c\u540d\u5b57\u6bb5\u3002\u5b83\u4e0d\u662f\u7f13\u5b58\u6216\u9884\u53d6\u673a\u5236\uff0c\u4e0d\u4f1a\u5929\u7136\u964d\u4f4e\u67e5\u8be2\u6210\u672c\uff1b\u4e3b\u8981\u4ef7\u503c\u662f\u590d\u7528\u5b57\u6bb5\u9009\u62e9\u3001\u7edf\u4e00\u7ec4\u4ef6\u4f9d\u8d56\u548c\u964d\u4f4e\u7ef4\u62a4\u6210\u672c\u3002<\/p>\n<p><strong>Directive\uff08\u6307\u4ee4\uff09<\/strong> \u7528\u4e8e\u6761\u4ef6\u6027\u5305\u542b\u5b57\u6bb5\uff1a<\/p>\n<pre><code class=\"graphql\">query GetUser($withPosts: Boolean!) {\n  user(id: &quot;1&quot;) {\n    name\n    posts @include(if: $withPosts) { title }\n  }\n}<\/code><\/pre>\n<p>\u5185\u7f6e\u7684 <code>@include<\/code> \u4e0e <code>@skip<\/code> \u4f1a\u5728\u5b57\u6bb5\u6536\u96c6\u9636\u6bb5\u51b3\u5b9a\u67d0\u4e2a\u5b57\u6bb5\u662f\u5426\u8fdb\u5165\u672c\u6b21\u6267\u884c\u3002\u81ea\u5b9a\u4e49 Directive \u8981\u5206\u6e05\u4e24\u7c7b\uff1a<code>@deprecated<\/code> \u8fd9\u7c7b Schema \u5143\u4fe1\u606f\u4e0d\u53c2\u4e0e\u6bcf\u6b21\u6267\u884c\uff1b<code>@auth<\/code> \u8fd9\u7c7b\u8fd0\u884c\u65f6\u89c4\u5219\u901a\u5e38\u9700\u8981\u6846\u67b6\u63d2\u4ef6\u3001schema transform \u6216 resolver wrapper \u4ecb\u5165\u3002<\/p>\n<hr>\n<p>\u5230\u8fd9\u91cc\uff0cParse \u2192 Validate \u2192 Execute \u7684\u6838\u5fc3\u94fe\u8def\u5df2\u7ecf\u95ed\u73af\u3002\u4e0b\u9762\u7684 Subscription\u3001\u6280\u672f\u9009\u578b\u548c\u751f\u4ea7\u9632\u62a4\u5c5e\u4e8e\u8fd9\u5957\u6267\u884c\u6a21\u578b\u4e4b\u4e0a\u7684\u5de5\u7a0b\u6269\u5c55\uff0c\u53ef\u4ee5\u6309\u9700\u9605\u8bfb\u3002<\/p>\n<hr>\n<h2>\u5341\u3001Subscription\uff1a\u4ece\u300c\u8bf7\u6c42-\u54cd\u5e94\u300d\u5230\u300c\u63a8\u9001\u6d41\u300d<\/h2>\n<p>Query \u4e0e Mutation \u901a\u5e38\u4ea7\u751f\u4e00\u6b21\u6267\u884c\u7ed3\u679c\uff1bSubscription \u5219\u5148\u521b\u5efa\u4e8b\u4ef6\u6d41\uff0c\u518d\u5bf9\u6d41\u4e2d\u7684\u6bcf\u4e2a\u4e8b\u4ef6\u6267\u884c\u9009\u62e9\u96c6\u3002GraphQL \u6267\u884c\u5c42\u66b4\u9732\u7684\u662f <code>AsyncIterable<\/code>\uff0cWebSocket \u6216 SSE \u957f\u8fde\u63a5\u7531\u4f20\u8f93\u5c42\u8d1f\u8d23\u3002<\/p>\n<pre><code class=\"javascript\">import { createPubSub } from 'graphql-yoga';\n\nconst pubsub = createPubSub();\n\nconst resolvers = {\n  Mutation: {\n    \/**\n     * \u53d1\u6d88\u606f: \u540c\u65f6\u5411\u8ba2\u9605\u901a\u9053\u63a8\u9001\n     *\/\n    sendMessage: (_p, { text }, _ctx) =&gt; {\n      const msg = { id: crypto.randomUUID(), text, ts: Date.now() };\n      pubsub.publish('MESSAGE_ADDED', { messageAdded: msg });\n      return msg;\n    },\n  },\n  Subscription: {\n    messageAdded: {\n      \/**\n       * subscribe \u8fd4\u56de\u4e00\u4e2a AsyncIterable\n       * \u5f15\u64ce\u6d88\u8d39\u4e8b\u4ef6\u5e76\u751f\u6210\u6267\u884c\u7ed3\u679c\uff0c\u518d\u7531\u4f20\u8f93\u5c42\u53d1\u9001\u7ed9\u5ba2\u6237\u7aef\n       *\/\n      subscribe: () =&gt; pubsub.subscribe('MESSAGE_ADDED'),\n    },\n  },\n};<\/code><\/pre>\n<p>WebSocket \u573a\u666f\u5e38\u7528 <a href=\"https:\/\/link.segmentfault.com\/?enc=9RySXsTjArwvBqiBruc2xQ%3D%3D.e0rmcEw4qE3mw6wfQBNFyIfkLQ9IVfTeLocGlduUSK%2B3sdBVIqOP8jFq0LPgJOGx\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><code>graphql-ws<\/code><\/a> \u5b9e\u73b0 <code>graphql-transport-ws<\/code>\uff0c\u90e8\u5206\u6846\u67b6\u4e5f\u652f\u6301 GraphQL over SSE\u3002\u4f20\u8f93\u65b9\u6848\u4e0d\u6539\u53d8 Subscription \u7684\u6838\u5fc3\u6a21\u578b\uff1a\u8ba2\u9605\u6839\u5b57\u6bb5\u5148\u4ea7\u751f\u4e8b\u4ef6\u6d41\uff0c\u6bcf\u6b21\u4e8b\u4ef6\u518d\u4ee5 payload \u4e3a\u8d77\u70b9\u6267\u884c\u8be5\u5b57\u6bb5\u4e0b\u9762\u7684\u9009\u62e9\u96c6\u3002<\/p>\n<hr>\n<h2>\u5341\u4e00\u3001\u751f\u6001\u9009\u578b\u5bf9\u6bd4\uff1aGraphQL vs REST vs tRPC<\/h2>\n<table>\n<thead>\n<tr>\n<th>\u7ef4\u5ea6<\/th>\n<th>GraphQL<\/th>\n<th>REST<\/th>\n<th>tRPC<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>\u5951\u7ea6\u4f4d\u7f6e<\/strong><\/td>\n<td>Schema\uff08SDL\uff09<\/td>\n<td>OpenAPI\/\u6587\u6863\uff08\u53d6\u51b3\u4e8e\u6cbb\u7406\uff09<\/td>\n<td>TS \u7c7b\u578b\uff08\u7f16\u8bd1\u671f\u5171\u4eab\uff09<\/td>\n<\/tr>\n<tr>\n<td><strong>\u5ba2\u6237\u7aef\u9009\u5b57\u6bb5<\/strong><\/td>\n<td>\u539f\u751f\u652f\u6301<\/td>\n<td>\u9700 sparse fieldsets \u624b\u5de5\u8bbe\u8ba1<\/td>\n<td>\u5426\uff08\u8fd4\u56de\u503c\u56fa\u5b9a\uff09<\/td>\n<\/tr>\n<tr>\n<td><strong>\u7c7b\u578b\u5b89\u5168<\/strong><\/td>\n<td>\u5f3a\uff08Schema \u2192 codegen\uff09<\/td>\n<td>\u4f9d\u8d56 OpenAPI \u751f\u6210<\/td>\n<td>\u6781\u5f3a\uff08\u540c monorepo \u5171\u4eab TS \u7c7b\u578b\uff09<\/td>\n<\/tr>\n<tr>\n<td><strong>\u591a\u7aef\u590d\u7528<\/strong><\/td>\n<td>\u597d\uff08Web\/iOS\/Android\/\u670d\u52a1\u95f4\uff09<\/td>\n<td>\u597d<\/td>\n<td>\u4ec5 TS \u751f\u6001<\/td>\n<\/tr>\n<tr>\n<td><strong>N+1 \u98ce\u9669<\/strong><\/td>\n<td>\u53d6\u51b3\u4e8e Resolver \u4e0e\u6570\u636e\u8bbf\u95ee\u8bbe\u8ba1\uff0c\u5e38\u9700\u6279\u5904\u7406<\/td>\n<td>\u901a\u5e38\u8f83\u4f4e\uff08\u63a5\u53e3\u7c92\u5ea6\u663e\u5f0f\uff09<\/td>\n<td>\u901a\u5e38\u8f83\u4f4e\uff08procedure \u7c92\u5ea6\u663e\u5f0f\uff09<\/td>\n<\/tr>\n<tr>\n<td><strong>\u7f13\u5b58<\/strong><\/td>\n<td>\u5ba2\u6237\u7aef\u5e38\u7528\u89c4\u8303\u5316\u7f13\u5b58\uff1bHTTP \u7f13\u5b58\u9700\u989d\u5916\u8bbe\u8ba1<\/td>\n<td>HTTP \u7f13\u5b58\u53cb\u597d<\/td>\n<td>\u5e38\u4e0e TanStack Query \u914d\u5408<\/td>\n<\/tr>\n<tr>\n<td><strong>\u5b9e\u65f6\u80fd\u529b<\/strong><\/td>\n<td>Subscription\uff08SSE \/ WebSocket\uff09<\/td>\n<td>SSE \/ WebSocket<\/td>\n<td>Subscription<\/td>\n<\/tr>\n<tr>\n<td><strong>\u5b66\u4e60\u66f2\u7ebf<\/strong><\/td>\n<td>\u9661\uff08Schema\u3001Resolver\u3001DataLoader\u3001Federation\uff09<\/td>\n<td>\u7f13<\/td>\n<td>\u4f4e\u5230\u4e2d\uff08\u53d6\u51b3\u4e8e TS\/\u6846\u67b6\u719f\u6089\u5ea6\uff09<\/td>\n<\/tr>\n<tr>\n<td><strong>\u534f\u8bae\u5f00\u9500<\/strong><\/td>\n<td>\u5e38\u89c1 POST + JSON\uff1bQuery \u4e5f\u53ef\u4f7f\u7528 GET<\/td>\n<td>\u5145\u5206\u5229\u7528 HTTP \u8bed\u4e49<\/td>\n<td>HTTP + JSON<\/td>\n<\/tr>\n<tr>\n<td><strong>\u5178\u578b\u573a\u666f<\/strong><\/td>\n<td>\u591a\u5ba2\u6237\u7aef\u3001\u591a\u6570\u636e\u6e90\u805a\u5408\u3001BFF\/\u6570\u636e\u56fe<\/td>\n<td>\u516c\u5f00 API\u3001CRUD<\/td>\n<td>\u5168\u6808 TS \u5355\u4ed3<\/td>\n<\/tr>\n<tr>\n<td><strong>\u4ee3\u8868\u5b9e\u73b0<\/strong><\/td>\n<td>Apollo Server \/ graphql-yoga \/ Mercurius<\/td>\n<td>Express \/ Fastify + OpenAPI<\/td>\n<td>tRPC + Next.js<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>\u9009\u578b\u901f\u5224<\/strong>\uff1a<\/p>\n<ul>\n<li>\u56e2\u961f\u5168 TS + \u5355\u4ed3 \u2192 \u4f18\u5148\u8bc4\u4f30 <strong>tRPC<\/strong><\/li>\n<li>\u9700\u8981\u591a\u7aef\uff08Web\/App\/\u670d\u52a1\u95f4\uff09+ \u5f3a Schema \u6cbb\u7406 \u2192 <strong>GraphQL<\/strong><\/li>\n<li>\u7a33\u5b9a\u516c\u5f00 API\u3001CDN \u7f13\u5b58\u53cb\u597d\u3001\u88ab\u5927\u91cf\u4e09\u65b9\u6d88\u8d39 \u2192 <strong>REST<\/strong>\uff08\u6216\u52a0\u4e2a REST \u7f51\u5173\u4ee3\u7406 GraphQL\uff09<\/li>\n<\/ul>\n<hr>\n<h2>\u5341\u4e8c\u3001\u6027\u80fd\u4e0e\u5b89\u5168\uff1a\u751f\u4ea7\u73af\u5883\u7684\u5fc5\u4fee\u8bfe<\/h2>\n<p>GraphQL \u7684\u7075\u6d3b\u6027\u4e5f\u610f\u5473\u7740\u2014\u2014<strong>\u6076\u610f\u5ba2\u6237\u7aef\u53ef\u4ee5\u6784\u9020\u975e\u5e38\u6df1\u3001\u975e\u5e38\u8d35\u7684\u67e5\u8be2<\/strong>\u3002\u751f\u4ea7\u90e8\u7f72\u5fc5\u987b\u52a0\u62a4\u680f\uff1a<\/p>\n<p><strong>1. \u67e5\u8be2\u6df1\u5ea6\u9650\u5236<\/strong>\uff1a\u9632\u6b62 <code>friends { friends { friends { ... } } }<\/code> \u8fd9\u7c7b\u5faa\u73af\u5173\u7cfb\u88ab\u5c55\u5f00\u5f97\u8fc7\u6df1\u3002<\/p>\n<pre><code class=\"javascript\">import depthLimit from 'graphql-depth-limit';\n\/\/ Apollo Server \/ Yoga \u90fd\u652f\u6301 validationRules \u6ce8\u5165\n{ validationRules: [depthLimit(7)] }<\/code><\/pre>\n<p><strong>2. \u67e5\u8be2\u590d\u6742\u5ea6\u8ba1\u7b97<\/strong>\uff1a\u7ed9\u6bcf\u4e2a\u5b57\u6bb5\u6253\u5206\uff0c\u603b\u5206\u8d85\u9608\u503c\u62d2\u7edd\u3002<\/p>\n<pre><code class=\"javascript\">import costAnalysisModule from 'graphql-cost-analysis';\n\nconst costAnalysis = costAnalysisModule.default ?? costAnalysisModule;\n{ validationRules: [costAnalysis({ maximumCost: 1000 })] }<\/code><\/pre>\n<p><strong>3. \u6301\u4e45\u5316\/\u767d\u540d\u5355\u67e5\u8be2<\/strong>\uff1aAPQ\uff08Automatic Persisted Queries\uff09\u8ba9\u5ba2\u6237\u7aef\u540e\u7eed\u53ea\u4f20 Query \u54c8\u5e0c\uff0c\u4e3b\u8981\u51cf\u5c11\u67e5\u8be2\u6587\u6863\u7684\u4f20\u8f93\uff1b\u5982\u679c\u670d\u52a1\u7aef\u540c\u65f6\u7f13\u5b58\u89e3\u6790\u540e\u7684\u6587\u6863\uff0c\u8fd8\u53ef\u4ee5\u907f\u514d\u91cd\u590d\u89e3\u6790\u3002APQ \u4e0d\u7b49\u4e8e\u5b89\u5168\u767d\u540d\u5355\uff1a\u5b89\u5168\u8981\u6c42\u66f4\u9ad8\u65f6\uff0c\u5e94\u53ea\u63a5\u53d7\u6784\u5efa\u6216\u53d1\u5e03\u9636\u6bb5\u767b\u8bb0\u8fc7\u7684 operation hash\u3002<\/p>\n<p><strong>4. \u8d85\u65f6\u4e0e\u5e76\u53d1<\/strong>\uff1a\u4e3a\u8bf7\u6c42\u8bbe\u7f6e deadline\uff0c\u628a <code>AbortSignal<\/code> \u7ee7\u7eed\u4f20\u7ed9\u6570\u636e\u5e93\u3001HTTP\/RPC \u5ba2\u6237\u7aef\uff1b\u540c\u65f6\u9650\u5236 DataLoader \u6279\u91cf\u5927\u5c0f\u548c\u6602\u8d35\u4e0a\u6e38\u7684\u5e76\u53d1\u6570\u3002\u53ea\u521b\u5efa <code>AbortController<\/code> \u800c\u4e0d\u5411\u4e0b\u6e38\u4f20\u64ad signal\uff0c\u5e76\u4e0d\u80fd\u771f\u6b63\u53d6\u6d88\u5de5\u4f5c\u3002<\/p>\n<p><strong>5. \u5b57\u6bb5\u7ea7 rate limit<\/strong>\uff1a\u4e0d\u540c\u5b57\u6bb5\u8bbe\u4e0d\u540c\u9884\u7b97\uff08\u5982 <code>me<\/code> \u4fbf\u5b9c\u3001<code>search<\/code> \u6602\u8d35\uff09\u3002<\/p>\n<p><strong>6. Introspection \u7b56\u7565<\/strong>\uff1a\u5bf9\u516c\u7f51\u63a5\u53e3\u53ef\u4ee5\u6309\u73af\u5883\u5173\u95ed\u6216\u9274\u6743 <code>__schema<\/code> \u67e5\u8be2\uff0c\u5185\u90e8 IDE\u3001\u7f51\u5173\u6cbb\u7406\u548c codegen \u573a\u666f\u5219\u6309\u9700\u4fdd\u7559\u3002\u5173\u95ed Introspection \u4e0d\u80fd\u66ff\u4ee3\u5b57\u6bb5\u9274\u6743\u3001\u67e5\u8be2\u6210\u672c\u63a7\u5236\u548c\u9519\u8bef\u8131\u654f\u3002<\/p>\n<p><strong>7. \u9519\u8bef\u8131\u654f<\/strong>\uff1a<code>formatError<\/code> \u8fc7\u6ee4\u5806\u6808\u4e0e SQL \u7ec6\u8282\uff0c\u907f\u514d\u4fe1\u606f\u6cc4\u9732\u3002<\/p>\n<p><strong>\u6027\u80fd\u5c42\u9762<\/strong>\uff1a<\/p>\n<ul>\n<li>Query \u7684\u5144\u5f1f\u5b57\u6bb5\u53ef\u4ee5\u5e76\u53d1\u5b8c\u6210\uff0c\u4f46\u540c\u6b65 CPU \u5de5\u4f5c\u4ecd\u4f1a\u963b\u585e Node.js \u4e8b\u4ef6\u5faa\u73af<\/li>\n<li>\u5728\u6570\u636e\u8bbf\u95ee\u5c42\u6279\u91cf\u53d6\u6570\uff1bDataLoader \u662f\u5e38\u89c1\u65b9\u6848\u4e4b\u4e00\uff0c\u4e0d\u662f\u56fa\u5b9a\u524d\u63d0<\/li>\n<li>APQ \u7528\u4e8e\u538b\u7f29\u67e5\u8be2\u6587\u6863\uff1b\u54cd\u5e94\u7f13\u5b58\u9700\u8981\u5355\u72ec\u8bbe\u8ba1 cache key\u3001TTL \u548c\u5931\u6548\u7b56\u7565<\/li>\n<li>\u9700\u8981 CDN \u7f13\u5b58\u65f6\uff0c\u53ef\u4e3a\u53ea\u8bfb Query \u8bbe\u8ba1 GET\u3001\u7a33\u5b9a cache key \u4e0e\u6070\u5f53\u7684\u7f13\u5b58\u5934<\/li>\n<li>Federation \u573a\u666f\u6ce8\u610f\u8de8 Subgraph \u7684 N+1\uff08Apollo \u7684 <code>@key<\/code> + <code>_entities<\/code> \u9700\u642d\u914d loader\uff09<\/li>\n<\/ul>\n<hr>\n<h2>\u901f\u67e5\u6e05\u5355<\/h2>\n<table>\n<thead>\n<tr>\n<th>\u573a\u666f<\/th>\n<th>\u7528\u4ec0\u4e48<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>\u4e00\u6b21\u6027\u83b7\u53d6\u591a\u8d44\u6e90\u3001\u5b57\u6bb5\u7ed3\u6784\u968f\u9875\u9762\u53d8\u5316<\/td>\n<td><strong>Query + Fragment<\/strong><\/td>\n<\/tr>\n<tr>\n<td>\u5199\u64cd\u4f5c\u3001\u9700\u6309\u987a\u5e8f\u89e6\u53d1\u9876\u5c42\u5b57\u6bb5<\/td>\n<td><strong>Mutation<\/strong>\uff08\u4ec5\u9876\u5c42\u5b57\u6bb5\u4e32\u884c\uff0c\u4e0d\u4ee3\u8868\u4e8b\u52a1\uff09<\/td>\n<\/tr>\n<tr>\n<td>\u5b9e\u65f6\u63a8\u9001<\/td>\n<td><strong>Subscription<\/strong> + SSE \/ WebSocket \u4f20\u8f93<\/td>\n<\/tr>\n<tr>\n<td>\u907f\u514d N+1<\/td>\n<td>\u6279\u91cf\u6570\u636e\u8bbf\u95ee\uff1b\u9700\u8981\u65f6\u4f7f\u7528\u8bf7\u6c42\u7ea7 <strong>DataLoader<\/strong><\/td>\n<\/tr>\n<tr>\n<td>\u590d\u7528\u5b57\u6bb5\u9009\u62e9<\/td>\n<td><strong>Fragment<\/strong><\/td>\n<\/tr>\n<tr>\n<td>\u6761\u4ef6\u5b57\u6bb5<\/td>\n<td><code>@include(if:)<\/code> \/ <code>@skip(if:)<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u8ba4\u8bc1\/\u6743\u9650<\/td>\n<td>Resolver \/ \u4e2d\u95f4\u4ef6 \/ Schema Directive<\/td>\n<\/tr>\n<tr>\n<td>\u751f\u4ea7\u9632\u5fa1<\/td>\n<td>depth-limit + cost-analysis + persisted \/ allowlisted queries<\/td>\n<\/tr>\n<tr>\n<td>\u591a\u670d\u52a1\u805a\u5408<\/td>\n<td><strong>Apollo Federation<\/strong> \/ <strong>Schema Stitching<\/strong><\/td>\n<\/tr>\n<tr>\n<td>\u5ba2\u6237\u7aef\u7f13\u5b58<\/td>\n<td>Apollo Client \/ urql \/ Relay<\/td>\n<\/tr>\n<tr>\n<td>\u5168\u6808 TS \u5355\u4ed3\u4e14\u4e0d\u9700\u591a\u7aef<\/td>\n<td>\u4f18\u5148\u8bc4\u4f30 <strong>tRPC<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>\u5fc3\u667a\u6a21\u578b\u53e3\u8bc0<\/strong>\uff1a<\/p>\n<ul>\n<li><strong>Schema \u662f\u5951\u7ea6\uff0cQuery \u662f\u5f62\u72b6\uff0cResolver \u662f\u53d6\u6570\u3002<\/strong><\/li>\n<li><strong>\u54cd\u5e94\u6309 response key \u7ec4\u88c5\uff0c\u9519\u8bef\u6309\u5b57\u6bb5\u8def\u5f84\u5b9a\u4f4d\u3002<\/strong><\/li>\n<li><strong>\u5b57\u6bb5\u7531\u663e\u5f0f Resolver \u6216\u9ed8\u8ba4\u5b57\u6bb5\u89e3\u6790\u5668\u5b8c\u6210\u3002<\/strong><\/li>\n<li><strong>Non-Null \u51b3\u5b9a\u9519\u8bef\u5411\u4e0a\u5f71\u54cd\u7684\u8303\u56f4\u3002<\/strong><\/li>\n<\/ul>\n<hr>\n<h2>\u7ed3\u8bed<\/h2>\n<p>GraphQL \u7684\u4e3b\u8981\u5de5\u7a0b\u6210\u672c\u96c6\u4e2d\u5728 Schema \u6f14\u8fdb\u3001Resolver \u7ec4\u7ec7\u3001\u6743\u9650\u4e0e\u6210\u672c\u63a7\u5236\u3001N+1 \u6cbb\u7406\uff0c\u4ee5\u53ca\u591a\u670d\u52a1\u573a\u666f\u4e0b\u7684\u56fe\u7ec4\u5408\u3002\u76f8\u5e94\u5730\uff0c\u5ba2\u6237\u7aef\u83b7\u5f97\u5b57\u6bb5\u81ea\u9009\u3001Schema \u9a71\u52a8\u7684\u7c7b\u578b\u751f\u6210\u548c\u591a\u7aef\u590d\u7528\u80fd\u529b\u3002<\/p>\n<p>\u7406\u89e3 Parse \u2192 Validate \u2192 Execute \u2192 Format \u56db\u9636\u6bb5\u6d41\u6c34\u7ebf\uff0c\u4ee5\u53ca Resolver\u3001\u503c\u5b8c\u6210\u548c Non-Null \u5192\u6ce1\u4e4b\u95f4\u7684\u5173\u7cfb\u540e\uff0c\u5c31\u80fd\u66f4\u51c6\u786e\u5730\u5224\u65ad Apollo\u3001Relay\u3001Federation\u3001Yoga \u7b49\u5de5\u5177\u5206\u522b\u89e3\u51b3\u4e86\u6267\u884c\u94fe\u4e0a\u7684\u54ea\u7c7b\u5de5\u7a0b\u95ee\u9898\u3002<\/p>\n<p>\u5982\u679c\u53ea\u4ece\u63a5\u53e3\u5f62\u6001\u770b\uff0c\u5b83\u50cf REST \u7684\u52a0\u5f3a\u7248\uff1b\u4ece\u67b6\u6784\u89c6\u89d2\u770b\uff0c\u5b83\u66f4\u50cf<strong>\u628a\u6570\u636e\u56fe\uff08Data Graph\uff09\u5efa\u6a21\u5230\u534f\u8bae\u91cc<\/strong>\u3002\u8fd9\u4e00\u70b9\uff0c\u662f\u5b83\u503c\u5f97\u5b66\u7684\u771f\u6b63\u7406\u7531\u3002<\/p>\n<p><a href=\"https:\/\/segmentfault.com\/a\/1190000048027163\">https:\/\/segmentfault.com\/a\/1190000048027163<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u5927\u591a\u6570\u540e\u7aef\u5f00\u53d1\u8005\u5bf9 GraphQL \u7684\u7b2c\u4e00\u5370\u8c61\u662f\u300c\u4e00\u4e2a\u66f4\u7075\u6d3b\u7684 REST\u300d\u2014\u2014\u5ba2\u6237\u7aef\u80fd\u81ea\u9009\u5b57\u6bb5\u3001\u907f\u514d\u5197\u4f59\u3002\u4f46\u8fd9 [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[4],"tags":[],"class_list":["post-10021411","post","type-post","status-publish","format-standard","hentry","category-share"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10021411","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=10021411"}],"version-history":[{"count":0,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10021411\/revisions"}],"wp:attachment":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=10021411"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=10021411"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=10021411"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}