# GraphQL

## Hvad det er

GraphQL er et forespørgselssprog til API'er sammen med den runtime, der udfører
forespørgslerne på serveren. Klienten sender et dokument, der nævner præcis de
felter, den vil have, og serveren svarer med data i samme form. Specifikationen
beskriver det som et forespørgselssprog og en eksekveringsmotor til
klient-server-applikationer. Den fortæller, at GraphQL blev skabt i 2012, at
arbejdet med det som åben standard begyndte i 2015, og at det siden 2019 har
været en leverance fra GraphQL Specification Project under Joint Development
Foundation. Den seneste udgave er fra september 2025.

Alt udgår fra det skema, en tjeneste offentliggør: et sæt typer, hver med
navngivne felter, og hvert felt med sin egen type. De indbyggede skalartyper er
`Int`, `Float`, `String`, `Boolean` og `ID`, og en tjeneste kan tilføje sine
egne skalarer, for eksempel én til UUID'er. Enhver type kan være null, medmindre
den er markeret med `!`, så en `String!` kan aldrig være null, og lister skrives
i firkantede parenteser: `[LineItem!]!` er en liste, der altid er til stede og
aldrig indeholder null. Skemaet kan også selv forespørges. Gennem
introspektionsfelterne `__schema` og `__type` kan en klient spørge en tjeneste,
hvad den tilbyder, og udviklerværktøjer og klientbiblioteker bygger netop på
det.

Der findes tre slags operationer. En `query` er med specifikationens ord »a
read-only fetch«, altså en hentning, der kun læser; en `mutation` er en
skrivning efterfulgt af en hentning; og en `subscription` er en langvarig
forespørgsel, der leverer data som svar på en række hændelser over tid. På
serveren står en funktion bag hvert felt, som regel kaldet en resolver, og den
leverer feltets værdi. Tjenesten validerer først en operation mod skemaet og
kalder først derefter resolverne for de felter, operationen har valgt.
Topniveaufelterne i en mutation køres ét ad gangen i stedet for parallelt, så to
skrivninger i samme forespørgsel ikke kan overhale hinanden.

Specifikationen foreskriver hverken en transport eller et serialiseringsformat.
HTTP er det mest almindelige valg, og dér ligger et GraphQL-API på én enkelt
URL, som regel med endelsen `/graphql`, og svarer i JSON, det mest almindelige
format. Klienten sender en POST, hvis brødtekst er JSON med `query`-teksten og
om nødvendigt dens `variables`. En server kan også tage imod GET til queries,
men en mutation skal altid sendes som POST. Svaret har resultatet under `data`
og alt, hvad der gik galt, under `errors`. GraphQL Foundations vejledning om alt
dette følger en GraphQL-over-HTTP-specifikation, der stadig er et udkast.

## Hvorfor det betyder noget

I de fleste HTTP-API'er er det serveren, der bestemmer, hvad hvert endpoint
returnerer. En skærm, der skal bruge en ordres status og kundens navn, får enten
hele ordren og hele kunden, altså mere, end den bad om, eller må sende én
forespørgsel pr. ressource og selv sætte svarene sammen. Specifikationen kalder
de to problemer over-fetching og under-fetching. GraphQL vender det om: klienten
spørger på niveau med de enkelte felter og får præcis de felter tilbage, hverken
mere eller mindre. Én forespørgsel kan også følge relationer, fra en ordre til
dens ordrelinjer og videre til kunden, fordi forespørgslen har samme form som de
data, den får tilbage.

Typesystemet betaler sig to gange. Hver operation valideres mod skemaet, før
nogen resolver kører, så et felt med en stavefejl eller et argument af forkert
type afvises samlet, før noget overhovedet er udført, og værktøjer kan fange de
samme fejl, mens koden stadig bliver skrevet. Og da en klient kun får de felter,
den har valgt, koster et nyt felt ingenting for de eksisterende klienter. Det
gør det praktisk at videreudvikle et API uden versionsnumre: et nyt felt kommer
til ved siden af det gamle, det gamle markeres med `@deprecated` og en
begrundelse, det forbliver gyldigt, så eksisterende klienter ikke går i stykker,
og det fjernes, når ingen klient bruger det længere.

