Uge 38: GraphQL
- Kenneth H Sørensen
- Sep 20
- 6 min read

The basics
Jeg vil gennemgå de grundlæggende begreber i GraphQL for at forstå, hvordan det fungerer, og reflektere over forskelle i forhold til REST.
Intro til GraphQL
GraphQL
Et query-sprog for API'er - klient beskriver præcis hvilke data den ønsker
Et runtime på serveren - udfører queries ved hjælp af en type-defineret kontrakt (schema)
Ikke bundet til en database - kan bruges med SQL, NoSQL, API-kald, filer eller andet
Felt-for-felt eksekvering
klient anmoder om præcis data
server leverer via funktioner / metoder (resolvers)
Kernekoncepter (uafhængigt af værktøjer)
Schema - definerer types og fields - kontrakten mellem klient og server
Resolvers - funktioner/metoder der leverer de enkelte felters værdier
Context - et objekt delt pr. request, f.eks med auth-info, DB-forbindelser, caches
Execution - flow er altid:
Query
Valider
Kald resolvers
Byg JSON (samme form som query)
Eksempel - hent eget navn
Problem / behov
En klient vil kende navnet på den bruger, der er logget ind.
Query
GraphQL gør det muligt at spørge præcist om de felter, der er brug for.
{
me {
name
}
}GraphQL forespørgsel skrevet i GraphQL-sproget
Sproget klienten bruger for at sige “jeg vil have felt name fra me”
Request
Hele HTTP-requesten, som bruges til sende query til serveren.
Klient sender en HTTP POST til serverens GraphQL-endpoint (f.eks /graphql)
F.eks fra browser, mobil (ofte via biblioteker som Apollo Client) eller fra test-værktøjer som Postman under udvikling
POST /graphql HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer <jwt-token>{
"query": "{ me { name } }"
}Header
POST /graphql: endpointet for GraphQL
Content-Type: application/json: body (content) er JSON
Authorization: Bearer ...: JWT-token til auth
Body
Indeholder feltet "query", hvor GraphQL-sproget { me { name } } er skrevet som en streng
Schema - typer og felter
Et schema definerer, hvilke felter en klient kan spørge efter.
type Query {
me: User
}
type User {
name: String
}Query (root type for READ) har field me, som returnere User
User har field name, som returnerer String
Schema er en kontrakt - beskriver form og tilgængelige felter (IKKE hvordan data hentes).
Resolvers - logikken bag felterne
En resolver er en funktion / metode, der leverer værdien til et felt under eksekvering.
Hvert felt i schema kan have en resolver
Default-resolver i GraphQL kan automatisk læse værdier direkte fra objektets properties - eksplicit resolver er ikke altid nødvendig
Når der er brug for logik (f.eks databaseopslag, autorisation eller beregnede værdier) - eksplicit resolver defineres
Fra dokumentationen (JavaScript):
// Resolver for the me field on the Query type
function resolveQueryMe(_parent, _args, context, _info) {
return context.request.auth.user;
}
// Resolver for the name field on the User type
function resolveUserName(user, _args, context, _info) {
return context.db.getUserFullName(user.id);
}I C# (Hot Chocolate) skrives det som klasser og metoder:
public class Query
{
public User Me([Service] IHttpContextAccessor httpContext)
{
return new User
{
// Id for authenticated user from claims
Id = httpContext.HttpContext.User.FindFirst("id").Value
};
}
}
public class User
{
public string Id { get; set; }
public string GetName([Parent] User user, [Service] MyDb db)
{
return db.GetUserFullName(user.Id);
}
}Query.Me() - resolver for root-feltet me
User.GetName() - resolver for feltet name på typen User
[Parent] - giver adgang til objektet fra det forrige felt (User returneret fra me)
[Service] - injicerer afhængigheder (f.eks. IHttpContextAccessor, databaseforbindelse)
Execution
Eksekveringsflow for query { me { name } }
Query modtages
klientens forespørgsel kommer ind som en streng i HTTP-body
Valideres mod schema
serveren tjekker, om de efterspurgte felter findes i schemaet
ellers returner fejl
Kald resolvers
Felt-for-felt
Query.Me() kaldes og returnerer User { Id = "42" }
User.GetName() kaldes med User som [Parent] og slår navnet op i databasen
Byg svar
resultatet samles i samme struktur som query
Response
{
"data": {
"me": {
"name": "Luke Skywalker"
}
}
}Delkonklusion
Eksemplet ovenfor viser kun en lille del af GraphQL (en Query med felterne me og name).
I praksis kan et schema indeholde flere typer og tre forskellige operationer.
Schema og Types
Et schema er som sagt kontrakten mellem klient og server. Det beskriver, hvilke typer data der findes, og hvilke felter klienten kan spørge på.
GraphQL’s typesystem er kernen - det definerer alt, hvad der kan forespørges i API'et.
Object types
Den mest almindelige type i GraphQL
Definerer et objekt og de felter, det har
type Character {
name: String!
appearsIn: [Episode!]!
}Her har Character to felter: name (en streng, altid ikke-null) og appearsIn (en liste af episoder).
Scalars
Felter ender altid i en scalar type - de er bladene i træet
Standard scalars: Int, Float, String, Boolean, ID
Man kan definere custom scalars, f.eks Date
Enums
Som scalars, men begrænset til et fast sæt værdier.
enum Episode {
NEWHOPE
EMPIRE
JEDI
}Type modifiers
! Non-Null - feltet må ikke være tomt
[] List - feltet returnerer en liste
Kan kombineres, f.eks [String!]! (liste, ikke null, af strings som heller ikke er null)
Interfaces
Definerer et fælles sæt felter, som flere typer skal implementere
Gør det muligt at spørge på tværs af typer
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}Typer som kunne implementere interface
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}Brug inline fragment for at differentiere mellem objekter
{
hero(episode: JEDI) {
name
... on Droid {
primaryFunction
}
}
}Unions
En type, der kan være én af flere forskellige objekttyper
Eks - Schema for Searchresult - Alle steder hvor Searchresults returneres kan man få de 3 typer
union SearchResult = Human | Droid | StarshipOperation
Klienten spørger efter search(text: "an")
Resolver for search bestemmer hvordan argumentet bruges (f.eks på om navn har "an)
{
search(text: "an") {
__typename
... on Human {
name
height
}
... on Droid {
name
primaryFunction
}
... on Starship {
name
length
}
}
}Response
{
"data": {
"search": [
{"__typename": "Human", "name": "Han Solo", "height": 1.8},
{"__typename": "Human", "name": "Leia Organa", "height": 1.5},
{"__typename": "Starship", "name": "TIE Advanced x1", "length": 9.2}
]
}
}__typename - special meta felt der returnere navnet på typen
Input Object types
Bruges til at sende komplekse input, især i mutationer (f.eks oprette / opdatere et objekt)
input i stedet for type
input ReviewInput {
stars: Int!
commentary: String
}
type Mutation {
createReview(episode: Episode, review: ReviewInput!): Review
}Giver mulighed for at sende flere felter samlet i ét argument i stedet for mange små.
Operation
mutation {
createReview(
episode: JEDI,
review: {
stars: 5
commentary: "This is a great movie!"
}
) {
stars
commentary
}
}Response
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}Komma efter 5 i respons fordi det er JSON
Men i GraphQL bruger object literals IKKE komme mellem felter
Direktiv(er)
Ligesom annotations eller metadata i andre sprog - små instruktioner, der ændrer hvordan et felt, en type eller en operation behandles.
Starter altid med @ efterfulgt af navnet på direktivet
Kan have argumenter ligesom felter
Kan bruges på schema-niveau (type system directives) eller på query-niveau (executable directives)
Indbyggede direktiver
@deprecated
Markerer at et felt eller enum-værdi ikke længere bør bruges
Hjælper klienter med at migrere til nye felter
type User {
fullName: String
name: String @deprecated(reason: "Use `fullName`.")
}@include / @skip
Bestemmer om et felt skal returneres afhængigt af en betingelse
query getUser($withPicture: Boolean!) {
me {
name
picture @include(if: $withPicture)
}
}Hvis variablen withPicture = false, udelades picture fra svaret
Refleksion
Når jeg sammenligner GraphQL med REST, kan jeg se nogle klare fordele.
REST har fungeret godt til mange systemer, men vi har i tidligere projekter oplevet problemer med over-fetching og payloads, der er større end nødvendigt, fra eksterne API producers.
REST erfaringer
API'et returnerer ofte en fast struktur
Vi får mange felter, som vi ikke har brug for
Klienten skal selv sortere og filtrere i data
GraphQL fordele
Klienten kan præcist spørge efter de felter, den har brug for (meget fleksibel)
I en og samme request kan klienten
hente forskellige typer data
nøjes med enkelte felter fra et objekt
kombinere dybe relationer og flade data uden ekstra requests
få data tilbage i samme form som query
Undgår både over-fetching og under-fetching
Payloads bliver mindre og mere effektive
API'et kan udvikle sig uden versionering - felter kan få @deprecated direktiv i stedet
GraphQL er mere komplekst at sætte op end et simpelt REST-endpoint, men fleksibiliteten og kontrol på klientsiden kan gøre det til en stærk løsning i større systemer
Hvornår REST stadig er godt
Små API'er med få felter og simple endpoints
Når caching via HTTP er vigtig
Når klienten ikke har brug for fleksible queries
Den faste struktur kan være hurtigere, hvis behovet er lille
Mere kendt og ofte lettere at sætte op, især til små projekter
Konklusion
Valget mellem REST og GraphQL afhænger af behovet. REST kan være simpelt og effektivt i små systemer, mens GraphQL giver fleksibilitet og kontrol i større løsninger med komplekse databehov. Det er derfor vigtigt at kunne vurdere konteksten, inden man vælger teknologi.
Videre plan
Læse videre i GraphQL Learn (Queries, Mutations, Subscriptions ...)
Bygge et simpelt GraphQL endpoint i C#
Kigge på Kafka og forskellen til GraphQL (asynkron events vs. synkrone queries)
Undersøge storage og pre-signed URLs til Ingestion Service
Ressourcer
GraphQL Learn
Dokumentation
GraphQL
Specifikation
Evt. videre læsning
GraphQL
Training Courses
