[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"sanity-2cMwOKIt9Yt1pg_0QkIE3W9IYs5oWfntZeCDi6hdjKw":3,"sanity-25QWuHnfx_g-GNsoY_adwIOn4d4UpvBLJfbasmPaGms":1801},{"data":4,"sourceMap":-1},{"latestPodcast":5,"latestReleases":14,"post":39,"recent":1776},[6],{"_id":7,"publishedAt":8,"slug":9,"sponsored":12,"title":13},"d53f9358-3bb2-4f69-aebe-d31d19522cd4","2026-07-10T07:40:00.000Z",{"_type":10,"current":11},"slug","building-more-than-just-an-agent-harness",null,"Building more than just an agent harness",[15,21,27,33],{"_id":16,"publishedAt":17,"slug":18,"title":20},"eb5b66eb-9410-4329-83bb-22bbff39402a","2026-04-28T13:00:00.000Z",{"_type":10,"current":19},"turn-scattered-knowledge-into-trusted-intelligence","Turning scattered knowledge into trusted intelligence: Stack Internal 2026.3",{"_id":22,"publishedAt":23,"slug":24,"title":26},"369c2401-b62e-4a37-8ff8-bf603023ecad","2026-03-02T15:03:00.988Z",{"_type":10,"current":25},"what-s-new-at-stack-overflow-march-2026","What’s new at Stack Overflow: March 2026",{"_id":28,"publishedAt":29,"slug":30,"title":32},"5e9053a4-07ea-447c-91ea-29e0b6228537","2026-02-02T15:00:00.000Z",{"_type":10,"current":31},"what-s-new-at-stack-overflow-february-2026","What’s new at Stack Overflow: February 2026",{"_id":34,"publishedAt":35,"slug":36,"title":38},"a1b538eb-a8a6-46d0-80a1-ac70ec9bb935","2026-01-05T10:00:00.000-05:00",{"_type":10,"current":37},"what-s-new-at-stack-overflow-january-2026","What’s new at Stack Overflow: January 2026",{"_createdAt":40,"_id":41,"_rev":42,"_type":43,"_updatedAt":44,"author":45,"body":62,"comments":1741,"dateUrl":1742,"excerpt":1743,"image":1744,"legacyBody":1747,"product":12,"publishedAt":1750,"slug":1751,"sponsored":12,"tags":1753,"title":1775,"visible":1741},"2023-05-24T12:51:03Z","wp-post-21194","Bqj3l3jZyXV24sfjnzaZDZ","blogPost","2026-05-11T18:33:17Z",[46],{"_createdAt":47,"_id":48,"_rev":49,"_type":50,"_updatedAt":51,"avatar":52,"bio":57,"employee":58,"name":59,"slug":60},"2023-05-23T16:27:18Z","wp-author-cap-21198","07ZbrKPSUrjrV4wQ6fDtDP","blogAuthor","2023-06-20T15:05:14Z",{"_type":53,"asset":54},"image",{"_ref":55,"_type":56},"image-f0fc20e543766e8aa687dae2d53b6b2239f7d0e3-40x40-png","reference","","none","Loren Sands-Ramshaw",{"current":61},"loren-sands-ramshaw",[63,75,117,127,146,165,183,192,218,227,236,264,272,310,337,356,364,372,376,407,410,418,421,453,456,478,486,489,513,516,524,598,606,629,661,677,704,740,748,767,775,783,873,910,978,986,1056,1115,1131,1139,1155,1171,1183,1232,1240,1248,1256,1264,1272,1280,1288,1296,1304,1311,1320,1328,1337,1345,1353,1361,1369,1377,1385,1393,1411,1419,1427,1434,1441,1449,1457,1465,1473,1481,1489,1497,1505,1512,1520,1528,1536,1544,1552,1560,1568,1576,1593,1601,1653,1661,1669,1677,1685,1733],{"_key":64,"_type":65,"children":66,"markDefs":73,"style":74},"cd465d991d0d","block",[67],{"_key":68,"_type":69,"marks":70,"text":72},"cd465d991d0d0","span",[71],"em","TLDR: Use GraphQL for client-server communication and gRPC for server-to-server. See the Verdict section for exceptions to this rule.",[],"normal",{"_key":76,"_type":65,"children":77,"markDefs":109,"style":74},"96591816590e",[78,82,87,91,96,100,105],{"_key":79,"_type":69,"marks":80,"text":81},"96591816590e0",[],"I've read a lot of comparisons of these two protocols and wanted to write one that is comprehensive and impartial. (Well, as impartial as I and my reviewers can make it 😄.) I was inspired by the release of ",{"_key":83,"_type":69,"marks":84,"text":86},"96591816590e1",[85],"b33de8f36a48","connect-web",{"_key":88,"_type":69,"marks":89,"text":90},"96591816590e2",[]," (a TypeScript gRPC client that can be used in the browser) and a popular HN post entitled ",{"_key":92,"_type":69,"marks":93,"text":95},"96591816590e3",[94],"de021c599049","GraphQL kinda sucks",{"_key":97,"_type":69,"marks":98,"text":99},"96591816590e4",[],". My personal history of communication protocols built on top of ",{"_key":101,"_type":69,"marks":102,"text":104},"96591816590e5",[103],"128e4d3e029b","layer 7",{"_key":106,"_type":69,"marks":107,"text":108},"96591816590e6",[],":",[110,113,115],{"_key":85,"_type":111,"href":112,"reference":12},"link","https:\u002F\u002Fbuf.build\u002Fblog\u002Fconnect-web-protobuf-grpc-in-the-browser",{"_key":94,"_type":111,"href":114,"reference":12},"https:\u002F\u002Fnews.ycombinator.com\u002Fitem?id=32366759",{"_key":103,"_type":111,"href":116,"reference":12},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FOSI_model",{"_key":118,"_type":65,"children":119,"level":124,"listItem":125,"markDefs":126,"style":74},"5aa7ab9bf9af",[120],{"_key":121,"_type":69,"marks":122,"text":123},"5aa7ab9bf9af0",[],"REST (Rails and Express)",1,"bullet",[],{"_key":128,"_type":65,"children":129,"level":124,"listItem":125,"markDefs":143,"style":74},"db6c1fe878ce",[130,134,139],{"_key":131,"_type":69,"marks":132,"text":133},"db6c1fe878ce0",[],"➡️ DDP (Meteor's ",{"_key":135,"_type":69,"marks":136,"text":138},"db6c1fe878ce1",[137],"7bb1be2fd0ba","WebSocket protocol",{"_key":140,"_type":69,"marks":141,"text":142},"db6c1fe878ce2",[],")",[144],{"_key":137,"_type":111,"href":145,"reference":12},"https:\u002F\u002Fgithub.com\u002Fmeteor\u002Fmeteor\u002Fblob\u002Fdevel\u002Fpackages\u002Fddp\u002FDDP.md",{"_key":147,"_type":65,"children":148,"level":124,"listItem":125,"markDefs":162,"style":74},"31ddf2f212c1",[149,153,158],{"_key":150,"_type":69,"marks":151,"text":152},"31ddf2f212c10",[],"➡️ GraphQL (which I wrote ",{"_key":154,"_type":69,"marks":155,"text":157},"31ddf2f212c11",[156],"bdb752cfc68a","a book",{"_key":159,"_type":69,"marks":160,"text":161},"31ddf2f212c12",[]," about)",[163],{"_key":156,"_type":111,"href":164,"reference":12},"https:\u002F\u002Fgraphql.guide\u002F",{"_key":166,"_type":65,"children":167,"level":124,"listItem":125,"markDefs":180,"style":74},"40cc1aec05e1",[168,172,177],{"_key":169,"_type":69,"marks":170,"text":171},"40cc1aec05e10",[],"➡️ gRPC (which I use at ",{"_key":173,"_type":69,"marks":174,"text":176},"40cc1aec05e11",[175],"47e806fd9f79","Temporal",{"_key":178,"_type":69,"marks":179,"text":142},"40cc1aec05e12",[],[181],{"_key":175,"_type":111,"href":182,"reference":12},"https:\u002F\u002Ftemporal.io\u002F",{"_key":184,"_type":65,"children":185,"markDefs":190,"style":191},"52a327beb681",[186],{"_key":187,"_type":69,"marks":188,"text":189},"52a327beb6810",[],"Background",[],"h1",{"_key":193,"_type":65,"children":194,"markDefs":213,"style":74},"f9e6fa07f998",[195,200,204,209],{"_key":196,"_type":69,"marks":197,"text":199},"f9e6fa07f9980",[198],"6373525887df","gRPC",{"_key":201,"_type":69,"marks":202,"text":203},"f9e6fa07f9981",[]," was released in 2016 by Google as an efficient and developer-friendly method of server-to-server communication. ",{"_key":205,"_type":69,"marks":206,"text":208},"f9e6fa07f9982",[207],"51f6107e85e1","GraphQL",{"_key":210,"_type":69,"marks":211,"text":212},"f9e6fa07f9983",[]," was released in 2015 by Meta as an efficient and developer-friendly method of client-server communication. They both have significant advantages over REST and have a lot in common. We’ll spend most of the article comparing their traits, and then we’ll summarize each protocol’s strengths and weaknesses. At the end, we’ll know why each is such a good fit for its intended domain and when we might want to use one in the other’s domain.",[214,216],{"_key":198,"_type":111,"href":215,"reference":12},"http:\u002F\u002Fgrpc.io",{"_key":207,"_type":111,"href":217,"reference":12},"http:\u002F\u002Fgraphql.org",{"_key":219,"_type":65,"children":220,"markDefs":225,"style":226},"13b02d639d49",[221],{"_key":222,"_type":69,"marks":223,"text":224},"13b02d639d490",[],"Comparing gRPC and GraphQL features",[],"h2",{"_key":228,"_type":65,"children":229,"markDefs":234,"style":235},"f24259d551f0",[230],{"_key":231,"_type":69,"marks":232,"text":233},"f24259d551f00",[],"Interface design",[],"h3",{"_key":237,"_type":65,"children":238,"markDefs":261,"style":74},"a880e792c4ab",[239,243,248,252,257],{"_key":240,"_type":69,"marks":241,"text":242},"a880e792c4ab0",[],"Both gRPC and GraphQL are ",{"_key":244,"_type":69,"marks":245,"text":247},"a880e792c4ab1",[246],"2a80688e30ba","Interface Description Languages",{"_key":249,"_type":69,"marks":250,"text":251},"a880e792c4ab2",[]," (IDLs) that describe how two computers can talk to each other. They work across different programming languages, and we can use codegen tools to generate typed interfaces in a number of languages. IDLs abstract away the transport layer; GraphQL is transport-agnostic but generally used over HTTP, while gRPC uses HTTP\u002F2. We don’t need to know about transport-level details like the method, path, query parameters, and body format in as REST. We just need to know a ",{"_key":253,"_type":69,"marks":254,"text":256},"a880e792c4ab3",[255],"strong","single endpoint",{"_key":258,"_type":69,"marks":259,"text":260},"a880e792c4ab4",[]," that we use our higher-level client library to communicate with.",[262],{"_key":246,"_type":111,"href":263,"reference":12},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FInterface_description_language",{"_key":265,"_type":65,"children":266,"markDefs":271,"style":235},"7d95105d3848",[267],{"_key":268,"_type":69,"marks":269,"text":270},"7d95105d38480",[],"Message format",[],{"_key":273,"_type":65,"children":274,"markDefs":305,"style":74},"8c269e5ac29f",[275,279,284,288,292,296,301],{"_key":276,"_type":69,"marks":277,"text":278},"8c269e5ac29f0",[],"Message size matters because smaller messages generally take less time to send over the network. gRPC uses ",{"_key":280,"_type":69,"marks":281,"text":283},"8c269e5ac29f1",[282],"3b223ac240f4","protocol buffers",{"_key":285,"_type":69,"marks":286,"text":287},"8c269e5ac29f2",[]," (a.k.a. protobufs), a ",{"_key":289,"_type":69,"marks":290,"text":291},"8c269e5ac29f3",[255],"binary format",{"_key":293,"_type":69,"marks":294,"text":295},"8c269e5ac29f4",[]," that just includes values, while GraphQL uses JSON, which is text-based and includes field names in addition to values. The binary format combined with less information sent generally results in gRPC messages being smaller than GraphQL messages. (While an efficient binary format is ",{"_key":297,"_type":69,"marks":298,"text":300},"8c269e5ac29f5",[299],"f1f53bdbd1d4","feasible",{"_key":302,"_type":69,"marks":303,"text":304},"8c269e5ac29f6",[]," in GraphQL, it’s rarely used and isn’t supported by most of the libraries and tooling.)",[306,308],{"_key":282,"_type":111,"href":307,"reference":12},"https:\u002F\u002Fdevelopers.google.com\u002Fprotocol-buffers",{"_key":299,"_type":111,"href":309,"reference":12},"https:\u002F\u002Fgithub.com\u002Fesseswann\u002Fgraphql-binary",{"_key":311,"_type":65,"children":312,"markDefs":334,"style":74},"8055a074f5fe",[313,317,321,325,330],{"_key":314,"_type":69,"marks":315,"text":316},"8055a074f5fe0",[],"Another aspect that affects message size is ",{"_key":318,"_type":69,"marks":319,"text":320},"8055a074f5fe1",[255],"overfetching",{"_key":322,"_type":69,"marks":323,"text":324},"8055a074f5fe2",[],": whether we can request only specific fields or will always receive all fields (“overfetching” fields we don’t need). GraphQL always specifies in the request which fields are desired, and in gRPC, we can use ",{"_key":326,"_type":69,"marks":327,"text":329},"8055a074f5fe3",[328],"66a1df11d7ef","FieldMasks",{"_key":331,"_type":69,"marks":332,"text":333},"8055a074f5fe4",[]," as reusable filters for requests.",[335],{"_key":328,"_type":111,"href":336,"reference":12},"https:\u002F\u002Fnetflixtechblog.com\u002Fpractical-api-design-at-netflix-part-1-using-protobuf-fieldmask-35cfdc606518",{"_key":338,"_type":65,"children":339,"markDefs":353,"style":74},"83354d466b27",[340,344,349],{"_key":341,"_type":69,"marks":342,"text":343},"83354d466b270",[],"Another benefit to gRPC’s binary format is faster serializing and parsing of messages compared to that of GraphQL’s text messages. The downside is that it’s harder to view and debug than the human-readable JSON. We at Temporal use protobuf’s ",{"_key":345,"_type":69,"marks":346,"text":348},"83354d466b271",[347],"40c8b71db2cf","JSON format",{"_key":350,"_type":69,"marks":351,"text":352},"83354d466b272",[]," by default for the visibility benefit to developer experience. (That loses the efficiency that came with the binary format, but users who value the efficiency more can switch to binary.)",[354],{"_key":347,"_type":111,"href":355,"reference":12},"https:\u002F\u002Fdevelopers.google.com\u002Fprotocol-buffers\u002Fdocs\u002Fproto3#json",{"_key":357,"_type":65,"children":358,"markDefs":363,"style":235},"5dd72e102462",[359],{"_key":360,"_type":69,"marks":361,"text":362},"5dd72e1024620",[],"Defaults",[],{"_key":365,"_type":65,"children":366,"markDefs":371,"style":74},"f57727682ec5",[367],{"_key":368,"_type":69,"marks":369,"text":370},"f57727682ec50",[],"gRPC also doesn’t include default values in messages, which GraphQL can do for arguments but not request fields or response types. This is another factor in gRPC messages’ smaller size. It also affects the DX of consuming a gRPC API. There’s no distinction between leaving an input field unset and setting it to the default value, and the default value is based on the type of the field. All booleans default to false, and all numbers and enums default to 0. We can’t default the `behavior` enum input field to `BEHAVIOR_FOO = 2`—we have to either put the default value first (`BEHAVIOR_FOO = 0`), which means it will always be the default in the future, or we follow the recommended practice of having a `BEHAVIOR_UNSPECIFIED = 0` enum value:",[],{"_key":373,"_type":374,"code":375,"markDefs":12},"7cd07f6a2f42","code","enum Behavior {\n  BEHAVIOR_UNSPECIFIED = 0;\n  BEHAVIOR_FOO = 1;\n  BEHAVIOR_BAR = 2;\n}\n",{"_key":377,"_type":65,"children":378,"markDefs":406,"style":74},"7345bfc228cf",[379,383,387,391,395,399,402],{"_key":380,"_type":69,"marks":381,"text":382},"7345bfc228cf0",[],"The API provider needs to communicate what ",{"_key":384,"_type":69,"marks":385,"text":386},"7345bfc228cf1",[374],"UNSPECIFIED",{"_key":388,"_type":69,"marks":389,"text":390},"7345bfc228cf2",[]," means (by documenting “unspecified will use the default behavior, which is currently ",{"_key":392,"_type":69,"marks":393,"text":394},"7345bfc228cf3",[374],"FOO",{"_key":396,"_type":69,"marks":397,"text":398},"7345bfc228cf4",[],"”), the consumer needs to think about whether the server default behavior may change in the future (if the server saves the provided ",{"_key":400,"_type":69,"marks":401,"text":386},"7345bfc228cf5",[374],{"_key":403,"_type":69,"marks":404,"text":405},"7345bfc228cf6",[]," \u002F 0 value in some business entity the consumer is creating, and the server later changes the default behavior, the entity will start behaving differently) and whether that would be desired. If it wouldn’t be desired, the client needs to set the value to the current default. Here’s an example scenario:",[],{"_key":408,"_type":374,"code":409,"markDefs":12},"3544e838c1df","service ExampleGrpcService {\n  rpc CreateEntity (CreateEntityRequest) returns (CreateEntityResponse) {}\n}\n\nmessage CreateEntityRequest {\n  string name = 1;\n  Behavior behavior = 2;\n}\n",{"_key":411,"_type":65,"children":412,"markDefs":417,"style":74},"a08000362fa9",[413],{"_key":414,"_type":69,"marks":415,"text":416},"a08000362fa90",[],"If we do:",[],{"_key":419,"_type":374,"code":420,"markDefs":12},"405826e9c1ef","const request = new CreateEntityRequest({ name: “my entity” })\nservice.CreateEntity(request)\n",{"_key":422,"_type":65,"children":423,"markDefs":452,"style":74},"caf468a2bc7c",[424,428,432,436,440,444,448],{"_key":425,"_type":69,"marks":426,"text":427},"caf468a2bc7c0",[],"we’ll be sending ",{"_key":429,"_type":69,"marks":430,"text":431},"caf468a2bc7c1",[374],"BEHAVIOR_UNSPECIFIED",{"_key":433,"_type":69,"marks":434,"text":435},"caf468a2bc7c2",[],", which depending on the server implementation and future changes, might mean ",{"_key":437,"_type":69,"marks":438,"text":439},"caf468a2bc7c3",[374],"BEHAVIOR_FOO",{"_key":441,"_type":69,"marks":442,"text":443},"caf468a2bc7c4",[]," now and ",{"_key":445,"_type":69,"marks":446,"text":447},"caf468a2bc7c5",[374],"BEHAVIOR_BAR",{"_key":449,"_type":69,"marks":450,"text":451},"caf468a2bc7c6",[]," later. Or we can do:",[],{"_key":454,"_type":374,"code":455,"markDefs":12},"460cfb15cf6d","const request = new CreateEntityRequest({ name: “my entity”, behavior: Behavior.BEHAVIOR_FOO })\nservice.CreateEntity(request)\n",{"_key":457,"_type":65,"children":458,"markDefs":477,"style":74},"a00bda6ed072",[459,463,466,470,473],{"_key":460,"_type":69,"marks":461,"text":462},"a00bda6ed0720",[],"to be certain the behavior is stored as ",{"_key":464,"_type":69,"marks":465,"text":394},"a00bda6ed0721",[374],{"_key":467,"_type":69,"marks":468,"text":469},"a00bda6ed0722",[]," and will remain ",{"_key":471,"_type":69,"marks":472,"text":394},"a00bda6ed0723",[374],{"_key":474,"_type":69,"marks":475,"text":476},"a00bda6ed0724",[],".",[],{"_key":479,"_type":65,"children":480,"markDefs":485,"style":74},"48bed0413f95",[481],{"_key":482,"_type":69,"marks":483,"text":484},"48bed0413f950",[],"The equivalent GraphQL schema would be:",[],{"_key":487,"_type":374,"code":488,"markDefs":12},"6c075a6e63bf","type Mutation {\n  createEntity(name: String, behavior: Behavior = FOO): Entity\n}\n\nenum Behavior {\n  FOO\n  BAR\n}\n",{"_key":490,"_type":65,"children":491,"markDefs":512,"style":74},"6031d1438055",[492,496,500,504,508],{"_key":493,"_type":69,"marks":494,"text":495},"6031d14380550",[],"When we don’t include ",{"_key":497,"_type":69,"marks":498,"text":499},"6031d14380551",[374],"behavior",{"_key":501,"_type":69,"marks":502,"text":503},"6031d14380552",[]," in the request, the server code will receive and store FOO as the value, matching the ",{"_key":505,"_type":69,"marks":506,"text":507},"6031d14380553",[374],"= FOO",{"_key":509,"_type":69,"marks":510,"text":511},"6031d14380554",[]," default in the schema above.",[],{"_key":514,"_type":374,"code":515,"markDefs":12},"70c96d332857","graphqlClient.request(`\n  mutation  {\n    createEntity(name: “my entity”)\n  }\n`\n",{"_key":517,"_type":65,"children":518,"markDefs":523,"style":74},"4ba82486ce5a",[519],{"_key":520,"_type":69,"marks":521,"text":522},"4ba82486ce5a0",[],"It’s simpler than the gRPC version to know what will happen if the field isn’t provided, and we don’t need to consider whether to pass the default value ourselves.",[],{"_key":525,"_type":65,"children":526,"markDefs":595,"style":74},"863a03bfa51e",[527,531,536,540,544,548,552,556,560,564,568,572,576,580,584,588,591],{"_key":528,"_type":69,"marks":529,"text":530},"863a03bfa51e0",[],"Other ",{"_key":532,"_type":69,"marks":533,"text":535},"863a03bfa51e1",[534],"250e2a4853b8","types’ defaults",{"_key":537,"_type":69,"marks":538,"text":539},"863a03bfa51e2",[]," have other quirks. For numbers, sometimes the default 0 is a valid value, and sometimes it will mean a different default value. For booleans, the default false results in negatively named fields. When we’re naming a boolean variable while coding, we use the positive name. For instance, we’d usually declare ",{"_key":541,"_type":69,"marks":542,"text":543},"863a03bfa51e3",[374],"let retryable = true",{"_key":545,"_type":69,"marks":546,"text":547},"863a03bfa51e4",[]," rather than ",{"_key":549,"_type":69,"marks":550,"text":551},"863a03bfa51e5",[374],"let nonRetryable = false",{"_key":553,"_type":69,"marks":554,"text":555},"863a03bfa51e6",[],". People generally find the former more readable, as the latter takes an extra step to understand the double negative (“",{"_key":557,"_type":69,"marks":558,"text":559},"863a03bfa51e7",[374],"notRetryable",{"_key":561,"_type":69,"marks":562,"text":563},"863a03bfa51e8",[]," is ",{"_key":565,"_type":69,"marks":566,"text":567},"863a03bfa51e9",[374],"false",{"_key":569,"_type":69,"marks":570,"text":571},"863a03bfa51e10",[],", so it’s retryable”). But if we have a gRPC API in which we want the default state to be retryable, then we have to name the field ",{"_key":573,"_type":69,"marks":574,"text":575},"863a03bfa51e11",[374],"nonRetryable",{"_key":577,"_type":69,"marks":578,"text":579},"863a03bfa51e12",[],", because the default of an ",{"_key":581,"_type":69,"marks":582,"text":583},"863a03bfa51e13",[374],"retryable",{"_key":585,"_type":69,"marks":586,"text":587},"863a03bfa51e14",[]," field would be ",{"_key":589,"_type":69,"marks":590,"text":567},"863a03bfa51e15",[374],{"_key":592,"_type":69,"marks":593,"text":594},"863a03bfa51e16",[],", like all booleans in gRPC.",[596],{"_key":534,"_type":111,"href":597,"reference":12},"https:\u002F\u002Fdevelopers.google.com\u002Fprotocol-buffers\u002Fdocs\u002Fproto3#default",{"_key":599,"_type":65,"children":600,"markDefs":605,"style":235},"d6fd4ca9629e",[601],{"_key":602,"_type":69,"marks":603,"text":604},"d6fd4ca9629e0",[],"Request format",[],{"_key":607,"_type":65,"children":608,"markDefs":626,"style":74},"36dab8f13f39",[609,613,618,622],{"_key":610,"_type":69,"marks":611,"text":612},"36dab8f13f390",[],"In gRPC, we call methods one at a time. If we need more data than a single method provides, we need to call multiple methods. And if we need response data from the first method in order to know which method to call next, then we’re doing multiple ",{"_key":614,"_type":69,"marks":615,"text":617},"36dab8f13f391",[616],"4e4cf40d389a","round trips",{"_key":619,"_type":69,"marks":620,"text":621},"36dab8f13f392",[]," in a row. Unless we’re in the same data center as the server, that causes a significant delay. This issue is called ",{"_key":623,"_type":69,"marks":624,"text":625},"36dab8f13f393",[255],"underfetching.",[627],{"_key":616,"_type":111,"href":628,"reference":12},"https:\u002F\u002Fgraphql.guide\u002Fbackground\u002Flatency\u002F",{"_key":630,"_type":65,"children":631,"markDefs":660,"style":74},"9ff8a4a04e54",[632,636,640,644,648,652,656],{"_key":633,"_type":69,"marks":634,"text":635},"9ff8a4a04e540",[],"This is one of the issues GraphQL was designed to solve. It’s particularly important over high-latency mobile phone connections to be able to get all the data you need in a single request. In GraphQL, we send a string (called a ",{"_key":637,"_type":69,"marks":638,"text":639},"9ff8a4a04e541",[71],"document",{"_key":641,"_type":69,"marks":642,"text":643},"9ff8a4a04e542",[],") with our request that includes all the methods (called ",{"_key":645,"_type":69,"marks":646,"text":647},"9ff8a4a04e543",[71],"queries",{"_key":649,"_type":69,"marks":650,"text":651},"9ff8a4a04e544",[]," and ",{"_key":653,"_type":69,"marks":654,"text":655},"9ff8a4a04e545",[71],"mutations",{"_key":657,"_type":69,"marks":658,"text":659},"9ff8a4a04e546",[],") we want to call and all the nested data we need based on the first-level results. Some of the nested data may require subsequent requests from the server to the database, but they’re usually located in the same data center, which should have sub-millisecond network latency.",[],{"_key":662,"_type":65,"children":663,"markDefs":676,"style":74},"7c1149023547",[664,668,672],{"_key":665,"_type":69,"marks":666,"text":667},"7c11490235470",[],"GraphQL’s request flexibility lets front-end and back-end teams become ",{"_key":669,"_type":69,"marks":670,"text":671},"7c11490235471",[255],"less coupled",{"_key":673,"_type":69,"marks":674,"text":675},"7c11490235472",[],". Instead of the front-end developers waiting for the back-end developers to add more data to a method’s response (so the client can receive the data in a single request), the front-end developers can add more queries or nested result fields to their request. When there’s a GraphQL API that covers the organization’s entire data graph, the front-end team gets blocked waiting for backend changes much less frequently.",[],{"_key":678,"_type":65,"children":679,"markDefs":701,"style":74},"38ef73525b5d",[680,684,688,692,697],{"_key":681,"_type":69,"marks":682,"text":683},"38ef73525b5d0",[],"The fact that the GraphQL request specifies all desired data fields means that the client can use ",{"_key":685,"_type":69,"marks":686,"text":687},"38ef73525b5d1",[255],"declarative data fetching",{"_key":689,"_type":69,"marks":690,"text":691},"38ef73525b5d2",[],": instead of imperatively fetching data (like calling `grpcClient.callMethod()```), we declare the data we need next to our view component, and the GraphQL client library combines those pieces into a single request and provides the data to the components when the response arrives and later when the data changes. The parallel for ",{"_key":693,"_type":69,"marks":694,"text":696},"38ef73525b5d3",[695],"b3b403b935b3","view libraries",{"_key":698,"_type":69,"marks":699,"text":700},"38ef73525b5d4",[]," in web development is using React instead of jQuery: declaring how our components should look and having them automatically update when data changes instead of imperatively manipulating the DOM with jQuery.",[702],{"_key":695,"_type":111,"href":703,"reference":12},"https:\u002F\u002Fwww.javascriptstuff.com\u002Fview-libraries\u002F",{"_key":705,"_type":65,"children":706,"markDefs":735,"style":74},"3c970600f0c4",[707,711,715,719,724,727,732],{"_key":708,"_type":69,"marks":709,"text":710},"3c970600f0c40",[],"Another effect GraphQL’s request format has is ",{"_key":712,"_type":69,"marks":713,"text":714},"3c970600f0c41",[255],"increased visibility",{"_key":716,"_type":69,"marks":717,"text":718},"3c970600f0c42",[],": the server sees each field that’s requested. We can track field usage and see when clients have stopped using deprecated fields, so that we know when we can remove them as opposed to forever supporting something that we said we’d get rid of. Tracking is built into common tools like ",{"_key":720,"_type":69,"marks":721,"text":723},"3c970600f0c43",[722],"a9ef3c7f3bdd","Apollo GraphOS",{"_key":725,"_type":69,"marks":726,"text":651},"3c970600f0c44",[],{"_key":728,"_type":69,"marks":729,"text":731},"3c970600f0c45",[730],"e4a9976ed8ab","Stellate",{"_key":733,"_type":69,"marks":734,"text":476},"3c970600f0c46",[],[736,738],{"_key":722,"_type":111,"href":737,"reference":12},"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Fgraphos\u002Fmetrics\u002Ffield-usage\u002F",{"_key":730,"_type":111,"href":739,"reference":12},"https:\u002F\u002Fstellate.co\u002Fgraphql-metrics",{"_key":741,"_type":65,"children":742,"markDefs":747,"style":235},"c971c172fb04",[743],{"_key":744,"_type":69,"marks":745,"text":746},"c971c172fb040",[],"Forward compatibility",[],{"_key":749,"_type":65,"children":750,"markDefs":764,"style":74},"05ef553fe43a",[751,755,760],{"_key":752,"_type":69,"marks":753,"text":754},"05ef553fe43a0",[],"Both gRPC and GraphQL have good forward compatibility; that is, it’s easy to update the server in a way that doesn’t break existing clients. This is particularly important for mobile apps that may be out of date, but also necessary in order for ",{"_key":756,"_type":69,"marks":757,"text":759},"05ef553fe43a1",[758],"9f1b681d8a25","SPAs",{"_key":761,"_type":69,"marks":762,"text":763},"05ef553fe43a2",[]," loaded in users’ browser tabs to continue working after a server update.",[765],{"_key":758,"_type":111,"href":766,"reference":12},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSingle-page_application",{"_key":768,"_type":65,"children":769,"markDefs":774,"style":74},"3a0a6c622004",[770],{"_key":771,"_type":69,"marks":772,"text":773},"3a0a6c6220040",[],"In gRPC, you can maintain forward compatibility by numerically ordering fields, adding fields with new numbers, and not changing the types\u002Fnumbers of existing fields. In GraphQL, you can add fields, deprecate old fields with the `@deprecated``` directive (and leave them functioning), and avoid changing optional arguments to be required.",[],{"_key":776,"_type":65,"children":777,"markDefs":782,"style":235},"eccadcc99cbf",[778],{"_key":779,"_type":69,"marks":780,"text":781},"eccadcc99cbf0",[],"Transport",[],{"_key":784,"_type":65,"children":785,"markDefs":859,"style":74},"de4af12b6bfd",[786,790,794,798,802,806,811,815,820,824,829,833,838,842,847,851,856],{"_key":787,"_type":69,"marks":788,"text":789},"de4af12b6bfd0",[],"Both gRPC and GraphQL support the ",{"_key":791,"_type":69,"marks":792,"text":793},"de4af12b6bfd1",[255],"server streaming",{"_key":795,"_type":69,"marks":796,"text":797},"de4af12b6bfd2",[]," data to the client: gRPC has ",{"_key":799,"_type":69,"marks":800,"text":793},"de4af12b6bfd3",[801],"1ecbdfee7c4e",{"_key":803,"_type":69,"marks":804,"text":805},"de4af12b6bfd4",[]," and GraphQL has ",{"_key":807,"_type":69,"marks":808,"text":810},"de4af12b6bfd5",[809],"5e1a30df23e5","Subscriptions",{"_key":812,"_type":69,"marks":813,"text":814},"de4af12b6bfd6",[]," and the directives ",{"_key":816,"_type":69,"marks":817,"text":819},"de4af12b6bfd7",[818],"be78a905453b","@defer",{"_key":821,"_type":69,"marks":822,"text":823},"de4af12b6bfd8",[],", ",{"_key":825,"_type":69,"marks":826,"text":828},"de4af12b6bfd9",[827],"bbf96a4f5f8d","@stream",{"_key":830,"_type":69,"marks":831,"text":832},"de4af12b6bfd10",[],", and ",{"_key":834,"_type":69,"marks":835,"text":837},"de4af12b6bfd11",[836],"0ef1ff008468","@live",{"_key":839,"_type":69,"marks":840,"text":841},"de4af12b6bfd12",[],". gRPC’s HTTP\u002F2 also supports ",{"_key":843,"_type":69,"marks":844,"text":846},"de4af12b6bfd13",[845],"aba5d6d15f9e","client and bidirectional",{"_key":848,"_type":69,"marks":849,"text":850},"de4af12b6bfd14",[]," streaming (although we can’t do bidirectional when one side is a browser). HTTP\u002F2 also has improved performance through ",{"_key":852,"_type":69,"marks":853,"text":855},"de4af12b6bfd15",[854],"17bb86f49cdb","multiplexing",{"_key":857,"_type":69,"marks":858,"text":476},"de4af12b6bfd16",[],[860,862,864,866,867,869,871],{"_key":801,"_type":111,"href":861,"reference":12},"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Fwhat-is-grpc\u002Fcore-concepts\u002F#server-streaming-rpc",{"_key":809,"_type":111,"href":863,"reference":12},"https:\u002F\u002Fspec.graphql.org\u002FOctober2021\u002F#sec-Subscription",{"_key":818,"_type":111,"href":865,"reference":12},"https:\u002F\u002Fgraphql.org\u002Fblog\u002F2020-12-08-improving-latency-with-defer-and-stream-directives\u002F",{"_key":827,"_type":111,"href":865,"reference":12},{"_key":836,"_type":111,"href":868,"reference":12},"https:\u002F\u002Fgithub.com\u002Fn1ru4l\u002Fgraphql-live-query",{"_key":845,"_type":111,"href":870,"reference":12},"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Fwhat-is-grpc\u002Fcore-concepts\u002F#client-streaming-rpc",{"_key":854,"_type":111,"href":872,"reference":12},"https:\u002F\u002Fweb.dev\u002Fperformance-http2\u002F#request-and-response-multiplexing",{"_key":874,"_type":65,"children":875,"markDefs":905,"style":74},"4019030c4678",[876,880,884,888,893,897,902],{"_key":877,"_type":69,"marks":878,"text":879},"4019030c46780",[],"gRPC has ",{"_key":881,"_type":69,"marks":882,"text":883},"4019030c46781",[255],"built-in retries",{"_key":885,"_type":69,"marks":886,"text":887},"4019030c46782",[]," on network failure, whereas in GraphQL, it might be included in your particular client library, like Apollo Client’s ",{"_key":889,"_type":69,"marks":890,"text":892},"4019030c46783",[891],"29a21fb861ca","RetryLink",{"_key":894,"_type":69,"marks":895,"text":896},"4019030c46784",[],". gRPC also has built-in ",{"_key":898,"_type":69,"marks":899,"text":901},"4019030c46785",[900],"3c2cd3c3cdc9","deadlines",{"_key":903,"_type":69,"marks":904,"text":476},"4019030c46786",[],[906,908],{"_key":891,"_type":111,"href":907,"reference":12},"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Freact\u002Fapi\u002Flink\u002Fapollo-link-retry\u002F",{"_key":900,"_type":111,"href":909,"reference":12},"https:\u002F\u002Fgrpc.io\u002Fblog\u002Fdeadlines\u002F",{"_key":911,"_type":65,"children":912,"markDefs":969,"style":74},"299ec7401f77",[913,917,921,925,930,934,939,943,948,952,956,960,965],{"_key":914,"_type":69,"marks":915,"text":916},"299ec7401f770",[],"There are also some limitations of the transports. gRPC is unable to use most ",{"_key":918,"_type":69,"marks":919,"text":920},"299ec7401f771",[255],"API proxies",{"_key":922,"_type":69,"marks":923,"text":924},"299ec7401f772",[]," like ",{"_key":926,"_type":69,"marks":927,"text":929},"299ec7401f773",[928],"79064f829b09","Apigee Edge",{"_key":931,"_type":69,"marks":932,"text":933},"299ec7401f774",[]," that operate on HTTP headers, and when the client is a browser, we need to use ",{"_key":935,"_type":69,"marks":936,"text":938},"299ec7401f775",[937],"0a98cf6ddc50","gRPC-Web proxy",{"_key":940,"_type":69,"marks":941,"text":942},"299ec7401f776",[]," or ",{"_key":944,"_type":69,"marks":945,"text":947},"299ec7401f777",[946],"34f051fd087a","Connect",{"_key":949,"_type":69,"marks":950,"text":951},"299ec7401f778",[]," (while modern browsers do support HTTP\u002F2, there aren’t browser APIs that allow enough control over the requests). By default, GraphQL doesn’t work with ",{"_key":953,"_type":69,"marks":954,"text":955},"299ec7401f779",[255],"GET caching",{"_key":957,"_type":69,"marks":958,"text":959},"299ec7401f7710",[],": much of HTTP caching works on GET requests, and most GraphQL libraries default to using POST. GraphQL has a number of options for using GET, including putting the operation in a query parameter (viable when the operation string isn’t too long), build-time persisted queries (usually just used with private APIs), and ",{"_key":961,"_type":69,"marks":962,"text":964},"299ec7401f7711",[963],"0e4b07cb9311","automatic persisted queries",{"_key":966,"_type":69,"marks":967,"text":968},"299ec7401f7712",[],". Cache directives can be provided at the field level (the shortest value in the whole response is used for the Cache-Control header’s `max-age`).",[970,972,974,976],{"_key":928,"_type":111,"href":971,"reference":12},"https:\u002F\u002Fdocs.apigee.com\u002Fapi-platform\u002Fget-started\u002Fget-started",{"_key":937,"_type":111,"href":973,"reference":12},"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Fplatforms\u002Fweb\u002Fbasics\u002F#configure-the-envoy-proxy",{"_key":946,"_type":111,"href":975,"reference":12},"https:\u002F\u002Fconnect.build\u002Fdocs\u002Fprotocol",{"_key":963,"_type":111,"href":977,"reference":12},"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Fapollo-server\u002Fperformance\u002Fapq\u002F",{"_key":979,"_type":65,"children":980,"markDefs":985,"style":235},"be22f0c1c6f4",[981],{"_key":982,"_type":69,"marks":983,"text":984},"be22f0c1c6f40",[],"Schema and types",[],{"_key":987,"_type":65,"children":988,"markDefs":1045,"style":74},"42c6789313f9",[989,993,997,1001,1006,1010,1015,1019,1024,1028,1033,1037,1042],{"_key":990,"_type":69,"marks":991,"text":992},"42c6789313f90",[],"GraphQL has a schema that the server publishes for client devs and uses to process requests. It defines all the possible queries and mutations and all the data types and their relations to each other (the graph). The schema makes it easy to ",{"_key":994,"_type":69,"marks":995,"text":996},"42c6789313f91",[255],"combine data",{"_key":998,"_type":69,"marks":999,"text":1000},"42c6789313f92",[]," from multiple services. GraphQL has the concepts of schema stitching (imperatively combining multiple GraphQL APIs into a single API that proxies parts of the schema) and ",{"_key":1002,"_type":69,"marks":1003,"text":1005},"42c6789313f93",[1004],"32b281cd9e71","federation",{"_key":1007,"_type":69,"marks":1008,"text":1009},"42c6789313f94",[]," (each downstream API ",{"_key":1011,"_type":69,"marks":1012,"text":1014},"42c6789313f95",[1013],"b3b68f127fda","declares",{"_key":1016,"_type":69,"marks":1017,"text":1018},"42c6789313f96",[]," how to associate shared types, and the gateway ",{"_key":1020,"_type":69,"marks":1021,"text":1023},"42c6789313f97",[1022],"59642d88b138","automatically resolves",{"_key":1025,"_type":69,"marks":1026,"text":1027},"42c6789313f98",[]," a request by making requests to downstream APIs and combining the results) for creating a ",{"_key":1029,"_type":69,"marks":1030,"text":1032},"42c6789313f99",[1031],"92adf74bcb40","supergraph",{"_key":1034,"_type":69,"marks":1035,"text":1036},"42c6789313f910",[]," (a graph of all our data that combines smaller subgraphs \u002F partial schemas). There are also libraries that proxy other protocols to GraphQL, ",{"_key":1038,"_type":69,"marks":1039,"text":1041},"42c6789313f911",[1040],"a8515081746d","including gRPC",{"_key":1043,"_type":69,"marks":1044,"text":476},"42c6789313f912",[],[1046,1048,1050,1052,1054],{"_key":1004,"_type":111,"href":1047,"reference":12},"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Ffederation\u002F",{"_key":1013,"_type":111,"href":1049,"reference":12},"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Ffederation\u002Ffederated-types\u002Ffederated-directives",{"_key":1022,"_type":111,"href":1051,"reference":12},"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Ffederation\u002Fquery-plans",{"_key":1031,"_type":111,"href":1053,"reference":12},"https:\u002F\u002Fwww.apollographql.com\u002Fblog\u002Fannouncement\u002Fbackend\u002Fthe-supergraph-a-new-way-to-think-about-graphql\u002F",{"_key":1040,"_type":111,"href":1055,"reference":12},"https:\u002F\u002Fwww.graphql-mesh.com\u002Fdocs\u002Fhandlers\u002Fgrpc",{"_key":1057,"_type":65,"children":1058,"markDefs":1106,"style":74},"78cfea26eabe",[1059,1063,1067,1071,1076,1080,1085,1088,1093,1097,1102],{"_key":1060,"_type":69,"marks":1061,"text":1062},"78cfea26eabe0",[],"Along with GraphQL’s schema comes further developed ",{"_key":1064,"_type":69,"marks":1065,"text":1066},"78cfea26eabe1",[255],"introspection",{"_key":1068,"_type":69,"marks":1069,"text":1070},"78cfea26eabe2",[],": the ability to query the server in a standard way to determine what its capabilities are. All GraphQL server libraries have introspection, and there are advanced tools based on introspection like ",{"_key":1072,"_type":69,"marks":1073,"text":1075},"78cfea26eabe3",[1074],"54aa193e5074","GraphiQL",{"_key":1077,"_type":69,"marks":1078,"text":1079},"78cfea26eabe4",[],", request linting with ",{"_key":1081,"_type":69,"marks":1082,"text":1084},"78cfea26eabe5",[1083],"0090b7c82454","graphql-eslint",{"_key":1086,"_type":69,"marks":1087,"text":832},"78cfea26eabe6",[],{"_key":1089,"_type":69,"marks":1090,"text":1092},"78cfea26eabe7",[1091],"6065ee0e6a6c","Apollo Studio",{"_key":1094,"_type":69,"marks":1095,"text":1096},"78cfea26eabe8",[],", which includes a query IDE with field autocompletion, linting, autogenerated docs, and search. gRPC has ",{"_key":1098,"_type":69,"marks":1099,"text":1101},"78cfea26eabe9",[1100],"673e401b6913","reflection",{"_key":1103,"_type":69,"marks":1104,"text":1105},"78cfea26eabe10",[],", but it’s not as widespread, and there’s less tooling that uses it.",[1107,1109,1111,1113],{"_key":1074,"_type":111,"href":1108,"reference":12},"http:\u002F\u002Fgraphql.org\u002Fswapi-graphql",{"_key":1083,"_type":111,"href":1110,"reference":12},"https:\u002F\u002Fgithub.com\u002FB2o5T\u002Fgraphql-eslint",{"_key":1091,"_type":111,"href":1112,"reference":12},"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Fstudio\u002F",{"_key":1100,"_type":111,"href":1114,"reference":12},"https:\u002F\u002Fgithub.com\u002Fgrpc\u002Fgrpc\u002Fblob\u002Fmaster\u002Fdoc\u002Fserver-reflection.md",{"_key":1116,"_type":65,"children":1117,"markDefs":1130,"style":74},"4c52fc7b01ea",[1118,1122,1126],{"_key":1119,"_type":69,"marks":1120,"text":1121},"4c52fc7b01ea0",[],"The GraphQL schema enables a ",{"_key":1123,"_type":69,"marks":1124,"text":1125},"4c52fc7b01ea1",[255],"reactive normalized client cache",{"_key":1127,"_type":69,"marks":1128,"text":1129},"4c52fc7b01ea2",[],": because each (nested) object has a type field, types are shared between different queries, and we can tell the client which field to use as an ID for each type, the client can store data objects normalized. This enables advanced client features, such as a query result or optimistic update triggering updates to view components that depend on different queries that include the same object.",[],{"_key":1132,"_type":65,"children":1133,"markDefs":1138,"style":74},"01c1984c5a3f",[1134],{"_key":1135,"_type":69,"marks":1136,"text":1137},"01c1984c5a3f0",[],"There are a few differences between gRPC and GraphQL types:",[],{"_key":1140,"_type":65,"children":1141,"level":124,"listItem":125,"markDefs":1154,"style":74},"2cb5b41fc07f",[1142,1146,1150],{"_key":1143,"_type":69,"marks":1144,"text":1145},"2cb5b41fc07f0",[],"gRPC version 3 (latest as of writing) does not have ",{"_key":1147,"_type":69,"marks":1148,"text":1149},"2cb5b41fc07f1",[255],"required fields",{"_key":1151,"_type":69,"marks":1152,"text":1153},"2cb5b41fc07f2",[],": instead, every field has a default value. In GraphQL, the server can differentiate between a value being present and absent (null), and the schema can indicate that an argument must be present or that a response field will always be present.",[],{"_key":1156,"_type":65,"children":1157,"level":124,"listItem":125,"markDefs":1170,"style":74},"38528b24e6a7",[1158,1162,1166],{"_key":1159,"_type":69,"marks":1160,"text":1161},"38528b24e6a70",[],"In gRPC, there is no standard way to know whether a method will ",{"_key":1163,"_type":69,"marks":1164,"text":1165},"38528b24e6a71",[255],"mutate state",{"_key":1167,"_type":69,"marks":1168,"text":1169},"38528b24e6a72",[]," (vs GraphQL, which separates queries and mutations).",[],{"_key":1172,"_type":65,"children":1173,"level":124,"listItem":125,"markDefs":1182,"style":74},"84e3fae822fd",[1174,1178],{"_key":1175,"_type":69,"marks":1176,"text":1177},"84e3fae822fd0",[255],"Maps",{"_key":1179,"_type":69,"marks":1180,"text":1181},"84e3fae822fd1",[]," are supported in gRPC but not in GraphQL: if you have a data type like `{[key: string] : T}`, you need to use a JSON string type for the whole thing.",[],{"_key":1184,"_type":65,"children":1185,"markDefs":1225,"style":74},"a7569f68769c",[1186,1190,1194,1198,1203,1207,1212,1216,1221],{"_key":1187,"_type":69,"marks":1188,"text":1189},"a7569f68769c0",[],"A downside of GraphQL’s schema and flexible queries is that ",{"_key":1191,"_type":69,"marks":1192,"text":1193},"a7569f68769c1",[255],"rate limiting",{"_key":1195,"_type":69,"marks":1196,"text":1197},"a7569f68769c2",[]," is more complex for public APIs (for private APIs, we can allowlist our persisted queries). Since we can include as many queries as we’d like in a single request, and those queries can ask for arbitrarily nested data, we can’t just limit the number of requests from a client or assign cost to different methods. We need to implement ",{"_key":1199,"_type":69,"marks":1200,"text":1202},"a7569f68769c3",[1201],"512b3a6c8503","cost analysis rate limiting",{"_key":1204,"_type":69,"marks":1205,"text":1206},"a7569f68769c4",[]," on the whole operation, for example by using the ",{"_key":1208,"_type":69,"marks":1209,"text":1211},"a7569f68769c5",[1210],"d5bd0ea92676","graphql-cost-analysis",{"_key":1213,"_type":69,"marks":1214,"text":1215},"a7569f68769c6",[]," library to sum individual field costs and pass them to a ",{"_key":1217,"_type":69,"marks":1218,"text":1220},"a7569f68769c7",[1219],"9ac3f8cb10b1","leaky bucket",{"_key":1222,"_type":69,"marks":1223,"text":1224},"a7569f68769c8",[]," algorithm.",[1226,1228,1230],{"_key":1201,"_type":111,"href":1227,"reference":12},"https:\u002F\u002Fshopify.engineering\u002Frate-limiting-graphql-apis-calculating-query-complexity",{"_key":1210,"_type":111,"href":1229,"reference":12},"https:\u002F\u002Fgithub.com\u002Fpa-bru\u002Fgraphql-cost-analysis",{"_key":1219,"_type":111,"href":1231,"reference":12},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FLeaky_bucket",{"_key":1233,"_type":65,"children":1234,"markDefs":1239,"style":226},"ab6beca40d14",[1235],{"_key":1236,"_type":69,"marks":1237,"text":1238},"ab6beca40d140",[],"Summary",[],{"_key":1241,"_type":65,"children":1242,"markDefs":1247,"style":74},"f84223e47039",[1243],{"_key":1244,"_type":69,"marks":1245,"text":1246},"f84223e470390",[],"Here’s a summary of the topics we’ve covered:",[],{"_key":1249,"_type":65,"children":1250,"markDefs":1255,"style":235},"bf242d96bc8f",[1251],{"_key":1252,"_type":69,"marks":1253,"text":1254},"bf242d96bc8f0",[],"Similarities between gRPC and GraphQL",[],{"_key":1257,"_type":65,"children":1258,"level":124,"listItem":125,"markDefs":1263,"style":74},"d98ab047cf84",[1259],{"_key":1260,"_type":69,"marks":1261,"text":1262},"d98ab047cf840",[],"Typed interfaces with codegen",[],{"_key":1265,"_type":65,"children":1266,"level":124,"listItem":125,"markDefs":1271,"style":74},"8424c53ee648",[1267],{"_key":1268,"_type":69,"marks":1269,"text":1270},"8424c53ee6480",[],"Abstract away the network layer",[],{"_key":1273,"_type":65,"children":1274,"level":124,"listItem":125,"markDefs":1279,"style":74},"09769e2bdeb2",[1275],{"_key":1276,"_type":69,"marks":1277,"text":1278},"09769e2bdeb20",[],"Can have JSON responses",[],{"_key":1281,"_type":65,"children":1282,"level":124,"listItem":125,"markDefs":1287,"style":74},"85ebc91472a4",[1283],{"_key":1284,"_type":69,"marks":1285,"text":1286},"85ebc91472a40",[],"Server streaming",[],{"_key":1289,"_type":65,"children":1290,"level":124,"listItem":125,"markDefs":1295,"style":74},"9059a3d8a3e1",[1291],{"_key":1292,"_type":69,"marks":1293,"text":1294},"9059a3d8a3e10",[],"Good forward compatibility",[],{"_key":1297,"_type":65,"children":1298,"level":124,"listItem":125,"markDefs":1303,"style":74},"b8dcfc436455",[1299],{"_key":1300,"_type":69,"marks":1301,"text":1302},"b8dcfc4364550",[],"Can avoid overfetching",[],{"_key":1305,"_type":65,"children":1306,"markDefs":1310,"style":235},"1d5f48480201",[1307],{"_key":1308,"_type":69,"marks":1309,"text":199},"1d5f484802010",[],[],{"_key":1312,"_type":65,"children":1313,"markDefs":1318,"style":1319},"75558b504bc1",[1314],{"_key":1315,"_type":69,"marks":1316,"text":1317},"75558b504bc10",[],"Strengths",[],"h4",{"_key":1321,"_type":65,"children":1322,"level":124,"listItem":125,"markDefs":1327,"style":74},"40396e356193",[1323],{"_key":1324,"_type":69,"marks":1325,"text":1326},"40396e3561930",[],"Binary format: ",[],{"_key":1329,"_type":65,"children":1330,"level":1335,"listItem":125,"markDefs":1336,"style":74},"049f5f66abc3",[1331],{"_key":1332,"_type":69,"marks":1333,"text":1334},"049f5f66abc30",[],"Faster transfer over network",2,[],{"_key":1338,"_type":65,"children":1339,"level":1335,"listItem":125,"markDefs":1344,"style":74},"c17579050edf",[1340],{"_key":1341,"_type":69,"marks":1342,"text":1343},"c17579050edf0",[],"Faster serializing, parsing, and validation",[],{"_key":1346,"_type":65,"children":1347,"level":1335,"listItem":125,"markDefs":1352,"style":74},"dc24e46e5feb",[1348],{"_key":1349,"_type":69,"marks":1350,"text":1351},"dc24e46e5feb0",[],"However, harder to view and debug than JSON",[],{"_key":1354,"_type":65,"children":1355,"level":124,"listItem":125,"markDefs":1360,"style":74},"6e0c936d6082",[1356],{"_key":1357,"_type":69,"marks":1358,"text":1359},"6e0c936d60820",[],"HTTP\u002F2:",[],{"_key":1362,"_type":65,"children":1363,"level":1335,"listItem":125,"markDefs":1368,"style":74},"d7fe211ea808",[1364],{"_key":1365,"_type":69,"marks":1366,"text":1367},"d7fe211ea8080",[],"Multiplexing",[],{"_key":1370,"_type":65,"children":1371,"level":1335,"listItem":125,"markDefs":1376,"style":74},"aa5171e7cf75",[1372],{"_key":1373,"_type":69,"marks":1374,"text":1375},"aa5171e7cf750",[],"Client and bidirectional streaming",[],{"_key":1378,"_type":65,"children":1379,"level":124,"listItem":125,"markDefs":1384,"style":74},"a0f8892a8f54",[1380],{"_key":1381,"_type":69,"marks":1382,"text":1383},"a0f8892a8f540",[],"Built-in retries and deadlines",[],{"_key":1386,"_type":65,"children":1387,"markDefs":1392,"style":1319},"a5d94bc06c81",[1388],{"_key":1389,"_type":69,"marks":1390,"text":1391},"a5d94bc06c810",[],"Weaknesses",[],{"_key":1394,"_type":65,"children":1395,"level":124,"listItem":125,"markDefs":1408,"style":74},"31287fe60cda",[1396,1400,1404],{"_key":1397,"_type":69,"marks":1398,"text":1399},"31287fe60cda0",[],"Need proxy or ",{"_key":1401,"_type":69,"marks":1402,"text":947},"31287fe60cda1",[1403],"4d964be98012",{"_key":1405,"_type":69,"marks":1406,"text":1407},"31287fe60cda2",[]," to use from the browser",[1409],{"_key":1403,"_type":111,"href":1410,"reference":12},"https:\u002F\u002Fconnect.build\u002Fdocs\u002Fprotocol\u002F",{"_key":1412,"_type":65,"children":1413,"level":124,"listItem":125,"markDefs":1418,"style":74},"5bad76da7973",[1414],{"_key":1415,"_type":69,"marks":1416,"text":1417},"5bad76da79730",[],"Unable to use most API proxies",[],{"_key":1420,"_type":65,"children":1421,"level":124,"listItem":125,"markDefs":1426,"style":74},"153c54724cc1",[1422],{"_key":1423,"_type":69,"marks":1424,"text":1425},"153c54724cc10",[],"No standard way to know whether a method will mutate state",[],{"_key":1428,"_type":65,"children":1429,"markDefs":1433,"style":235},"e631afc94618",[1430],{"_key":1431,"_type":69,"marks":1432,"text":208},"e631afc946180",[],[],{"_key":1435,"_type":65,"children":1436,"markDefs":1440,"style":1319},"1e053d870f89",[1437],{"_key":1438,"_type":69,"marks":1439,"text":1317},"1e053d870f890",[],[],{"_key":1442,"_type":65,"children":1443,"level":124,"listItem":125,"markDefs":1448,"style":74},"f37f11991b1a",[1444],{"_key":1445,"_type":69,"marks":1446,"text":1447},"f37f11991b1a0",[],"Client determines which data fields it wants returned. Results in: ",[],{"_key":1450,"_type":65,"children":1451,"level":1335,"listItem":125,"markDefs":1456,"style":74},"32dbd794d96d",[1452],{"_key":1453,"_type":69,"marks":1454,"text":1455},"32dbd794d96d0",[],"No underfetching",[],{"_key":1458,"_type":65,"children":1459,"level":1335,"listItem":125,"markDefs":1464,"style":74},"23700ec75dce",[1460],{"_key":1461,"_type":69,"marks":1462,"text":1463},"23700ec75dce0",[],"Team decoupling",[],{"_key":1466,"_type":65,"children":1467,"level":1335,"listItem":125,"markDefs":1472,"style":74},"b3b200abf3c8",[1468],{"_key":1469,"_type":69,"marks":1470,"text":1471},"b3b200abf3c80",[],"Increased visibility",[],{"_key":1474,"_type":65,"children":1475,"level":124,"listItem":125,"markDefs":1480,"style":74},"81b6233fd19b",[1476],{"_key":1477,"_type":69,"marks":1478,"text":1479},"81b6233fd19b0",[],"Easier to combine data from multiple services",[],{"_key":1482,"_type":65,"children":1483,"level":124,"listItem":125,"markDefs":1488,"style":74},"b0e6fa97ab57",[1484],{"_key":1485,"_type":69,"marks":1486,"text":1487},"b0e6fa97ab570",[],"Further developed introspection and tooling",[],{"_key":1490,"_type":65,"children":1491,"level":124,"listItem":125,"markDefs":1496,"style":74},"665947bea37a",[1492],{"_key":1493,"_type":69,"marks":1494,"text":1495},"665947bea37a0",[],"Declarative data fetching",[],{"_key":1498,"_type":65,"children":1499,"level":124,"listItem":125,"markDefs":1504,"style":74},"74f9ff151ca9",[1500],{"_key":1501,"_type":69,"marks":1502,"text":1503},"74f9ff151ca90",[],"Reactive normalized client cache",[],{"_key":1506,"_type":65,"children":1507,"markDefs":1511,"style":1319},"b115410eba1d",[1508],{"_key":1509,"_type":69,"marks":1510,"text":1391},"b115410eba1d0",[],[],{"_key":1513,"_type":65,"children":1514,"level":124,"listItem":125,"markDefs":1519,"style":74},"44d849bb0264",[1515],{"_key":1516,"_type":69,"marks":1517,"text":1518},"44d849bb02640",[],"If we already have gRPC services that can be exposed to the public, it takes more backend work to add a GraphQL server.",[],{"_key":1521,"_type":65,"children":1522,"level":124,"listItem":125,"markDefs":1527,"style":74},"5ae2f8937a99",[1523],{"_key":1524,"_type":69,"marks":1525,"text":1526},"5ae2f8937a990",[],"HTTP GET caching doesn’t work by default.",[],{"_key":1529,"_type":65,"children":1530,"level":124,"listItem":125,"markDefs":1535,"style":74},"0769197aa8c4",[1531],{"_key":1532,"_type":69,"marks":1533,"text":1534},"0769197aa8c40",[],"Rate limiting is more complex for public APIs.",[],{"_key":1537,"_type":65,"children":1538,"level":124,"listItem":125,"markDefs":1543,"style":74},"c6409d33fa6f",[1539],{"_key":1540,"_type":69,"marks":1541,"text":1542},"c6409d33fa6f0",[],"Maps aren’t supported.",[],{"_key":1545,"_type":65,"children":1546,"level":124,"listItem":125,"markDefs":1551,"style":74},"fb493c568ae0",[1547],{"_key":1548,"_type":69,"marks":1549,"text":1550},"fb493c568ae00",[],"Inefficient text-based transport",[],{"_key":1553,"_type":65,"children":1554,"markDefs":1559,"style":226},"93a484445060",[1555],{"_key":1556,"_type":69,"marks":1557,"text":1558},"93a4844450600",[],"Verdict",[],{"_key":1561,"_type":65,"children":1562,"markDefs":1567,"style":235},"f341dd0ad4e2",[1563],{"_key":1564,"_type":69,"marks":1565,"text":1566},"f341dd0ad4e20",[],"Server-to-server",[],{"_key":1569,"_type":65,"children":1570,"markDefs":1575,"style":74},"e43bce6a555b",[1571],{"_key":1572,"_type":69,"marks":1573,"text":1574},"e43bce6a555b0",[],"In server-to-server communication, where low latency is often important, and more types of streaming are sometimes necessary, gRPC is the clear standard. However, there are cases in which we may find some of the benefits of GraphQL more important:",[],{"_key":1577,"_type":65,"children":1578,"level":124,"listItem":125,"markDefs":1591,"style":74},"94e4830ad9f0",[1579,1583,1587],{"_key":1580,"_type":69,"marks":1581,"text":1582},"94e4830ad9f00",[],"We’re using GraphQL ",{"_key":1584,"_type":69,"marks":1585,"text":1005},"94e4830ad9f01",[1586],"a896eb091d99",{"_key":1588,"_type":69,"marks":1589,"text":1590},"94e4830ad9f02",[]," or schema stitching to create a supergraph of all our business data and decide to have GraphQL subgraphs published by each service. We create two supergraph endpoints: one external to be called by clients and one internal to be called by services. In this case, it may not be worth it for services to also expose a gRPC API, because they can all be conveniently reached through the supergraph.",[1592],{"_key":1586,"_type":111,"href":1047,"reference":12},{"_key":1594,"_type":65,"children":1595,"level":124,"listItem":125,"markDefs":1600,"style":74},"93c316bd54d9",[1596],{"_key":1597,"_type":69,"marks":1598,"text":1599},"93c316bd54d90",[],"We know our services’ data fields are going to be changing and want field-level visibility on usage so that we can remove old deprecated fields (and aren’t stuck with maintaining them forever).",[],{"_key":1602,"_type":65,"children":1603,"markDefs":1644,"style":74},"fa7c29cb1c21",[1604,1608,1613,1617,1622,1626,1631,1635,1640],{"_key":1605,"_type":69,"marks":1606,"text":1607},"fa7c29cb1c210",[71],"There’s also the question of whether we should be doing server-to-server communication ourselves at all. For data fetching (GraphQL’s queries), it’s the fastest way to get a response, but for modifying data (mutations), things like Martin Fowler’s “synchronous calls considered harmful” (see ",{"_key":1609,"_type":69,"marks":1610,"text":1612},"fa7c29cb1c211",[71,1611],"3304b8e30a34","sidebar here",{"_key":1614,"_type":69,"marks":1615,"text":1616},"fa7c29cb1c212",[71],") have led to using async, event-driven architecture with either choreography or orchestration between services. ",{"_key":1618,"_type":69,"marks":1619,"text":1621},"fa7c29cb1c213",[71,1620],"d3eaec5eefd0","Microservices Patterns",{"_key":1623,"_type":69,"marks":1624,"text":1625},"fa7c29cb1c214",[71]," recommends using the latter in most cases, and to maintain DX and development speed, we need a ",{"_key":1627,"_type":69,"marks":1628,"text":1630},"fa7c29cb1c215",[71,1629],"863f488659f3","code-based orchestrator",{"_key":1632,"_type":69,"marks":1633,"text":1634},"fa7c29cb1c216",[71]," instead of a DSL-based one. And once we’re working in a code-based orchestrator like Temporal, we no longer make network requests ourselves—the platform reliably handles it for us. In ",{"_key":1636,"_type":69,"marks":1637,"text":1639},"fa7c29cb1c217",[71,1638],"2801cfcf13f3","my opinion",{"_key":1641,"_type":69,"marks":1642,"text":1643},"fa7c29cb1c218",[71],", that’s the future.",[1645,1647,1649,1651],{"_key":1611,"_type":111,"href":1646,"reference":12},"https:\u002F\u002Fwww.martinfowler.com\u002Farticles\u002Fmicroservices.html?ref=wellarchitected#SynchronousCallsConsideredHarmful",{"_key":1620,"_type":111,"href":1648,"reference":12},"https:\u002F\u002Fwww.manning.com\u002Fbooks\u002Fmicroservices-patterns",{"_key":1629,"_type":111,"href":1650,"reference":12},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=6lSuDRRFgyY",{"_key":1638,"_type":111,"href":1652,"reference":12},"https:\u002F\u002Ftwitter.com\u002Florendsr\u002Fstatus\u002F1419880024929415173",{"_key":1654,"_type":65,"children":1655,"markDefs":1660,"style":235},"6cb3c51c8cf9",[1656],{"_key":1657,"_type":69,"marks":1658,"text":1659},"6cb3c51c8cf90",[],"Client-server",[],{"_key":1662,"_type":65,"children":1663,"markDefs":1668,"style":74},"e3615a060ff5",[1664],{"_key":1665,"_type":69,"marks":1666,"text":1667},"e3615a060ff50",[],"In client-server communication, latency is high. We want to be able to get all the data we need in a single round trip, have flexibility in what data we fetch for different views, and have powerful caching, so GraphQL is the clear winner. However, there are cases in which we may choose to use gRPC instead:\n",[],{"_key":1670,"_type":65,"children":1671,"level":124,"listItem":125,"markDefs":1676,"style":74},"e4b10057062e",[1672],{"_key":1673,"_type":69,"marks":1674,"text":1675},"e4b10057062e0",[],"We already have a gRPC API that can be used, and the cost of adding a GraphQL server in front of that isn’t worth the benefits.",[],{"_key":1678,"_type":65,"children":1679,"level":124,"listItem":125,"markDefs":1684,"style":74},"6e52915a9182",[1680],{"_key":1681,"_type":69,"marks":1682,"text":1683},"6e52915a91820",[],"JSON is not a good fit for the data (e.g. we’re sending a significant amount of binary data).",[],{"_key":1686,"_type":65,"children":1687,"markDefs":1725,"style":74},"14dd9e439c7a",[1688,1692,1697,1701,1706,1710,1714,1717,1722],{"_key":1689,"_type":69,"marks":1690,"text":1691},"14dd9e439c7a0",[],"I hope this article aided your understanding of the protocols and when to use them! If you’d like to learn more about GraphQL, check out their ",{"_key":1693,"_type":69,"marks":1694,"text":1696},"14dd9e439c7a1",[1695],"49c5d14da343","site",{"_key":1698,"_type":69,"marks":1699,"text":1700},"14dd9e439c7a2",[]," or my book, ",{"_key":1702,"_type":69,"marks":1703,"text":1705},"14dd9e439c7a3",[1704,71],"06903980db7f","The GraphQL Guide",{"_key":1707,"_type":69,"marks":1708,"text":1709},"14dd9e439c7a4",[],". For more about gRPC, here’s their ",{"_key":1711,"_type":69,"marks":1712,"text":1696},"14dd9e439c7a5",[1713],"bfc814d13ad9",{"_key":1715,"_type":69,"marks":1716,"text":651},"14dd9e439c7a6",[],{"_key":1718,"_type":69,"marks":1719,"text":1721},"14dd9e439c7a7",[1720],"56aaa9128be6","documentation",{"_key":1723,"_type":69,"marks":1724,"text":476},"14dd9e439c7a8",[],[1726,1728,1729,1731],{"_key":1695,"_type":111,"href":1727,"reference":12},"https:\u002F\u002Fgraphql.org\u002Flearn\u002F",{"_key":1704,"_type":111,"href":164,"reference":12},{"_key":1713,"_type":111,"href":1730,"reference":12},"https:\u002F\u002Fgrpc.io\u002Fabout\u002F",{"_key":1720,"_type":111,"href":1732,"reference":12},"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Fwhat-is-grpc\u002Fintroduction\u002F",{"_key":1734,"_type":65,"children":1735,"markDefs":1740,"style":74},"dd3c70466203",[1736],{"_key":1737,"_type":69,"marks":1738,"text":1739},"dd3c704662030",[71],"Thanks to Marc-André Giroux, Uri Goldshtein, Sashko Stubailo, Morgan Kestner, Andrew Ingram, Lenny Burdette, Martin Bonnin, James Watkins-Harvey, Josh Wise, Patrick Rachford, and Jay Miller for reading drafts of this.",[],true,"2022\u002F11\u002F28","We dig into two of the most popular API protocols to see where they work best. ",{"_type":53,"asset":1745},{"_ref":1746,"_type":56},"image-c11378ed5715f1b600f7c43c88c4d70bcfc5164a-2560x1344-jpg",{"code":1748,"language":1749},"\u003C!-- wp:paragraph -->\n\u003Cp>\u003Cem>TLDR: Use GraphQL for client-server communication and gRPC for server-to-server. See the Verdict section for exceptions to this rule.\u003C\u002Fem>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>I've read a lot of comparisons of these two protocols and wanted to write one that is comprehensive and impartial. (Well, as impartial as I and my reviewers can make it 😄.) I was inspired by the release of \u003Ca href=\"https:\u002F\u002Fbuf.build\u002Fblog\u002Fconnect-web-protobuf-grpc-in-the-browser\">connect-web\u003C\u002Fa> (a TypeScript gRPC client that can be used in the browser) and a popular HN post entitled \u003Ca href=\"https:\u002F\u002Fnews.ycombinator.com\u002Fitem?id=32366759\">GraphQL kinda sucks\u003C\u002Fa>. My personal history of communication protocols built on top of \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FOSI_model\">layer 7\u003C\u002Fa>:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>REST (Rails and Express)\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>➡️ DDP (Meteor's \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fmeteor\u002Fmeteor\u002Fblob\u002Fdevel\u002Fpackages\u002Fddp\u002FDDP.md\">WebSocket protocol\u003C\u002Fa>)\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>➡️ GraphQL (which I wrote \u003Ca href=\"https:\u002F\u002Fgraphql.guide\u002F\">a book\u003C\u002Fa> about)\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>➡️ gRPC (which I use at \u003Ca href=\"https:\u002F\u002Ftemporal.io\u002F\">Temporal\u003C\u002Fa>)\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:heading {\"level\":1} -->\n\u003Ch1 id=\"h-background\">Background\u003C\u002Fh1>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Ca href=\"http:\u002F\u002Fgrpc.io\">gRPC\u003C\u002Fa> was released in 2016 by Google as an efficient and developer-friendly method of server-to-server communication. \u003Ca href=\"http:\u002F\u002Fgraphql.org\">GraphQL\u003C\u002Fa> was released in 2015 by Meta as an efficient and developer-friendly method of client-server communication. They both have significant advantages over REST and have a lot in common. We’ll spend most of the article comparing their traits, and then we’ll summarize each protocol’s strengths and weaknesses. At the end, we’ll know why each is such a good fit for its intended domain and when we might want to use one in the other’s domain.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 id=\"h-comparing-grpc-and-graphql-features\">Comparing gRPC and GraphQL features\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-interface-design\">Interface design\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Both gRPC and GraphQL are \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FInterface_description_language\">Interface Description Languages\u003C\u002Fa> (IDLs) that describe how two computers can talk to each other. They work across different programming languages, and we can use codegen tools to generate typed interfaces in a number of languages. IDLs abstract away the transport layer; GraphQL is transport-agnostic but generally used over HTTP, while gRPC uses HTTP\u002F2. We don’t need to know about transport-level details like the method, path, query parameters, and body format in as REST. We just need to know a \u003Cstrong>single endpoint\u003C\u002Fstrong> that we use our higher-level client library to communicate with.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-message-format\">Message format\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Message size matters because smaller messages generally take less time to send over the network. gRPC uses \u003Ca href=\"https:\u002F\u002Fdevelopers.google.com\u002Fprotocol-buffers\">protocol buffers\u003C\u002Fa> (a.k.a. protobufs), a \u003Cstrong>binary format\u003C\u002Fstrong> that just includes values, while&nbsp; GraphQL uses JSON, which is text-based and includes field names in addition to values. The binary format combined with less information sent generally results in gRPC messages being smaller than GraphQL messages. (While an efficient binary format is \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fesseswann\u002Fgraphql-binary\">feasible\u003C\u002Fa> in GraphQL, it’s rarely used and isn’t supported by most of the libraries and tooling.)\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Another aspect that affects message size is \u003Cstrong>overfetching\u003C\u002Fstrong>: whether we can request only specific fields or will always receive all fields (“overfetching” fields we don’t need). GraphQL always specifies in the request which fields are desired, and in gRPC, we can use \u003Ca href=\"https:\u002F\u002Fnetflixtechblog.com\u002Fpractical-api-design-at-netflix-part-1-using-protobuf-fieldmask-35cfdc606518\">FieldMasks\u003C\u002Fa> as reusable filters for requests.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Another benefit to gRPC’s binary format is faster serializing and parsing of messages compared to that of GraphQL’s text messages. The downside is that it’s harder to view and debug than the human-readable JSON. We at Temporal use protobuf’s \u003Ca href=\"https:\u002F\u002Fdevelopers.google.com\u002Fprotocol-buffers\u002Fdocs\u002Fproto3#json\">JSON format\u003C\u002Fa> by default for the visibility benefit to developer experience. (That loses the efficiency that came with the binary format, but users who value the efficiency more can switch to binary.)\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-defaults\">Defaults\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>gRPC also doesn’t include default values in messages, which GraphQL can do for arguments but not request fields or response types. This is another factor in gRPC messages’ smaller size. It also affects the DX of consuming a gRPC API. There’s no distinction between leaving an input field unset and setting it to the default value, and the default value is based on the type of the field. All booleans default to false, and all numbers and enums default to 0. We can’t default the `behavior` enum input field to `BEHAVIOR_FOO = 2`—we have to either put the default value first (`BEHAVIOR_FOO = 0`), which means it will always be the default in the future, or we follow the recommended practice of having a `BEHAVIOR_UNSPECIFIED = 0` enum value:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>enum Behavior {\n  BEHAVIOR_UNSPECIFIED = 0;\n  BEHAVIOR_FOO = 1;\n  BEHAVIOR_BAR = 2;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The API provider needs to communicate what \u003Ccode>UNSPECIFIED\u003C\u002Fcode> means (by documenting “unspecified will use the default behavior, which is currently \u003Ccode>FOO\u003C\u002Fcode>”), the consumer needs to think about whether the server default behavior may change in the future (if the server saves the provided \u003Ccode>UNSPECIFIED\u003C\u002Fcode> \u002F 0 value in some business entity the consumer is creating, and the server later changes the default behavior, the entity will start behaving differently) and whether that would be desired. If it wouldn’t be desired, the client needs to set the value to the current default. Here’s an example scenario:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>service ExampleGrpcService {\n  rpc CreateEntity (CreateEntityRequest) returns (CreateEntityResponse) {}\n}\n\nmessage CreateEntityRequest {\n  string name = 1;\n  Behavior behavior = 2;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>If we do:&nbsp;\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>const request = new CreateEntityRequest({ name: “my entity” })\nservice.CreateEntity(request)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>we’ll be sending \u003Ccode>BEHAVIOR_UNSPECIFIED\u003C\u002Fcode>, which depending on the server implementation and future changes, might mean \u003Ccode>BEHAVIOR_FOO\u003C\u002Fcode> now and \u003Ccode>BEHAVIOR_BAR\u003C\u002Fcode> later. Or we can do:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>const request = new CreateEntityRequest({ name: “my entity”, behavior: Behavior.BEHAVIOR_FOO })\nservice.CreateEntity(request)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>to be certain the behavior is stored as \u003Ccode>FOO\u003C\u002Fcode> and will remain \u003Ccode>FOO\u003C\u002Fcode>.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The equivalent GraphQL schema would be:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>type Mutation {\n  createEntity(name: String, behavior: Behavior = FOO): Entity\n}\n\nenum Behavior {\n  FOO\n  BAR\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>When we don’t include \u003Ccode>behavior\u003C\u002Fcode> in the request, the server code will receive and store FOO as the value, matching the \u003Ccode>= FOO\u003C\u002Fcode> default in the schema above.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>graphqlClient.request(`\n  mutation  {\n    createEntity(name: “my entity”)\n  }\n`\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>It’s simpler than the gRPC version to know what will happen if the field isn’t provided, and we don’t need to consider whether to pass the default value ourselves.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Other \u003Ca href=\"https:\u002F\u002Fdevelopers.google.com\u002Fprotocol-buffers\u002Fdocs\u002Fproto3#default\">types’ defaults\u003C\u002Fa> have other quirks. For numbers, sometimes the default 0 is a valid value, and sometimes it will mean a different default value. For booleans, the default false results in negatively named fields. When we’re naming a boolean variable while coding, we use the positive name. For instance, we’d usually declare \u003Ccode>let retryable = true\u003C\u002Fcode> rather than \u003Ccode>let nonRetryable = false\u003C\u002Fcode>. People generally find the former more readable, as the latter takes an extra step to understand the double negative (“\u003Ccode>notRetryable\u003C\u002Fcode> is \u003Ccode>false\u003C\u002Fcode>, so it’s retryable”). But if we have a gRPC API in which we want the default state to be retryable, then we have to name the field \u003Ccode>nonRetryable\u003C\u002Fcode>, because the default of an \u003Ccode>retryable\u003C\u002Fcode> field would be \u003Ccode>false\u003C\u002Fcode>, like all booleans in gRPC.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-request-format\">Request format\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In gRPC, we call methods one at a time. If we need more data than a single method provides, we need to call multiple methods. And if we need response data from the first method in order to know which method to call next, then we’re doing multiple \u003Ca href=\"https:\u002F\u002Fgraphql.guide\u002Fbackground\u002Flatency\u002F\">round trips\u003C\u002Fa> in a row. Unless we’re in the same data center as the server, that causes a significant delay. This issue is called \u003Cstrong>underfetching.\u003C\u002Fstrong>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This is one of the issues GraphQL was designed to solve. It’s particularly important over high-latency mobile phone connections to be able to get all the data you need in a single request. In GraphQL, we send a string (called a \u003Cem>document\u003C\u002Fem>) with our request that includes all the methods (called \u003Cem>queries\u003C\u002Fem> and \u003Cem>mutations\u003C\u002Fem>) we want to call and all the nested data we need based on the first-level results. Some of the nested data may require subsequent requests from the server to the database, but they’re usually located in the same data center, which should have sub-millisecond network latency.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>GraphQL’s request flexibility lets front-end and back-end teams become \u003Cstrong>less coupled\u003C\u002Fstrong>. Instead of the front-end developers waiting for the back-end developers to add more data to a method’s response (so the client can receive the data in a single request), the front-end developers can add more queries or nested result fields to their request. When there’s a GraphQL API that covers the organization’s entire data graph, the front-end team gets blocked waiting for backend changes much less frequently.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The fact that the GraphQL request specifies all desired data fields means that the client can use \u003Cstrong>declarative data fetching\u003C\u002Fstrong>: instead of imperatively fetching data (like calling `grpcClient.callMethod()```), we declare the data we need next to our view component, and the GraphQL client library combines those pieces into a single request and provides the data to the components when the response arrives and later when the data changes. The parallel for \u003Ca href=\"https:\u002F\u002Fwww.javascriptstuff.com\u002Fview-libraries\u002F\">view libraries\u003C\u002Fa> in web development is using React instead of jQuery: declaring how our components should look and having them automatically update when data changes instead of imperatively manipulating the DOM with jQuery.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Another effect GraphQL’s request format has is \u003Cstrong>increased visibility\u003C\u002Fstrong>: the server sees each field that’s requested. We can track field usage and see when clients have stopped using deprecated fields, so that we know when we can remove them as opposed to forever supporting something that we said we’d get rid of. Tracking is built into common tools like \u003Ca href=\"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Fgraphos\u002Fmetrics\u002Ffield-usage\u002F\">Apollo GraphOS\u003C\u002Fa> and \u003Ca href=\"https:\u002F\u002Fstellate.co\u002Fgraphql-metrics\">Stellate\u003C\u002Fa>.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-forward-compatibility\">Forward compatibility\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Both gRPC and GraphQL have good forward compatibility; that is, it’s easy to update the server in a way that doesn’t break existing clients. This is particularly important for mobile apps that may be out of date, but also necessary in order for \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSingle-page_application\">SPAs\u003C\u002Fa> loaded in users’ browser tabs to continue working after a server update.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In gRPC, you can maintain forward compatibility by numerically ordering fields, adding fields with new numbers, and not changing the types\u002Fnumbers of existing fields. In GraphQL, you can add fields, deprecate old fields with the `@deprecated``` directive (and leave them functioning), and avoid changing optional arguments to be required.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-transport\">Transport\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Both gRPC and GraphQL support the \u003Cstrong>server streaming\u003C\u002Fstrong> data to the client: gRPC has \u003Ca href=\"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Fwhat-is-grpc\u002Fcore-concepts\u002F#server-streaming-rpc\">server streaming\u003C\u002Fa> and GraphQL has \u003Ca href=\"https:\u002F\u002Fspec.graphql.org\u002FOctober2021\u002F#sec-Subscription\">Subscriptions\u003C\u002Fa> and the directives \u003Ca href=\"https:\u002F\u002Fgraphql.org\u002Fblog\u002F2020-12-08-improving-latency-with-defer-and-stream-directives\u002F\">@defer\u003C\u002Fa>, \u003Ca href=\"https:\u002F\u002Fgraphql.org\u002Fblog\u002F2020-12-08-improving-latency-with-defer-and-stream-directives\u002F\">@stream\u003C\u002Fa>, and \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fn1ru4l\u002Fgraphql-live-query\">@live\u003C\u002Fa>. gRPC’s HTTP\u002F2 also supports \u003Ca href=\"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Fwhat-is-grpc\u002Fcore-concepts\u002F#client-streaming-rpc\">client and bidirectional\u003C\u002Fa> streaming (although we can’t do bidirectional when one side is a browser). HTTP\u002F2 also has improved performance through \u003Ca href=\"https:\u002F\u002Fweb.dev\u002Fperformance-http2\u002F#request-and-response-multiplexing\">multiplexing\u003C\u002Fa>.&nbsp;\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>gRPC has \u003Cstrong>built-in retries\u003C\u002Fstrong> on network failure, whereas in GraphQL, it might be included in your particular client library, like Apollo Client’s \u003Ca href=\"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Freact\u002Fapi\u002Flink\u002Fapollo-link-retry\u002F\">RetryLink\u003C\u002Fa>. gRPC also has built-in \u003Ca href=\"https:\u002F\u002Fgrpc.io\u002Fblog\u002Fdeadlines\u002F\">deadlines\u003C\u002Fa>.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>There are also some limitations of the transports. gRPC is unable to use most \u003Cstrong>API proxies\u003C\u002Fstrong> like \u003Ca href=\"https:\u002F\u002Fdocs.apigee.com\u002Fapi-platform\u002Fget-started\u002Fget-started\">Apigee Edge\u003C\u002Fa> that operate on HTTP headers, and when the client is a browser, we need to use \u003Ca href=\"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Fplatforms\u002Fweb\u002Fbasics\u002F#configure-the-envoy-proxy\">gRPC-Web proxy\u003C\u002Fa> or \u003Ca href=\"https:\u002F\u002Fconnect.build\u002Fdocs\u002Fprotocol\">Connect\u003C\u002Fa> (while modern browsers do support HTTP\u002F2, there aren’t browser APIs that allow enough control over the requests). By default, GraphQL doesn’t work with \u003Cstrong>GET caching\u003C\u002Fstrong>: much of HTTP caching works on GET requests, and most GraphQL libraries default to using POST. GraphQL has a number of options for using GET, including putting the operation in a query parameter (viable when the operation string isn’t too long), build-time persisted queries (usually just used with private APIs), and \u003Ca href=\"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Fapollo-server\u002Fperformance\u002Fapq\u002F\">automatic persisted queries\u003C\u002Fa>. Cache directives can be provided at the field level (the shortest value in the whole response is used for the Cache-Control header’s `max-age`).\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-schema-and-types\">Schema and types\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>GraphQL has a schema that the server publishes for client devs and uses to process requests. It defines all the possible queries and mutations and all the data types and their relations to each other (the graph). The schema makes it easy to \u003Cstrong>combine data\u003C\u002Fstrong> from multiple services. GraphQL has the concepts of schema stitching (imperatively combining multiple GraphQL APIs into a single API that proxies parts of the schema) and \u003Ca href=\"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Ffederation\u002F\">federation\u003C\u002Fa> (each downstream API \u003Ca href=\"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Ffederation\u002Ffederated-types\u002Ffederated-directives\">declares\u003C\u002Fa> how to associate shared types, and the gateway \u003Ca href=\"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Ffederation\u002Fquery-plans\">automatically resolves\u003C\u002Fa> a request by making requests to downstream APIs and combining the results) for creating a \u003Ca href=\"https:\u002F\u002Fwww.apollographql.com\u002Fblog\u002Fannouncement\u002Fbackend\u002Fthe-supergraph-a-new-way-to-think-about-graphql\u002F\">supergraph\u003C\u002Fa> (a graph of all our data that combines smaller subgraphs \u002F partial schemas). There are also libraries that proxy other protocols to GraphQL, \u003Ca href=\"https:\u002F\u002Fwww.graphql-mesh.com\u002Fdocs\u002Fhandlers\u002Fgrpc\">including gRPC\u003C\u002Fa>.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Along with GraphQL’s schema comes further developed \u003Cstrong>introspection\u003C\u002Fstrong>: the ability to query the server in a standard way to determine what its capabilities are. All GraphQL server libraries have introspection, and there are advanced tools based on introspection like \u003Ca href=\"http:\u002F\u002Fgraphql.org\u002Fswapi-graphql\">GraphiQL\u003C\u002Fa>, request linting with \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FB2o5T\u002Fgraphql-eslint\">graphql-eslint\u003C\u002Fa>, and \u003Ca href=\"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Fstudio\u002F\">Apollo Studio\u003C\u002Fa>, which includes a query IDE with field autocompletion, linting, autogenerated docs, and search. gRPC has \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fgrpc\u002Fgrpc\u002Fblob\u002Fmaster\u002Fdoc\u002Fserver-reflection.md\">reflection\u003C\u002Fa>, but it’s not as widespread, and there’s less tooling that uses it.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The GraphQL schema enables a \u003Cstrong>reactive normalized client cache\u003C\u002Fstrong>: because each (nested) object has a type field, types are shared between different queries, and we can tell the client which field to use as an ID for each type, the client can store data objects normalized. This enables advanced client features, such as a query result or optimistic update triggering updates to view components that depend on different queries that include the same object.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>There are a few differences between gRPC and GraphQL types:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>gRPC version 3 (latest as of writing) does not have \u003Cstrong>required fields\u003C\u002Fstrong>: instead, every field has a default value. In GraphQL, the server can differentiate between a value being present and absent (null), and the schema can indicate that an argument must be present or that a response field will always be present.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>In gRPC, there is no standard way to know whether a method will \u003Cstrong>mutate state\u003C\u002Fstrong> (vs GraphQL, which separates queries and mutations).\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>\u003Cstrong>Maps\u003C\u002Fstrong> are supported in gRPC but not in GraphQL: if you have a data type like `{[key: string] : T}`, you need to use a JSON string type for the whole thing.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>A downside of GraphQL’s schema and flexible queries is that \u003Cstrong>rate limiting\u003C\u002Fstrong> is more complex for public APIs (for private APIs, we can allowlist our persisted queries). Since we can include as many queries as we’d like in a single request, and those queries can ask for arbitrarily nested data, we can’t just limit the number of requests from a client or assign cost to different methods. We need to implement \u003Ca href=\"https:\u002F\u002Fshopify.engineering\u002Frate-limiting-graphql-apis-calculating-query-complexity\">cost analysis rate limiting\u003C\u002Fa> on the whole operation, for example by using the \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fpa-bru\u002Fgraphql-cost-analysis\">graphql-cost-analysis\u003C\u002Fa> library to sum individual field costs and pass them to a \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FLeaky_bucket\">leaky bucket\u003C\u002Fa> algorithm.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 id=\"h-summary\">Summary\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Here’s a summary of the topics we’ve covered:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-similarities-between-grpc-and-graphql\">Similarities between gRPC and GraphQL\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>Typed interfaces with codegen\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Abstract away the network layer\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Can have JSON responses\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Server streaming\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Good forward compatibility\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Can avoid overfetching\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-grpc\">gRPC\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:heading {\"level\":4} -->\n\u003Ch4 id=\"h-strengths\">Strengths\u003C\u002Fh4>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>Binary format:\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>Faster transfer over network\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Faster serializing, parsing, and validation\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>However, harder to view and debug than JSON\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>HTTP\u002F2:\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>Multiplexing\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Client and bidirectional streaming\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Built-in retries and deadlines\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:heading {\"level\":4} -->\n\u003Ch4 id=\"h-weaknesses\">Weaknesses\u003C\u002Fh4>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>Need proxy or \u003Ca href=\"https:\u002F\u002Fconnect.build\u002Fdocs\u002Fprotocol\u002F\">Connect\u003C\u002Fa> to use from the browser\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Unable to use most API proxies\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>No standard way to know whether a method will mutate state\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-graphql\">GraphQL\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:heading {\"level\":4} -->\n\u003Ch4 id=\"h-strengths-1\">Strengths\u003C\u002Fh4>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>Client determines which data fields it wants returned. Results in:\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>No underfetching\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Team decoupling\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Increased visibility\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Easier to combine data from multiple services\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Further developed introspection and tooling\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Declarative data fetching\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Reactive normalized client cache\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:heading {\"level\":4} -->\n\u003Ch4 id=\"h-weaknesses-1\">Weaknesses\u003C\u002Fh4>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>If we already have gRPC services that can be exposed to the public, it takes more backend work to add a GraphQL server.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>HTTP GET caching doesn’t work by default.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Rate limiting is more complex for public APIs.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Maps aren’t supported.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>Inefficient text-based transport\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 id=\"h-verdict\">Verdict\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-server-to-server\">Server-to-server\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In server-to-server communication, where low latency is often important, and more types of streaming are sometimes necessary, gRPC is the clear standard. However, there are cases in which we may find some of the benefits of GraphQL more important:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>We’re using GraphQL \u003Ca href=\"https:\u002F\u002Fwww.apollographql.com\u002Fdocs\u002Ffederation\u002F\">federation\u003C\u002Fa> or schema stitching to create a supergraph of all our business data and decide to have GraphQL subgraphs published by each service. We create two supergraph endpoints: one external to be called by clients and one internal to be called by services. In this case, it may not be worth it for services to also expose a gRPC API, because they can all be conveniently reached through the supergraph.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>We know our services’ data fields are going to be changing and want field-level visibility on usage so that we can remove old deprecated fields (and aren’t stuck with maintaining them forever).\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Cem>There’s also the question of whether we should be doing server-to-server communication ourselves at all. For data fetching (GraphQL’s queries), it’s the fastest way to get a response, but for modifying data (mutations), things like Martin Fowler’s “synchronous calls considered harmful” (see \u003Ca href=\"https:\u002F\u002Fwww.martinfowler.com\u002Farticles\u002Fmicroservices.html?ref=wellarchitected#SynchronousCallsConsideredHarmful\">sidebar here\u003C\u002Fa>) have led to using async, event-driven architecture with either choreography or orchestration between services. \u003Ca href=\"https:\u002F\u002Fwww.manning.com\u002Fbooks\u002Fmicroservices-patterns\">Microservices Patterns\u003C\u002Fa> recommends using the latter in most cases, and to maintain DX and development speed, we need a \u003Ca href=\"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=6lSuDRRFgyY\">code-based orchestrator\u003C\u002Fa> instead of a DSL-based one. And once we’re working in a code-based orchestrator like Temporal, we no longer make network requests ourselves—the platform reliably handles it for us. In \u003Ca href=\"https:\u002F\u002Ftwitter.com\u002Florendsr\u002Fstatus\u002F1419880024929415173\">my opinion\u003C\u002Fa>, that’s the future.\u003C\u002Fem>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-client-server\">Client-server\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In client-server communication, latency is high. We want to be able to get all the data we need in a single round trip, have flexibility in what data we fetch for different views, and have powerful caching, so GraphQL is the clear winner. However, there are cases in which we may choose to use gRPC instead:\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003C!-- wp:list-item -->\n\u003Cli>We already have a gRPC API that can be used, and the cost of adding a GraphQL server in front of that isn’t worth the benefits.\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\n\n\u003C!-- wp:list-item -->\n\u003Cli>JSON is not a good fit for the data (e.g. we’re sending a significant amount of binary data).\u003C\u002Fli>\n\u003C!-- \u002Fwp:list-item -->\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:separator -->\n\u003Chr class=\"wp-block-separator has-alpha-channel-opacity\"\u002F>\n\u003C!-- \u002Fwp:separator -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>I hope this article aided your understanding of the protocols and when to use them! If you’d like to learn more about GraphQL, check out their \u003Ca href=\"https:\u002F\u002Fgraphql.org\u002Flearn\u002F\">site\u003C\u002Fa> or my book, \u003Ca href=\"https:\u002F\u002Fgraphql.guide\u002F\">\u003Cem>The GraphQL Guide\u003C\u002Fem>\u003C\u002Fa>. For more about gRPC, here’s their \u003Ca href=\"https:\u002F\u002Fgrpc.io\u002Fabout\u002F\">site\u003C\u002Fa> and \u003Ca href=\"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Fwhat-is-grpc\u002Fintroduction\u002F\">documentation\u003C\u002Fa>.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Cem>Thanks to Marc-André Giroux, Uri Goldshtein, Sashko Stubailo, Morgan Kestner, Andrew Ingram, Lenny Burdette, Martin Bonnin, James Watkins-Harvey, Josh Wise, Patrick Rachford, and Jay Miller for reading drafts of this.\u003C\u002Fem>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->","html","2022-11-28T15:26:09.000Z",{"current":1752},"when-to-use-grpc-vs-graphql",[1754,1762,1767,1771],{"_createdAt":1755,"_id":1756,"_rev":1757,"_type":1758,"_updatedAt":1755,"slug":1759,"title":1761},"2023-05-23T16:43:21Z","wp-tagcat-api","9HpbCsT2tq0xwozQfkc4ih","blogTag",{"current":1760},"api","API",{"_createdAt":1755,"_id":1763,"_rev":1757,"_type":1758,"_updatedAt":1755,"slug":1764,"title":1766},"wp-tagcat-code-for-a-living",{"current":1765},"code-for-a-living","Code for a Living",{"_createdAt":1755,"_id":1768,"_rev":1757,"_type":1758,"_updatedAt":1755,"slug":1769,"title":1770},"wp-tagcat-graphql",{"current":1770},"graphql",{"_createdAt":1755,"_id":1772,"_rev":1757,"_type":1758,"_updatedAt":1755,"slug":1773,"title":199},"wp-tagcat-grpc",{"current":1774},"grpc","When to use gRPC vs GraphQL",[1777,1783,1789,1795],{"_id":1778,"publishedAt":1779,"slug":1780,"sponsored":12,"title":1782},"76c9771b-34e6-4d98-8641-ecefc711f0ef","2026-07-06T15:23:34.559Z",{"_type":10,"current":1781},"when-the-sensor-starts-thinking-snortml-agentic-ai-and-the-evolving-architecture-of-intrusion-detection","When the sensor starts thinking: SnortML, agentic AI, and the evolving architecture of intrusion detection",{"_id":1784,"publishedAt":1785,"slug":1786,"sponsored":12,"title":1788},"28e560af-f0aa-4d46-bd90-f435ad604aa7","2026-06-26T14:00:27.102Z",{"_type":10,"current":1787},"paging-charity-how-can-engineering-leaders-avoid-becoming-bond-villains","Paging Charity! How can engineering leaders avoid becoming Bond villains?",{"_id":1790,"publishedAt":1791,"slug":1792,"sponsored":12,"title":1794},"4b22c2a3-3779-4966-93eb-5230391dbdce","2026-06-23T14:08:58.595Z",{"_type":10,"current":1793},"your-ai-shipped-a-backend-that-boots-that-is-the-whole-problem","Your AI shipped a backend that boots. That is the whole problem.",{"_id":1796,"publishedAt":1797,"slug":1798,"sponsored":12,"title":1800},"5cf362e1-fe7b-45af-b69c-914731c6a052","2026-06-23T14:00:00.000Z",{"_type":10,"current":1799},"the-2026-developer-survey-is-now-open-for-human-developers-only","The 2026 Developer Survey is now open (for human developers only)!",{"data":1802,"sourceMap":-1},{"count":1803,"lastTimestamp":1804},12,"2023-05-25T09:47:55Z"]