|
| 1 | +--- |
| 2 | +authors: [joost-farla] |
| 3 | +tags: [api, api-design, graphql, rest] |
| 4 | +image: /img/graphql-onder-de-loep.png |
| 5 | +description: | |
| 6 | + GraphQL komt regelmatig voorbij in gesprekken over API's binnen de |
| 7 | + overheid, maar wat is het eigenlijk precies? In het eerste deel van deze |
| 8 | + vierdelige serie maken we kennis met de bouwstenen van GraphQL en laten we |
| 9 | + zien waarom het geen "betere REST" is, maar een fundamenteel ander model |
| 10 | + voor het ontsluiten van data. |
| 11 | +--- |
| 12 | + |
| 13 | +# GraphQL onder de loep (deel 1): een kennismaking |
| 14 | + |
| 15 | + |
| 16 | + |
| 17 | +In gesprekken over API's binnen de overheid komt GraphQL regelmatig voorbij. |
| 18 | +Sommige organisaties experimenteren ermee, grote internationale platformen zoals |
| 19 | +GitHub en Shopify bieden er publieke API's mee aan, en tegelijkertijd is het |
| 20 | +binnen de Nederlandse overheid nog nauwelijks zichtbaar: het |
| 21 | +[API-register](/blog/2025/06/18/het-nieuwe-api-register) op deze site is bewust |
| 22 | +REST-only, en de |
| 23 | +[REST API Design Rules](/kennisbank/api-ontwikkeling/standaarden/api-design-rules) |
| 24 | +(ADR) zijn, de naam zegt het al, geschreven voor REST. |
| 25 | + |
| 26 | +Reden genoeg om GraphQL eens grondig onder de loep te nemen. In een serie van |
| 27 | +vier blogposts verkennen we wat GraphQL is, wat het oplost, welke uitdagingen |
| 28 | +het met zich meebrengt en, als kernvraag, wanneer je het wel en wanneer je het |
| 29 | +beter niet kunt gebruiken. |
| 30 | + |
| 31 | +<!-- truncate --> |
| 32 | + |
| 33 | +:::success[TL;DR] |
| 34 | + |
| 35 | +GraphQL is een getypeerde querytaal en runtime voor API's: de client beschrijft |
| 36 | +exact welke data hij nodig heeft en krijgt precies dat terug, via één endpoint. |
| 37 | +Dat is een fundamenteel ander model dan REST, dat draait om resources en de |
| 38 | +semantiek van HTTP. Dit paradigmaverschil verklaart zowel de voordelen als de |
| 39 | +uitdagingen van GraphQL. |
| 40 | + |
| 41 | +::: |
| 42 | + |
| 43 | +## Wat is GraphQL? |
| 44 | + |
| 45 | +[GraphQL](https://graphql.org/) is een querytaal voor API's, gecombineerd met |
| 46 | +een schemataal om een sterk getypeerd datamodel te beschrijven en een runtime |
| 47 | +die queries tegen dat model uitvoert. Het is in 2012 ontwikkeld bij Facebook, |
| 48 | +dat worstelde met de datavoorziening van zijn mobiele apps, en in 2015 open |
| 49 | +source gemaakt. Sinds eind 2018 wordt de specificatie beheerd door de |
| 50 | +onafhankelijke [GraphQL Foundation](https://graphql.org/foundation/), onderdeel |
| 51 | +van de Linux Foundation. In september 2025 verscheen de |
| 52 | +[September 2025 Edition](https://graphql.org/blog/2025-09-08-september-edition/), |
| 53 | +de eerste nieuwe editie sinds oktober 2021. |
| 54 | + |
| 55 | +## De bouwstenen |
| 56 | + |
| 57 | +### Het schema |
| 58 | + |
| 59 | +Het hart van elke GraphQL-API is het schema, geschreven in de Schema Definition |
| 60 | +Language (SDL). Het schema beschrijft welke types er bestaan, hoe ze samenhangen |
| 61 | +en welke queries mogelijk zijn. Een sterk vereenvoudigd voorbeeld, losjes |
| 62 | +gebaseerd op het API-register: |
| 63 | + |
| 64 | +```graphql |
| 65 | +type Organisatie { |
| 66 | + id: ID! |
| 67 | + naam: String! |
| 68 | + apis: [Api!]! |
| 69 | +} |
| 70 | + |
| 71 | +type Api { |
| 72 | + id: ID! |
| 73 | + titel: String! |
| 74 | + versie: String! |
| 75 | + organisatie: Organisatie! |
| 76 | +} |
| 77 | + |
| 78 | +type Query { |
| 79 | + organisatie(id: ID!): Organisatie |
| 80 | + organisaties: [Organisatie!]! |
| 81 | + apis: [Api!]! |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +Het uitroepteken markeert verplichte (non-nullable) velden. Let op de relaties: |
| 86 | +een `Organisatie` heeft `apis`, en een `Api` verwijst terug naar zijn |
| 87 | +`organisatie`. Het schema vormt zo een _graph_ van samenhangende types; vandaar |
| 88 | +de naam. |
| 89 | + |
| 90 | +### Queries |
| 91 | + |
| 92 | +De client stelt een query samen die qua vorm het gewenste antwoord spiegelt: |
| 93 | + |
| 94 | +```graphql |
| 95 | +{ |
| 96 | + organisatie(id: "min-bzk") { |
| 97 | + naam |
| 98 | + apis { |
| 99 | + titel |
| 100 | + versie |
| 101 | + } |
| 102 | + } |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +De server antwoordt met exact de gevraagde velden, niets meer en niets minder: |
| 107 | + |
| 108 | +```json |
| 109 | +{ |
| 110 | + "data": { |
| 111 | + "organisatie": { |
| 112 | + "naam": "Ministerie van BZK", |
| 113 | + "apis": [ |
| 114 | + { "titel": "Adressenregister API", "versie": "2.1.0" }, |
| 115 | + { "titel": "Organisaties API", "versie": "1.0.3" } |
| 116 | + ] |
| 117 | + } |
| 118 | + } |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +### Variabelen |
| 123 | + |
| 124 | +In de praktijk schrijven clients geen letterlijke waarden in de query, maar |
| 125 | +benoemde operaties met variabelen: |
| 126 | + |
| 127 | +```graphql |
| 128 | +query ApisVanOrganisatie($id: ID!) { |
| 129 | + organisatie(id: $id) { |
| 130 | + naam |
| 131 | + apis { |
| 132 | + titel |
| 133 | + } |
| 134 | + } |
| 135 | +} |
| 136 | +``` |
| 137 | + |
| 138 | +```json |
| 139 | +{ "id": "min-bzk" } |
| 140 | +``` |
| 141 | + |
| 142 | +De query wordt daarmee een herbruikbaar, valideerbaar document dat los van de |
| 143 | +invoer bestaat. Onthoud deze vorm: in deel 2 zien we dat zulke benoemde |
| 144 | +documenten de basis vormen voor beheersmaatregelen zoals persisted queries. |
| 145 | + |
| 146 | +### Mutations en subscriptions |
| 147 | + |
| 148 | +Schrijfoperaties heten _mutations_. Ze zijn in het schema expliciet gescheiden |
| 149 | +van queries en geven, net als queries, precies de gevraagde velden terug: |
| 150 | + |
| 151 | +```graphql |
| 152 | +mutation { |
| 153 | + registreerApi( |
| 154 | + input: { titel: "Vergunningen API", organisatieId: "gm0363" } |
| 155 | + ) { |
| 156 | + id |
| 157 | + titel |
| 158 | + } |
| 159 | +} |
| 160 | +``` |
| 161 | + |
| 162 | +Daarnaast kent GraphQL _subscriptions_: de server pusht wijzigingen naar de |
| 163 | +client zodra die zich voordoen, een patroon dat aansluit bij |
| 164 | +[Event Driven Architecture](/kennisbank/api-ontwikkeling/architectuur/eda). In |
| 165 | +de rest van deze serie richten we ons op het bevragen van data; het ontwerp van |
| 166 | +mutations en subscriptions laten we verder buiten beschouwing. |
| 167 | + |
| 168 | +### Resolvers |
| 169 | + |
| 170 | +Aan de serverkant wordt elk veld ingevuld door een _resolver_: een functie die |
| 171 | +weet hoe de waarde van dat ene veld opgehaald moet worden. Hoe de data achter de |
| 172 | +schermen is opgeslagen (één database, meerdere services, een externe API) is |
| 173 | +voor de client onzichtbaar. Dat maakt GraphQL ook geschikt als |
| 174 | +_orkestratielaag_: een schema dat gegevens uit meerdere bestaande API's |
| 175 | +combineert en in samenhang bevraagbaar maakt, zonder die bronnen te vervangen. |
| 176 | +Op dat patroon komen we in deel 4 terug. |
| 177 | + |
| 178 | +### Introspectie |
| 179 | + |
| 180 | +Een GraphQL-API is zelfbeschrijvend: via een standaard introspectie-query kan |
| 181 | +elke client het volledige schema opvragen. Daarop bouwt een rijk ecosysteem van |
| 182 | +tooling, zoals interactieve explorers (GraphiQL), automatische documentatie en |
| 183 | +codegeneratie voor clients. |
| 184 | + |
| 185 | +## Dezelfde vraag in REST |
| 186 | + |
| 187 | +Hoe zou de bovenstaande informatiebehoefte er met een klassieke REST-API |
| 188 | +uitzien? Waarschijnlijk ongeveer zo: |
| 189 | + |
| 190 | +```http |
| 191 | +GET /organisaties/min-bzk |
| 192 | +GET /organisaties/min-bzk/apis |
| 193 | +``` |
| 194 | + |
| 195 | +De eerste call levert de volledige organisatie-representatie op, inclusief |
| 196 | +adresgegevens, contactinformatie en andere velden die we in dit scenario niet |
| 197 | +nodig hebben. Dat heet _overfetching_. Levert de tweede call alleen |
| 198 | +samenvattingen op, dan is per API mogelijk nog een extra call nodig voor de |
| 199 | +details: _underfetching_, met meerdere roundtrips tot gevolg. |
| 200 | + |
| 201 | +Dit is het probleem waarvoor GraphQL is ontworpen. In één request, in één |
| 202 | +roundtrip, precies de benodigde data, ongeacht hoeveel types de vraag raakt. |
| 203 | +Voor Facebook, met trage mobiele netwerken en een nieuws-feed vol diep geneste, |
| 204 | +onderling verbonden data, was dat de bestaansreden. |
| 205 | + |
| 206 | +REST kan hetzelfde bereiken, met zorgvuldig ontworpen resources, `fields`- of |
| 207 | +`expand`-parameters, of een specifiek op een scherm toegesneden endpoint. Het |
| 208 | +verschil is dat GraphQL deze flexibiliteit _generiek_ biedt, terwijl je haar in |
| 209 | +REST per geval ontwerpt. Op die afweging komen we in deel 2 uitgebreid terug. |
| 210 | + |
| 211 | +## Een ander paradigma, geen "betere REST" |
| 212 | + |
| 213 | +Het is verleidelijk GraphQL te zien als "REST, maar dan handiger". Dat doet |
| 214 | +beide tekort. Het verschil wordt zichtbaar zodra je naar het HTTP-verkeer kijkt. |
| 215 | +Een minimale variant van de benoemde operatie uit de paragraaf over variabelen |
| 216 | +gaat zo over de lijn: |
| 217 | + |
| 218 | +```http |
| 219 | +POST /graphql HTTP/1.1 |
| 220 | +Content-Type: application/json |
| 221 | +
|
| 222 | +{ |
| 223 | + "query": "query Naam($id: ID!) { organisatie(id: $id) { naam } }", |
| 224 | + "variables": { "id": "min-bzk" } |
| 225 | +} |
| 226 | +``` |
| 227 | + |
| 228 | +Eén endpoint, vrijwel altijd `POST`, en de eigenlijke vraag zit in de body. Voor |
| 229 | +het netwerk is elk GraphQL-request identiek; wat er gevraagd wordt, is alleen |
| 230 | +zichtbaar voor wie de body parseert. Zet de twee modellen naast elkaar: |
| 231 | + |
| 232 | +- **REST** modelleert _resources_ en leunt op de semantiek van HTTP: werkwoorden |
| 233 | + (`GET`, `POST`, `PUT`, `DELETE`), statuscodes, unieke URI's per resource, |
| 234 | + cache-headers en content negotiation. Het netwerk "begrijpt" een REST-API: een |
| 235 | + cache, proxy of loadbalancer kan zinvol reageren op wat er langskomt. |
| 236 | +- **GraphQL** modelleert een _graph_ van types en gebruikt HTTP vooral als |
| 237 | + transport. Vrijwel al het verkeer gaat via één endpoint (meestal |
| 238 | + `POST /graphql`), antwoorden komen doorgaans terug met status `200 OK`, ook |
| 239 | + als er iets misging, en fouten staan als data in de responsebody. |
| 240 | + |
| 241 | +In onze eerdere blogpost over |
| 242 | +[OpenAPI 3.1](/blog/2025/07/10/openapi-31-in-zicht) constateerden we dit al: |
| 243 | +GraphQL gebruikt HTTP wel als protocol, maar volgt niet het HTTP-model waarop |
| 244 | +specificaties als |
| 245 | +[OpenAPI](/kennisbank/api-ontwikkeling/standaarden/openapi-specification) zijn |
| 246 | +gebaseerd. De |
| 247 | +[GraphQL over HTTP-specificatie](https://graphql.github.io/graphql-over-http/), |
| 248 | +die vastlegt hoe GraphQL-verkeer over HTTP hoort te lopen, is op het moment van |
| 249 | +schrijven (medio 2026) nog een working draft; het transportgedrag ligt formeel |
| 250 | +dus nog niet vast. |
| 251 | + |
| 252 | +Dit paradigmaverschil is de rode draad van deze serie. Vrijwel elk voordeel én |
| 253 | +vrijwel elke uitdaging van GraphQL is erop terug te voeren: wat je wint aan |
| 254 | +flexibiliteit in de bevraging, moet je elders opnieuw organiseren. Denk aan |
| 255 | +caching, autorisatie en beheersing van de belasting. |
| 256 | + |
| 257 | +## De rest van deze serie |
| 258 | + |
| 259 | +<!-- TODO: bij publicatie van deel 2, 3 en 4 hier telkens de link naar het |
| 260 | +verschenen deel toevoegen --> |
| 261 | + |
| 262 | +In de komende delen gaan we de diepte in: |
| 263 | + |
| 264 | +- **Deel 2** behandelt de kernbelofte van GraphQL (flexibel bevragen als |
| 265 | + oplossing voor over- en underfetching) en de prijs die daar tegenover staat: |
| 266 | + onvoorspelbare performance en een groter aanvalsoppervlak, en hoe je daar |
| 267 | + limieten aan stelt. |
| 268 | +- **Deel 3** duikt in de praktijk van schema-ontwerp: paginering, filtering, |
| 269 | + union types, custom scalars, autorisatie en content negotiation. |
| 270 | +- **Deel 4** brengt alles samen in een afwegingskader: wanneer is GraphQL een |
| 271 | + logische keuze, en wanneer ben je met REST beter af, zeker binnen de context |
| 272 | + van de Nederlandse overheid en de ADR. |
| 273 | + |
| 274 | +Een compacte referentie over GraphQL, met de status binnen de overheid en de |
| 275 | +belangrijkste specificaties, staat in de |
| 276 | +[kennisbank](/kennisbank/api-ontwikkeling/standaarden/graphql). |
0 commit comments