Hoppa till innehåll

HTTP och JSON

En vanlig webbsida visar det den fick när den laddades. Vill du se något nytt laddar du om den. En webbapplikation som Zulip fungerar annorlunda: nya meddelanden dyker upp utan att sidan laddas om. Det kräver att JavaScript-koden på sidan själv hämtar och skickar data till en server medan sidan är öppen.

Den här artikeln handlar om språket de två talar, HTTP, och formatet datan oftast skickas i, JSON. De två följande artiklarna visar hur du skriver koden som väntar på svaret, Promises och async/await, och hur du gör själva anropet, Nätverksanrop med fetch.

Tidsåtgång: Ungefär 15 minuter.

HTTP (Hypertext Transfer Protocol) är samma protokoll som webbläsaren använder när den hämtar index.html, bilder och stilmallar. Det är byggt på en enkel princip: klienten skickar en förfrågan (request), och servern skickar tillbaka ett svar (response). Servern tar aldrig kontakt på eget initiativ.

En förfrågan består av en metod, en URL, ett antal headrar (headers) och ibland en body:

POST /books HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
{ "title": "Hemsöborna", "author": "August Strindberg" }

Svaret består av en statuskod, headrar och oftast en body:

HTTP/1.1 201 Created
Content-Type: application/json
Location: /books/43
{ "id": 43, "title": "Hemsöborna", "author": "August Strindberg" }

Headrarna är metadata om meddelandet. Content-Type talar om vilket format bodyn har, och Accept vilket format klienten vill ha tillbaka. Bodyn är själva innehållet.

Sekvensdiagram mellan webbläsaren och api.example.com. Webbläsaren skickar GET /books/42 med Accept: application/json och får 200 OK med Content-Type: application/json och boken som JSON. Sedan skickar webbläsaren POST /books med Content-Type: application/json och en ny bok i bodyn, och får 201 Created med Location: /books/43. Till sist skickar webbläsaren GET /books/999 och får 404 Not Found. En anteckning säger att statuskoden talar om hur det gick och att bodyn innehåller själva datan.

Du kan se varje förfrågan och varje svar själv. Öppna utvecklarverktygen, välj fliken Network och ladda om sidan. Klickar du på en rad ser du metod, statuskod, headrar och body, både det som skickades och det som kom tillbaka. Det är det första stället att titta på när en förfrågan inte gör det du väntar dig.

Metoden talar om vad klienten vill göra med resursen som URL:en pekar ut:

Metod Betydelse Har en body
GET Hämta resursen. Nej
POST Skapa en ny resurs eller skicka data. Ja
PUT Ersätt resursen helt. Ja
PATCH Ändra en del av resursen. Ja
DELETE Ta bort resursen. Nej

GET ändrar ingenting på servern. Därför kan webbläsaren skicka om, hämta i förväg och cacha GET-förfrågningar utan risk. En POST kan däremot göra något som inte ska hända två gånger, som att lägga en beställning.

Ett HTML-formulär kan bara skicka GET och POST. Med JavaScript kan du använda alla metoder.

En GET har ingen body. Behöver servern veta något skickas det i URL:en, som en query string efter ?:

GET /books?author=Strindberg&sort=year HTTP/1.1

Med POST, PUT och PATCH skickas datan i bodyn.

Ett formulär för inloggning skickar lösenordet med POST i stället för GET. Är lösenordet därmed skyddat på vägen till servern?

Nej. Med GET hamnar lösenordet i URL:en, och därmed i webbläsarens historik, i serverns loggar och synligt för den som tittar över axeln. Det är skäl nog att använda POST. Men på vägen till servern är bodyn lika läsbar som URL:en för den som kan avlyssna trafiken.

Det som skyddar datan på vägen är HTTPS, som krypterar hela förfrågan, både URL, headrar och body. Det gäller oavsett metod.

Varje svar har en statuskod. Den första siffran säger vilken sorts svar det är:

  • 2xx – det lyckades. 200 OK, 201 Created (en resurs har skapats) och 204 No Content (det lyckades, men det finns ingen body).
  • 3xx – resursen finns någon annanstans eller har inte ändrats. 301 Moved Permanently och 304 Not Modified. Webbläsaren hanterar dem själv: den följer omdirigeringen, eller använder sin sparade kopia.
  • 4xx – felet ligger i förfrågan. 400 Bad Request (förfrågan är felaktig), 401 Unauthorized (du måste logga in), 403 Forbidden (du får inte), 404 Not Found och 415 Unsupported Media Type (servern förstår inte formatet i bodyn).
  • 5xx – felet ligger hos servern. 500 Internal Server Error och 503 Service Unavailable.

Läs alltid statuskoden innan du använder bodyn. Ett svar med 404 har ofta en body också, men den innehåller ett felmeddelande, inte det du bad om.

Statuskoden säger hur det gick på HTTP-nivå, och inte nödvändigtvis hur det gick för det du ville göra. Wikipedias API, som veckans övningsuppgift använder, svarar 200 OK även när artikeln du söker inte finns, och berättar det i stället i bodyn. Hur ett API använder statuskoderna står i dess dokumentation, eller syns när du provar.

Din kod skickar JSON i bodyn på en POST, men glömmer headern Content-Type. Servern svarar 415 Unsupported Media Type. Är det ett fel i servern?

Nej, det är ett fel i förfrågan, vilket 4 i början också säger. Utan Content-Type vet servern inte att bodyn är JSON, och den gissar inte. Den svarar att den inte kan hantera formatet.

