Skip to content

Migrer depuis ky

Ce guide vous aide à migrer de ky vers @unireq. Les deux bibliothèques partagent une approche moderne basée sur fetch avec des APIs fluent.

Référence Rapide

ky@unireq
ky.create({ prefixUrl })client(http(prefixUrl))
ky.get(url)api.get(url)
ky.post(url, { json })api.post(url, body)
response.json()response.data (auto-parsé)
hooks.beforeRequestinterceptRequest()
hooks.afterResponseinterceptResponse()
retry: 3retry(..., { tries: 3 })
timeout: 5000timeout(5000)

Migration de Base

ky

typescript
import ky from 'ky';

const api = ky.create({
  prefixUrl: 'https://api.example.com',
  timeout: 5000,
  headers: {
    'Authorization': `Bearer ${token}`,
  },
  retry: 3,
});

const response = await api.get('users').json();
console.log(response);

@unireq - Équivalent direct

typescript
import { client, retry, backoff } from '@unireq/core';
import { http, parse, timeout, headers, httpRetryPredicate } from '@unireq/http';

const api = client(
  http('https://api.example.com'),
  timeout(5000),
  headers({ 'Authorization': `Bearer ${token}` }),
  retry(httpRetryPredicate(), [backoff()], { tries: 3 }),
  parse.json()
);

const response = await api.get('/users');
console.log(response.data); // Déjà parsé !

@unireq - Avec Preset Builder ✨

typescript
import { preset } from '@unireq/presets';

const api = preset.http
  .uri('https://api.example.com')
  .json
  .timeout
  .retry
  .withHeaders({ 'Authorization': `Bearer ${token}` })
  .build();

const response = await api.get('/users');
console.log(response.data);

💡 Pourquoi utiliser les presets ? Moins de boilerplate, valeurs par défaut sensées, API fluent.

Des Hooks aux Policies

ky hooks

typescript
const api = ky.create({
  hooks: {
    beforeRequest: [
      (request) => {
        request.headers.set('Authorization', `Bearer ${getToken()}`);
      },
    ],
    afterResponse: [
      async (request, options, response) => {
        if (response.status === 401) {
          const token = await refreshToken();
          request.headers.set('Authorization', `Bearer ${token}`);
          return ky(request);
        }
        return response;
      },
    ],
  },
});

@unireq - Équivalent direct

typescript
import { client } from '@unireq/core';
import { http, interceptRequest, interceptResponse, parse } from '@unireq/http';

const api = client(
  http('https://api.example.com'),
  interceptRequest((ctx) => ({
    ...ctx,
    headers: {
      ...ctx.headers,
      Authorization: `Bearer ${getToken()}`,
    },
  })),
  interceptResponse(async (response, ctx) => {
    if (response.status === 401) {
      const token = await refreshToken();
      // Retry avec le nouveau token (implémentation manuelle)
      return fetch(ctx.url, {
        ...ctx,
        headers: { ...ctx.headers, Authorization: `Bearer ${token}` },
      });
    }
    return response;
  }),
  parse.json()
);

@unireq - Solution idiomatique ✨

La policy oauthBearer() gère tout cela automatiquement :

typescript
import { client } from '@unireq/core';
import { http, parse } from '@unireq/http';
import { oauthBearer } from '@unireq/oauth';

const api = client(
  http('https://api.example.com'),
  oauthBearer({
    tokenSupplier: () => getToken(),
    onRefresh: async () => {
      const newToken = await refreshToken();
      return newToken;
    },
  }),
  parse.json()
);

💡 Pourquoi utiliser oauthBearer() ?

  • Gestion automatique des 401 : Refresh et retry de manière transparente
  • Pas de logique de retry manuelle : La policy gère tout
  • Validation JWT : Validation optionnelle du token avant les requêtes

Gestion des Erreurs

ky

typescript
import ky, { HTTPError } from 'ky';

try {
  await ky.get('https://api.example.com/users');
} catch (error) {
  if (error instanceof HTTPError) {
    console.log(error.response.status);
    const body = await error.response.json();
    console.log(body);
  }
}