Omkostningerne er reelle, og de fleste kommer af, at man træder uden for HTTP's
eget maskineri. Entiteter identificeres ikke længere af URL'er, og alt går til
én adresse, som regel med POST, mens det er GET-forespørgsler, der typisk regnes
for cachebare som standard. Den caching, et REST-API får forærende fra
mellemleddene, skal bygges op igen med vilje. Statuskoden fortæller ikke længere
hele historien, for et svar med både data og fejl er stadig en succes på
HTTP-niveau. Klienten sammensætter nu forespørgsler, serveren aldrig har set, så
en enkelt forespørgsel kan bede om meget arbejde, og at beskytte serveren mod
det er tjenestens opgave, ikke noget, GraphQL gør for den. Resolvere pr. felt er
lette at skrive, men skrevet naivt bliver de meget snakkesalige over for
databasen bag dem.

## Sådan ser det ud i praksis

En lille del af et skema for ordrer, hver med ordrelinjer og en kunde:

```graphql
type Query {
  order(id: ID!): Order
}

type Order {
  id: ID!
  status: String!
  items: [LineItem!]!
  customer: Customer
}

type LineItem {
  name: String!
  quantity: Int!
}

type Customer {
  name: String!
}
```

En skærm, der viser status, ordrelinjerne og kundens navn, sender denne
operation til `/graphql` i brødteksten af en POST med `{"id": "7421"}` som
variabler:

```graphql
query OrderSummary($id: ID!) {
  order(id: $id) {
    status
    items {
      name
      quantity
    }
    customer {
      name
    }
  }
}
```

Antag, at ordren og dens linjer bliver hentet, men at tjenesten bag `customer`
er nede. Svaret indeholder stadig alt, hvad der kunne leveres. Det fejlede felt
kommer tilbage som null, og en post i `errors` fortæller, hvad der gik galt,
hvor i dokumentet det skete (linje 8, kolonne 5 er `customer`), og hvilken sti i
svaret det berører:

```json
{
  "data": {
    "order": {
      "status": "open",
      "items": [{ "name": "notebook", "quantity": 2 }],
      "customer": null
    }
  },
  "errors": [
    {
      "message": "Customer service unavailable",
      "locations": [{ "line": 8, "column": 5 }],
      "path": ["order", "customer"]
    }
  ]
}
```

Fordi `customer` er erklæret, så det kan være null, standser skaden ved det
felt. Havde det været `Customer!`, var fejlen forplantet op til forælderen, og
hele ordren var kommet tilbage som null. Og fordi `data` ikke er null, svarer
serveren med en 2xx-status, selv om der er en fejl: HTTP har ingen statuskode
for delvis succes. En klient, der kun ser på statuskoden, vil behandle svaret
som komplet.

