Dansk

Mestr typesikre API-kald i TypeScript for robuste, vedligeholdelsesvenlige og fejlfri webapplikationer. Lær bedste praksis og avancerede teknikker.

Typesikre API-kald med TypeScript: En Omfattende Guide

I moderne webudvikling er interaktion med API'er en fundamental opgave. TypeScript, med sit kraftfulde typesystem, tilbyder en betydelig fordel ved at sikre pålideligheden og vedligeholdelsesvenligheden af dine applikationer ved at muliggøre typesikre API-kald. Denne guide vil udforske, hvordan du kan udnytte TypeScripts funktioner til at bygge robuste og fejlfri API-interaktioner, og dækker bedste praksis, avancerede teknikker og eksempler fra den virkelige verden.

Hvorfor typesikkerhed er vigtigt for API-kald

Når du arbejder med API'er, har du i bund og grund at gøre med data, der kommer fra en ekstern kilde. Disse data er måske ikke altid i det format, du forventer, hvilket kan føre til kørselsfejl og uventet adfærd. Typesikkerhed giver et afgørende beskyttelseslag ved at verificere, at de data, du modtager, overholder en foruddefineret struktur, og fanger potentielle problemer tidligt i udviklingsprocessen.

Opsætning af dit TypeScript-projekt

Før du dykker ned i API-kald, skal du sikre dig, at du har et TypeScript-projekt sat op. Hvis du starter fra bunden, kan du initialisere et nyt projekt ved hjælp af:

npm init -y
npm install typescript --save-dev
tsc --init

Dette vil oprette en `tsconfig.json`-fil med standard TypeScript-compilerindstillinger. Du kan tilpasse disse indstillinger, så de passer til dit projekts behov. For eksempel vil du måske aktivere strict mode for strengere typekontrol:

// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Definition af typer for API-svar

Det første skridt mod at opnå typesikre API-kald er at definere TypeScript-typer, der repræsenterer strukturen af de data, du forventer at modtage fra API'et. Dette gøres normalt ved hjælp af `interface`- eller `type`-erklæringer.

Brug af Interfaces

Interfaces er en kraftfuld måde at definere formen på et objekt. For eksempel, hvis du henter en liste over brugere fra et API, kan du definere et interface som dette:

interface User {
  id: number;
  name: string;
  email: string;
  address?: string; // Valgfri egenskab
  phone?: string; // Valgfri egenskab
  website?: string; // Valgfri egenskab
  company?: {
    name: string;
    catchPhrase: string;
    bs: string;
  };
}

`?` efter et egenskabsnavn indikerer, at egenskaben er valgfri. Dette er nyttigt til håndtering af API-svar, hvor visse felter kan mangle.

Brug af Types

Typer ligner interfaces, men tilbyder mere fleksibilitet, herunder muligheden for at definere union-typer og intersection-typer. Du kan opnå det samme resultat som med interfacet ovenfor ved at bruge en type:

type User = {
  id: number;
  name: string;
  email: string;
  address?: string; // Valgfri egenskab
  phone?: string; // Valgfri egenskab
  website?: string; // Valgfri egenskab
  company?: {
    name: string;
    catchPhrase: string;
    bs: string;
  };
};

For simple objektstrukturer er interfaces og typer ofte udskiftelige. Typer bliver dog mere kraftfulde, når man har at gøre med mere komplekse scenarier.

API-kald med Axios

Axios er en populær HTTP-klient til at lave API-kald i JavaScript og TypeScript. Den giver et rent og intuitivt API, hvilket gør det nemt at håndtere forskellige HTTP-metoder, request-headers og svardata.

Installation af Axios

npm install axios

Udførelse af et typesikret API-kald

For at lave et typesikret API-kald med Axios kan du bruge `axios.get`-metoden og specificere den forventede svartype ved hjælp af generics:

import axios from 'axios';

