Vi bygde et sakssystem med REST-API til internt bruk. Kunder, prosjekter og saker henger sammen, og fire valg vi tok tidlig i løpet endte med å spare oss for en god del omskriving lenger ut. Her er de, uten pynt.
1. Nest ressursene i URL-en

En sak tilhører et prosjekt, og et prosjekt tilhører en kunde. Speil det hierarkiet i ruten. Flate ruter som /issues/42 blir tvetydige i det øyeblikket du har mer enn ett prosjekt: hvem eier saken, og hvor hører den hjemme. Nestede ruter gjør eierskapet eksplisitt, og de lar deg autorisere på hvert nivå i stedet for å ettersjekke i etterkant.
GET /customers/{customer}/projects/{project}/issues
POST /customers/{customer}/projects/{project}/issues
GET /customers/{customer}/projects/{project}/issues/{issue}
Prisen er lengre URL-er. Det er verdt det. Du slipper å gjette kontekst ut av query-parametere, og ruteren gir deg en 404 gratis den dagen en sak ikke tilhører prosjektet i URL-en.
2. Skill det brukervennlige nummeret fra database-id-en
Brukeren vil ha et lite, lesbart saksnummer per prosjekt. Prosjekt A har sak 1, 2, 3. Prosjekt B har også sak 1, 2, 3. Fint for mennesker, men issue_number er altså ikke unikt på tvers av prosjekter. Bruk databasens id internt i URL-en, og vis issue_number i grensesnittet.
{
"id": 4821, // globalt unik, brukes i URL
"issue_number": 3, // vises til brukeren, unik per prosjekt
"title": "Feil i eksport"
}
Blander du de to, kolliderer numrene på tvers av prosjekter, og du ender med oppslag som treffer feil sak. Hold dem adskilt fra dag én, så slipper du å nøste opp i det senere.
3. Tving JSON på skrivende kall

Feiler en forespørsel og klienten ikke uttrykkelig ba om JSON, svarer rammeverket gjerne med en pen HTML-feilside. API-klienten sitter da igjen med en haug markup i stedet for en strukturert feil den kan lese. Krev riktige headere på alle POST-, PUT- og DELETE-kall.
Accept: application/json
Content-Type: application/json
Med Accept: application/json tvinger du rammeverket til å svare JSON også ved valideringsfeil og 500. Det gjør klientkoden forutsigbar. Uten den bruker du gjerne en time på å lure på hvorfor parseren kveler på det som skulle vært et pent feilobjekt.
4. Enkle query-filtre slår smarte
Det er fristende å bygge et rikt filterspråk med nestede parametere. La være til du faktisk trenger det. Flate filtre er lette å lese, lette å dokumentere og lette å cache.
GET /customers/1/projects/2/issues?status=open
GET /customers/1/projects/2/issues?status=open&priority=high
Styr unna ?filter[status]=open. Det ser fancy ut, men koster mer i parsing, dokumentasjon og klientkode enn det gir tilbake i et internt system.
Svarformat
Hold svaret forutsigbart. En data-liste og en meta-blokk med paginering. Da slipper klienten å lete etter hvor nyttelasten ligger.
{
"data": [ { "id": 4821, "issue_number": 3, "title": "..." } ],
"meta": { "current_page": 1, "last_page": 5, "per_page": 20, "total": 92 }
}
Fire valg, tatt først, som ikke måtte gjøres om siden. Nest ressursene, skill visningsnummer fra id, tving JSON, hold filtrene flate. Ingen av dem er spesielt smarte. Alle fire er billige å innføre og dyre å rette opp i ettertid.