Det modsatte design, hvor serveren ejer formen på hvert svar, er det, et projekt
i sitets portefølje byggede. I dets
[JSON-passthrough-arkitektur fra Postgres til API'et](/da/portfolio/designed-a-json-passthrough-from-postgres-to-api/)
samler PostgreSQL-funktioner hele svaret som JSON, og Go-handleren sender det
videre uændret, så når man ændrer, hvad en funktion returnerer, ændrer man også,
hvad klienten modtager, uden at nogen retter en linje handler-kode. Dér bygger
frontenden direkte på den form, databasen giver den. I GraphQL er det klienten,
der nævner felterne, og serveren skal være klar til at levere enhver kombination
af dem, som skemaet tillader.

## Hvor det går galt

**Ét felt, tusind databasekald.** Resolvere er små funktioner, én pr. felt, og
en naiv tjeneste kalder dem ét objekt ad gangen. En liste over ordrer med hver
ordres kunde koster så ét databasekald til listen og ét mere for hver ordre i
den. GraphQL Foundations vejledning kalder det N+1-problemet og peger på
batching: de opslag, der bliver bedt om inden for et kort tidsrum, samles og
sendes til databasen som én forespørgsel, med et værktøj som Facebooks
DataLoader.

**Én forespørgsel beder om alt.** Fordi det er klienten, der skriver
operationen, kan ét dokument indlejre venners venner mange niveauer ned, gentage
et dyrt felt under hundrede aliasser eller pakke mange operationer ind i én
forespørgsel. Hver af dem er gyldig GraphQL, og hver af dem kan lægge beslag på
serveren. Forsvaret ligger hos tjenesten: paginér listefelter, afvis
operationer, der overskrider en maksimal dybde eller bredde, før udførelsen
begynder, sæt loft over aliasser og batchstørrelse, og giv for finere kontrol
felterne en vægt og afvis enhver operation, hvis anslåede pris overstiger
budgettet, en teknik kendt som query complexity analysis, som specifikationen
selv ikke giver nogen regler for. Et API, der kun betjener sine egne klienter,
kan gå videre med trusted documents, så kun operationer, der er gemt på forhånd
og slås op efter hash, overhovedet må køre.

**200 OK skjuler fejlen.** Et svar med fejl kan stadig være en 2xx, som i
eksemplet ovenfor. Med den ældre medietype `application/json` går vejledningen
videre: en 2xx anbefales, selv når en gyldig forespørgsel ikke kan udføres, og
nogle ældre servere returnerer også 2xx ved valideringsfejl. En klient, og
overvågningen omkring den, må læse `errors` og ikke kun statuskoden. Den nyere
medietype `application/graphql-response+json` giver statuskoden mening igen, da
en gyldig forespørgsel, hvis udførelse mislykkes, så får en 4xx eller 5xx, og
klienter bør bede om den i deres `Accept`-header.

**Cachingen holder op med at virke.** Hver operation er en POST til den samme
URL, så de caches, der sidder mellem klient og server, et CDN medregnet, ser én
adresse og en metode, de som standard ikke cacher. Det hjælper at sende queries
som GET, men et langt dokument kan overskride den URL-længde, som browsere og
CDN'er accepterer. Persisted queries løser begge problemer: klienten sender en
hash, og serveren slår det fulde dokument op, før det valideres og udføres.
Overalt, hvor svar caches over HTTP, skal følsomme svar caches privat eller slet
ikke.

**Et felt forsvinder.** Klienter vælger felter ét for ét, så hvis et felt
fjernes eller omdøbes, går enhver klient, der stadig vælger det, i stykker, og
ikke stille og roligt: en operation, der nævner et ukendt felt, fejler
valideringen som helhed og giver slet ingen data. Specifikationen holder
forældede felter gyldige netop for, at eksisterende klienter ikke skal gå i
stykker. Tilføj erstatningen først, markér det gamle felt med `@deprecated` og
en begrundelse, der peger på det nye, og fjern først det gamle felt, når intet
længere vælger det.

## Læs mere

- [GraphQL Specification Versions](https://spec.graphql.org/) — alle udgaver af
  specifikationen, fra juli 2015 til udgaven fra september 2025, plus
  arbejdsudkastet: sproget, typesystemet, validering, udførelse og fejl.
- [Serving over HTTP](https://graphql.org/learn/serving-over-http/) — det ene
  endpoint, POST og GET, de to medietyper for svar og reglerne for statuskoder
  for hver af dem.
- [Performance](https://graphql.org/learn/performance/) — GET til queries,
  persisted queries, N+1-problemet og batching.
- [Security](https://graphql.org/learn/security/) — trusted documents, grænser
  for dybde, bredde og batches, query complexity analysis samt introspektion og
  fejldetaljer i produktion.

<https://engineer.company/da/glossary/graphql/>