async function fetchUsers(): Promise {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/users');
    return response.data;
  } catch (error) {
    console.error('Fejl ved hentning af brugere:', error);
    throw error;
  }
}

fetchUsers().then(users => {
  users.forEach(user => {
    console.log(user.name);
  });
});

I dette eksempel fortæller `axios.get('...')` TypeScript, at svardataene forventes at være en række af `User`-objekter. Dette giver TypeScript mulighed for at levere typekontrol og autofuldførelse, når du arbejder med svardataene.

Håndtering af forskellige HTTP-metoder

Axios understøtter forskellige HTTP-metoder, herunder `GET`, `POST`, `PUT`, `DELETE` og `PATCH`. Du kan bruge de tilsvarende metoder til at lave forskellige typer API-kald. For eksempel, for at oprette en ny bruger, kan du bruge `axios.post`-metoden:

async function createUser(user: Omit): Promise {
  try {
    const response = await axios.post('https://jsonplaceholder.typicode.com/users', user);
    return response.data;
  } catch (error) {
    console.error('Fejl ved oprettelse af bruger:', error);
    throw error;
  }
}

const newUser = {
  name: 'John Doe',
  email: 'john.doe@example.com',
  address: '123 Main St',
  phone: '555-1234',
  website: 'example.com',
  company: {
    name: 'Example Corp',
    catchPhrase: 'Viser vejen',
    bs: 'Innovative løsninger'
  }
};

createUser(newUser).then(user => {
  console.log('Oprettet bruger:', user);
});

I dette eksempel opretter `Omit` en type, der er den samme som `User`, men uden `id`-egenskaben. Dette er nyttigt, fordi `id`'et typisk genereres af serveren, når en ny bruger oprettes.

Brug af Fetch API

Fetch API er et indbygget JavaScript API til at lave HTTP-kald. Selvom det er mere basalt end Axios, kan det også bruges med TypeScript til at opnå typesikre API-kald. Du foretrækker måske at bruge det for at undgå at tilføje en afhængighed, hvis det passer til dine behov.

Udførelse af et typesikret API-kald med Fetch

For at lave et typesikret API-kald med Fetch kan du bruge `fetch`-funktionen og derefter parse svaret som JSON, hvor du specificerer den forventede svartype:

async function fetchUsers(): Promise {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    if (!response.ok) {
      throw new Error(`HTTP-fejl! status: ${response.status}`);
    }
    const data: User[] = await response.json();
    return data;
  } catch (error) {
    console.error('Fejl ved hentning af brugere:', error);
    throw error;
  }
}

fetchUsers().then(users => {
  users.forEach(user => {
    console.log(user.name);
  });
});

I dette eksempel fortæller `const data: User[] = await response.json();` TypeScript, at svardataene skal behandles som en række af `User`-objekter. Dette giver TypeScript mulighed for at udføre typekontrol og autofuldførelse.

Håndtering af forskellige HTTP-metoder med Fetch

For at lave forskellige typer API-kald med Fetch kan du bruge `fetch`-funktionen med forskellige indstillinger, såsom `method`- og `body`-indstillingerne. For eksempel, for at oprette en ny bruger, kan du bruge følgende kode:

async function createUser(user: Omit): Promise {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(user)
    });
    if (!response.ok) {
      throw new Error(`HTTP-fejl! status: ${response.status}`);
    }
    const data: User = await response.json();
    return data;
  } catch (error) {
    console.error('Fejl ved oprettelse af bruger:', error);
    throw error;
  }
}

const newUser = {
  name: 'John Doe',
  email: 'john.doe@example.com',
  address: '123 Main St',
  phone: '555-1234',
  website: 'example.com',
  company: {
    name: 'Example Corp',
    catchPhrase: 'Viser vejen',
    bs: 'Innovative løsninger'
  }
};

createUser(newUser).then(user => {
  console.log('Oprettet bruger:', user);
});