En kod i 4xx-serien betyder i regel att det inte hjälper att skicka samma förfrågan igen. Något i den måste ändras. En kod i 5xx-serien kan däremot gå över av sig själv.

Ett API där varje sak har en egen URL och metoderna betyder det de ska kallas ofta REST-API (Representational State Transfer). GET /books/42 hämtar bok 42, DELETE /books/42 tar bort den och POST /books skapar en ny. Du behöver inte lära dig ett nytt litet språk för varje API, eftersom metoderna redan säger vad som ska göras.

Många API:er skickar också med länkar i svaret, till det du kan göra härnäst:

{
"id": 42,
"title": "Röda rummet",
"_links": {
"self": { "href": "https://api.example.com/books/42" },
"author": { "href": "https://api.example.com/authors/7" }
}
}

Klienten följer länken i stället för att själv bygga ihop URL:en. Flyttar servern sina resurser fortsätter klienten att fungera, eftersom den aldrig har räknat ut URL:erna på egen hand. API:et i examinationsuppgift B2 fungerar på det sättet.

Bodyn i svaren ovan är skriven i JSON (JavaScript Object Notation), det format nästan alla webb-API:er använder i dag. Det ser ut som JavaScript, men är ett eget textformat med strängare regler, så att det kan läsas av vilket programspråk som helst. MIME-typen är application/json.

JSON har sex datatyper: strängar, tal, true och false, null, objekt och arrayer.

{
"title": "Röda rummet",
"year": 1879,
"available": true,
"translator": null,
"tags": ["roman", "satir"]
}

Jämfört med ett objekt i JavaScript:

  • Nycklar och strängar skrivs alltid med dubbla citattecken. { title: 'Röda rummet' } är giltig JavaScript men inte giltig JSON.
  • Inget kommatecken efter sista elementet, och inga kommentarer.
  • Inga funktioner, ingen undefined och inga datum. Ett datum skickas som en sträng, vanligen i formatet "2026-11-02T10:15:00Z".

JSON.parse() tolkar en JSON-sträng och skapar motsvarande värde i JavaScript. JSON.stringify() gör det omvända:

const text = '{ "title": "Röda rummet", "year": 1879 }'
const book = JSON.parse(text)
console.log(book.year) // 1879
const json = JSON.stringify(book)
console.log(json) // {"title":"Röda rummet","year":1879}

Döp variabeln efter vad den innehåller. Det som kommer ur JSON.parse() är en bok, inte JSON, och heter därför book. Bara strängen är JSON.

JSON.stringify() skriver allt på en rad. Vill du läsa resultatet själv, till exempel i konsolen, kan du be om indrag: JSON.stringify(book, null, 2).

const author = {
name: 'August Strindberg',
born: new Date('1849-01-22'),
nickname: undefined,
greet() {
return `Hello, my name is ${this.name}.`
}
}
const copy = JSON.parse(JSON.stringify(author))

Vilka egenskaper har copy, och vad är copy.born för något?

copy har bara name och born.

JSON.stringify() hoppar över egenskaper vars värde är en funktion eller undefined, eftersom JSON saknar motsvarighet. Det blir inget fel; egenskaperna försvinner bara. Ett datum görs om till en sträng, och JSON.parse() vet inte att strängen en gång var ett datum. copy.born är alltså strängen "1849-01-22T00:00:00.000Z", inte ett Date-objekt. Behöver du ett datum får du skapa det själv: new Date(copy.born).

Det är också därför JSON.parse(JSON.stringify(x)) är ett dåligt sätt att kopiera objekt. Använd structuredClone(x), som klarar datum och mycket annat.

JSON.parse() kastar ett SyntaxError om texten inte är giltig JSON. Det händer oftare än man tror, eftersom data från en server inte alltid är det du väntar dig. Ett vanligt fall är att servern svarar med en HTML-sida, till exempel en felsida, när du väntade dig JSON. Felmeddelandet ser då ut ungefär så här:

SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON

Tecknet < är början på <!DOCTYPE html>. Ser du det meddelandet är felet sällan i din tolkning, utan i att du fick fel sorts svar. Titta på förfrågan i fliken Network: vilken statuskod fick du, och till vilken URL gick förfrågan?

JSON.stringify() misslyckas sällan. Det kastar ett TypeError om objektet refererar till sig självt, direkt eller via andra objekt, eller om det innehåller ett BigInt.

JSON.parse() och JSON.stringify() är synkrona. Det märks inte för vanliga svar, men en mycket stor sträng tar tid att tolka, och under tiden står huvudtråden still, som du såg i Händelseloopen. Nätverksanropet som hämtar strängen är däremot asynkront, och det är ämnet för nästa artikel.

  • En förfrågan har en metod, en URL, headrar och ibland en body. Ett svar har en statuskod, headrar och oftast en body. Fliken Network visar allt.
  • GET hämtar och ändrar ingenting. POST, PUT, PATCH och DELETE ändrar något på servern.
  • Läs statuskoden innan du använder bodyn: 2xx lyckades, 4xx är fel i förfrågan och 5xx är fel hos servern. Läs också hur API:et du använder rapporterar fel.
  • Content-Type talar om formatet på bodyn. Skickar du JSON ska headern vara application/json.
  • JSON är text med dubbla citattecken, sex datatyper och inga funktioner eller datum. JSON.parse() kan kasta ett SyntaxError.
CCBY