Oppsett
Selv om det faktiske APIet til GraphQL er typet, er det dessverre ikke noen automatikk i at data som hentes på klienten også blir det.
Det har lenge fantes verktøy som f.eks. GraphQL-Codegen som automatisk genererer typer basert på et eksisterende GraphQL-API. Min erfaring er at slike verktøy har mye påkrevd oppsett med pakker som må installeres og skript som må legges til, og er vrient å få til å fungere akkurat som man ønsker.
Rammeverket gql.tada fungerer på en helt annen måte, og er skrevet som en TypeScript-plugin (nytt i versjon 5) som automatisk henter typene fra GraphQL-APIet når TypeScript kompileres. Det gjør at du som utvikler får en “set it and forget it”-opplevelse, og personlig har jeg nærmest glemt at gql.tada er installert fordi det bare fungerer.
I tillegg velger du selv hvilke spørringer og mutasjoner som skal types automatisk eller ikke, noe som gjør det enklere å taes i bruk på et eksisterende prosjekt uten å måtte fikse tusenvis av feil først.
Vi tok i bruk verktøyet på frontend av Mist, en webapplikasjon skrevet i React som bruker @apollo/client
til å kommunisere med et GraphQL-API eksponert fra en backend. Her er et (forenklet) eksempel på koden til en eksisterende spørring for å hente ut en paginert liste av brukere og vise dem i en tabell:
Dette er kode som antagelig virker kjent for de fleste som har jobbet med GraphQL i React før. Derimot vil endringer i User
-ressursen fra APIet ikke automatisk bli reflektert i koden. Det skaper utfordringer dersom man glemmer å vedlikeholde typene AllUsersQuery
og AllUsersQueryInput
når det forekommer en endring.
Om man f.eks. fjerner firstName
og lastName
for å introdusere et nytt felt name
i stedet, vil ikke denne koden gi noen feilmeldinger på det, men alle GraphQL-spørringene vil begynne å feile fordi de etterspurte feltene ikke lenger eksisterer i schemaet. Og dersom phone
plutselig blir returnert som en Int
, vil parseInt
-funksjonen feile, fordi den forventer et argument av typen String
.
Resultater
La oss installere gql.tada og ta det i bruk for å automatisk følge typene fra APIet. Vi fulgte startguiden på den offisielle nettsiden, som i skrivende stund innebærer:
- Installer pakkene
gql.tada
og@0no-co/graphqlsp
(TypeScript-plugin) - Legg til TypeScript-pluginen under
compilerOptions.plugins
i din eksisterendetsconfig.json
, og pek den mot GraphQL-API-adressen din. - Det er ikke noe steg 3 😉
Deretter kan vi omskrive koden over for å kvitte oss helt med de statiske typene:
Nå blir begge problemene nevnt over plukket opp av TypeScript-kompilatoren før koden i det hele tatt bygges. Hvis firstName
og lastName
er blitt fjernet, vil vi få opp en feil om at user.firstName
og user.lastName
ikke lenger finnes. Og tilsvarende med phone
: Nå vil vi få en feilmelding om at funksjonen forventer en String
, men at typen er Number
.
I andre filer som benyttet seg av de statiske typene, kan i vi stedet bytte dem ut med de innebygde verktøytypene ResultOf<typeof ALL_USERS_QUERY>
og VariablesOf<typeof ALL_USERS_QUERY>
. For å enda mer oppfordre til gjenbruk kan man trekke ut de etterspurte feltene til et fragment, og deretter bruke dette til å definere hvordan User
-typen ser ut.
Etter å ha skrudd på typing på flere og flere spørringer i den eksisterende Mist-koden, startet det å dukke opp feil som uheldigvis var blitt oversett tidligere. Dette var for det meste feil om at data potensielt kunne bli returnert som null
eller undefined
, men i ett tilfelle oppdaget vi en ulik implementasjon av et enum på frontend og backend – dette ville sannsynligvis ikke blitt oppdaget organisk av oss utviklere, så der reddet gql.tada oss fra en potensiell (sint) kundemail!
Fallgruver
Til slutt vil jeg nevne noen mindre positive erfaringer jeg har gjort meg etter å ha brukt gql.tada en stund.
Min største utfordring er at typesikkerheten kun fungerer på den uthentede dataen, og ikke mens man skriver de faktiske spørringene eller mutasjonene. Det vil si at all kode inne i graphql()
-wrapperen må skrives som ren tekst, og alle de potensielle feil det innebærer. Med forbehold om at jeg har konfigurert noe feil, kan man argumentere for at dette går litt utenfor verktøyets opprinnelige misjon. Det finnes uansett egne GraphQL-plugins til de største IDEene som tar seg av denne oppgaven, for ikke å snakke om GraphiQL
eller andre GraphQL-klienter. Men det hadde vært kjekt med alt på ett sted!
Benytter man seg av pipelines for å bygge og rulle ut kode, må det nevnes at man er nødt til å sjekke inn og inkludere den autogenererte env.d.ts
typefilen for at koden skal bygges på server. Om man bruker et eksternt API, eller ikke selv utvikler backend, er det ofte at det dukker opp endringer i denne filen som ikke har noe med koden du skriver på akkurat nå, noe som kan forkludre pull requests. I tillegg er filstørrelsen relativt stor – i vårt tilfelle er den på over 1 MB.
Det er også verdt å lese gjennom dokumentasjonen, særlig om du benytter deg av fragments. Disse blir ikke automatisk typet, og man er nødt til å kalle funksjonen readFragment()
for at typene fra fragmentet skal bli tatt i bruk. Og om du har definert egne skalarer i APIet, f.eks. en ID
eller Date
, er du nødt til å overstyre standardkonfigurasjonen og importere graphql()
-wrapperen derfra for at disse skal bli riktig typet.
Konklusjon
Ingen verktøy er perfekte, men alt i alt synes jeg nytteverdien gql.tada leverer gjør det verdt å prøve ut, særlig når innsatsen er såpass lav. Det er et spennende verktøy som allerede har hjulpet oss i utviklingen, og som vi kommer til å bruke videre på Mist og andre prosjekter i fremtiden.