Håndtering af API-fejl

Fejlhåndtering er et kritisk aspekt af API-kald. API'er kan fejle af mange årsager, herunder netværksproblemer, serverfejl og ugyldige anmodninger. Det er vigtigt at håndtere disse fejl på en elegant måde for at forhindre din applikation i at gå ned eller vise uventet adfærd.

Brug af Try-Catch-blokke

Den mest almindelige måde at håndtere fejl i asynkron kode på er ved at bruge try-catch-blokke. Dette giver dig mulighed for at fange eventuelle undtagelser, der kastes under API-kaldet, og håndtere dem korrekt.

async function fetchUsers(): Promise {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/users');
    return response.data;
  } catch (error) {
    console.error('Fejl ved hentning af brugere:', error);
    // Håndter fejlen, f.eks. ved at vise en fejlmeddelelse til brugeren
    throw error; // Gen-kast fejlen for at lade den kaldende kode også håndtere den
  }
}

Håndtering af specifikke fejlkoder

API'er returnerer ofte specifikke fejlkoder for at angive den type fejl, der er opstået. Du kan bruge disse fejlkoder til at give mere specifik fejlhåndtering. For eksempel vil du måske vise en anden fejlmeddelelse for en 404 Not Found-fejl end for en 500 Internal Server Error.

async function fetchUser(id: number): Promise {
  try {
    const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`);
    return response.data;
  } catch (error: any) {
    if (error.response?.status === 404) {
      console.log(`Bruger med ID ${id} blev ikke fundet.`);
      return null; // Eller kast en brugerdefineret fejl
    } else {
      console.error('Fejl ved hentning af bruger:', error);
      throw error;
    }
  }
}

fetchUser(123).then(user => {
  if (user) {
    console.log('Bruger:', user);
  } else {
    console.log('Bruger ikke fundet.');
  }
});

Oprettelse af brugerdefinerede fejltyper

For mere komplekse fejlhåndteringsscenarier kan du oprette brugerdefinerede fejltyper til at repræsentere forskellige typer API-fejl. Dette giver dig mulighed for at give mere struktureret fejlinformation og håndtere fejl mere effektivt.

class ApiError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message);
    this.name = 'ApiError';
  }
}

async function fetchUser(id: number): Promise {
  try {
    const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`);
    return response.data;
  } catch (error: any) {
    if (error.response?.status === 404) {
      throw new ApiError(404, `Bruger med ID ${id} blev ikke fundet.`);
    } else {
      console.error('Fejl ved hentning af bruger:', error);
      throw new ApiError(500, 'Internal Server Error'); //Eller en anden passende statuskode
    }
  }
}

fetchUser(123).catch(error => {
  if (error instanceof ApiError) {
    console.error(`API-fejl: ${error.statusCode} - ${error.message}`);
  } else {
    console.error('Der opstod en uventet fejl:', error);
  }
});

Datavalidering

Selv med TypeScripts typesystem er det afgørende at validere de data, du modtager fra API'er ved kørselstid. API'er kan ændre deres svarstruktur uden varsel, og dine TypeScript-typer er måske ikke altid perfekt synkroniseret med API'ets faktiske svar.

Brug af Zod til runtime-validering

Zod er et populært TypeScript-bibliotek til runtime datavalidering. Det giver dig mulighed for at definere skemaer, der beskriver den forventede struktur af dine data, og derefter validere dataene mod disse skemaer ved kørselstid.

Installation af Zod

npm install zod

Validering af API-svar med Zod

For at validere API-svar med Zod kan du definere et Zod-skema, der svarer til din TypeScript-type, og derefter bruge `parse`-metoden til at validere dataene.

import { z } from 'zod';

const userSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  address: z.string().optional(),
  phone: z.string().optional(),
  website: z.string().optional(),
  company: z.object({
    name: z.string(),
    catchPhrase: z.string(),
    bs: z.string(),
  }).optional(),
});