@unireq

typescript
import { isHttpError, HttpError } from '@unireq/core';

try {
  await api.get('/users');
} catch (error) {
  if (isHttpError(error)) {
    console.log(error.response?.status);
    console.log(error.response?.data); // Déjà parsé
  }
}

Paramètres de Recherche

ky

typescript
const response = await ky.get('users', {
  searchParams: {
    page: 1,
    limit: 10,
    filter: ['active', 'verified'],
  },
});

@unireq

typescript
import { query } from '@unireq/http';

const response = await api.get(
  '/users',
  query({
    page: '1',
    limit: '10',
    filter: ['active', 'verified'],
  })
);

Corps JSON

ky

typescript
const response = await ky.post('users', {
  json: {
    name: 'John',
    email: 'john@example.com',
  },
});

@unireq

typescript
import { body } from '@unireq/http';

const response = await api.post(
  '/users',
  body.json({ name: 'John', email: 'john@example.com' })
);

// Ou passez simplement l'objet (auto-sérialisé avec parse.json())
const response = await api.post('/users', {
  name: 'John',
  email: 'john@example.com',
});

Données de Formulaire

ky

typescript
const formData = new FormData();
formData.append('name', 'John');
formData.append('avatar', file);

await ky.post('users', { body: formData });

@unireq

typescript
import { body } from '@unireq/http';

await api.post(
  '/users',
  body.multipart(
    [{ name: 'avatar', data: file }],
    [{ name: 'name', value: 'John' }]
  )
);

Options Étendues

ky

typescript
const api = ky.extend({
  prefixUrl: 'https://api.example.com',
  timeout: 10000,
  retry: {
    limit: 3,
    methods: ['get'],
    statusCodes: [408, 429, 500, 502, 503, 504],
  },
});

@unireq (Preset Builder)

typescript
import { preset } from '@unireq/presets';

const api = preset.api
  .json
  .withTimeout(10000)
  .withRetry({ tries: 3, methods: ['GET'] })
  .build('https://api.example.com');

Comparaison des Fonctionnalités

Fonctionnalitéky@unireq
Taille du bundle~2KB~8KB
Support navigateur✅ Excellent✅ Bon
Node.js
Retry✅ + Rate-limit aware
Timeout✅ + Timeouts par phase
Hooks✅ Policies
Circuit Breaker✅ Intégré
Throttle✅ Intégré
OAuth✅ Intégré
Validation✅ Zod/Valibot
HTTP/2✅ @unireq/http2
GraphQL✅ @unireq/graphql
Introspectioninspect()

Quand Rester sur ky

  • Apps browser-only : Le bundle de 2KB de ky est difficile à battre
  • Cas d'usage simples : Si vous avez juste besoin d'un wrapper fetch basique
  • Dépendances minimales : ky n'a aucune dépendance

Quand Migrer vers @unireq

  • Apps enterprise : Besoin de circuit breaker, throttle, OAuth
  • Serveurs Node.js : Besoin du support HTTP/2, FTP, IMAP
  • Type safety : Besoin de génériques TypeScript robustes
  • Validation : Besoin de la validation de réponses Zod/Valibot
  • Debugging : Besoin d'introspection des requêtes

Comparaison Côte à Côte

GET Simple

typescript
// ky
const data = await ky.get('https://api.example.com/users').json();

// @unireq
const response = await api.get('/users');
const data = response.data;

POST avec JSON

typescript
// ky
await ky.post('https://api.example.com/users', {
  json: { name: 'John' },
});

// @unireq
await api.post('/users', { name: 'John' });

Avec Toutes les Options

typescript
// ky
const api = ky.create({
  prefixUrl: 'https://api.example.com',
  timeout: 5000,
  retry: 3,
  headers: { 'X-API-Key': key },
});

// @unireq
const api = preset.api
  .json
  .timeout
  .retry
  .withHeaders({ 'X-API-Key': key })
  .build('https://api.example.com');

Released under the MIT License.