type User = z.infer;

async function fetchUsers(): Promise {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/users');
    const data = z.array(userSchema).parse(response.data);
    return data;
  } catch (error) {
    console.error('Fejl ved hentning af brugere:', error);
    throw error;
  }
}

I dette eksempel validerer `z.array(userSchema).parse(response.data)`, at svardataene er en række af objekter, der overholder `userSchema`. Hvis dataene ikke overholder skemaet, vil Zod kaste en fejl, som du derefter kan håndtere korrekt.

Avancerede teknikker

Brug af Generics til genanvendelige API-funktioner

Generics giver dig mulighed for at skrive genanvendelige API-funktioner, der kan håndtere forskellige typer data. For eksempel kan du oprette en generisk `fetchData`-funktion, der kan hente data fra ethvert API-endepunkt og returnere det med den korrekte type.

async function fetchData(url: string): Promise {
  try {
    const response = await axios.get(url);
    return response.data;
  } catch (error) {
    console.error(`Fejl ved hentning af data fra ${url}:`, error);
    throw error;
  }
}

// Anvendelse
fetchData('https://jsonplaceholder.typicode.com/users').then(users => {
  console.log('Brugere:', users);
});

fetchData<{ title: string; body: string }>('https://jsonplaceholder.typicode.com/todos/1').then(todo => {
    console.log('Opgave', todo)
});

Brug af Interceptors til global fejlhåndtering

Axios tilbyder interceptorer, der giver dig mulighed for at opfange anmodninger og svar, før de håndteres af din kode. Du kan bruge interceptorer til at implementere global fejlhåndtering, såsom logning af fejl eller visning af fejlmeddelelser til brugeren.

axios.interceptors.response.use(
  (response) => response,
  (error) => {
    console.error('Global fejlhåndtering:', error);
    // Vis en fejlmeddelelse til brugeren
    return Promise.reject(error);
  }
);

Brug af miljøvariabler til API URL'er

For at undgå at hardcode API URL'er i din kode, kan du bruge miljøvariabler til at gemme URL'erne. Dette gør det lettere at konfigurere din applikation til forskellige miljøer, såsom udvikling, staging og produktion.

Eksempel med brug af `.env`-fil og `dotenv`-pakken.

// .env
API_URL=https://api.example.com
// Installer dotenv
npm install dotenv
// Importer og konfigurer dotenv
import * as dotenv from 'dotenv'
dotenv.config()

const apiUrl = process.env.API_URL || 'http://localhost:3000'; // angiv en standardværdi

async function fetchData(endpoint: string): Promise {
  try {
    const response = await axios.get(`${apiUrl}/${endpoint}`);
    return response.data;
  } catch (error) {
    console.error(`Fejl ved hentning af data fra ${apiUrl}/${endpoint}:`, error);
    throw error;
  }
}

Konklusion

Typesikre API-kald er essentielle for at bygge robuste, vedligeholdelsesvenlige og fejlfri webapplikationer. TypeScript tilbyder kraftfulde funktioner, der giver dig mulighed for at definere typer for API-svar, validere data ved kørselstid og håndtere fejl elegant. Ved at følge de bedste praksisser og teknikker, der er beskrevet i denne guide, kan du markant forbedre kvaliteten og pålideligheden af dine API-interaktioner.

Ved at bruge TypeScript og biblioteker som Axios og Zod kan du sikre, at dine API-kald er typesikre, dine data er validerede, og dine fejl håndteres elegant. Dette vil føre til mere robuste og vedligeholdelsesvenlige applikationer.

Husk altid at validere dine data ved kørselstid, selv med TypeScripts typesystem. API'er kan ændre sig, og dine typer er måske ikke altid perfekt synkroniseret med API'ets faktiske svar. Ved at validere dine data ved kørselstid kan du fange potentielle problemer, før de forårsager problemer i din applikation.

God kodning!