Client Http

@duplojs/http-client est une librairie permettant de crĂ©er des clients HTTP typĂ©s. Elle a Ă©tĂ© conçue pour ĂȘtre utilisĂ©e avec @duplojs/types-codegen qui gĂ©nĂšre les types Ă  partir des routes de votre application DuploJS. Vous pouvez consulter la documentation de @duplojs/types-codegen ici pour plus d’informations.

  1. Installation
  2. Exemple minimal
  3. Pourquoi @duplojs/http-client ?
  4. CrĂ©ation de l’instance du client HTTP
  5. Le concept d’information dans les rĂ©ponses HTTP
  6. RequĂȘtes POST
  7. MĂ©thodes de l’instance du client HTTP
  8. ParamĂštres par dĂ©faut de la requĂȘte
  9. Intercepteurs
  10. Hooks

Installation

Pour utiliser @duplojs/http-client, vous devez l’installer en tant que dĂ©pendance de votre projet.

npm install @duplojs/http-client

Exemple minimal

import { HttpClient } from '@duplojs/http-client';

const httpClient = new HttpClient({
    baseUrl: "/"
});

const promiseRequest = httpClient
    .get(
        "/users/{userId}",
        {
            params: {
                userId: "mySuperUserId",
            },
        },
    )

promiseRequest
    .whenResponseSuccess((response) => {
        console.log(response)
    })
    .whenError((error) => {
        console.error(error)
    });

const response = await promiseRequest

const successResponse = await promiseRequest.iWantResponseSuccess();

Dans cet exemple :

  • Un client http a Ă©tais crĂ©er avec la baseUrl /.
  • Nous utilisons la mĂ©thode get du client http pour effectuer une requĂȘte HTTP GET, cette mĂ©thode renvois un Promise.
  • La requĂȘte est faite sur le path /users/{userId}, la valeur {userId} du path sera remplacer pars la valeur dĂ©finit a params.userId.
  • La mĂ©thode whenResponseSuccess de PromiseRequest est utilisĂ©e pour dĂ©finir un callback qui sera executer dans le cas ou la requĂȘte porte un code 200.
  • La mĂ©thode whenError de PromiseRequest est utilisĂ©e pour dĂ©finir un callback qui sera executer en cas de d’echec de la requĂȘte.
  • La PromiseRequest est await pour obtenir la rĂ©ponse.
  • La mĂ©thode iWantResponseSuccess de PromiseRequest renvois un Promise qui rĂ©ussis uniquement si la requĂȘte porte un code 200.

Pourquoi @duplojs/http-client ?

Bien que des librairies comme fetch ou axios soient largement utilisĂ©es pour les requĂȘtes HTTP, @duplojs/http-client apporte plusieurs avantages significatifs :

  1. Support des réponse avec information.
  2. Systéme de hook complet.
  3. Typage bout en bout possible

CrĂ©ation de l’instance du client HTTP

Pour créer une nouvelle instance du client HTTP, utilisez la classe HttpClient. Celle-ci accepte un objet de configuration avec les propriétés suivantes :

import { HttpClient } from '@duplojs/http-client';

const httpClient = new HttpClient({
    baseUrl: "https://google.com/base/url",
    keyToInformation: "my-info" 
});

Dans cet exemple :

  • Un objet HttpClient a Ă©tais instancier.
  • Le lien de l’API qui sera utilisĂ© pour faire des requĂȘte est https://google.com/base/url.
  • La clef a la qu’elle sera chercher l’information dans les headers est my-info.
Propriété Type Valeur par défaut Description
baseUrl string - URL de base de l’API
keyToInformation string information clĂ© qui dĂ©signe l’info dans le header

Le concept d’information dans les rĂ©ponses HTTP

Par dĂ©faut, HTTP utilise des codes de statut (200, 404, 500, etc.) pour indiquer le rĂ©sultat d’une requĂȘte. Cependant, ces codes sont souvent trop gĂ©nĂ©riques et manquent de contexte. Par exemple, un code 404 peut signifier :

  • La route n’existe pas
  • La ressource demandĂ©e n’existe pas

Pour rĂ©soudre ce problĂšme, @duplojs/http-client introduit le concept d’information. Une information est un identifiant unique envoyĂ© dans les en-tĂȘtes HTTP qui permet d’identifier prĂ©cisĂ©ment le rĂ©sultat d’une requĂȘte.

await httpClient
    .post(
        "/timesheet",
        {
            body: {
                ...
            },
        },
    )
    .whenInformation("timesheet.created", () => {
        // code par rapport Ă  l'information timesheet.created
    })
    .whenInformation("workLocation.notfound", () => {
        // code par rapport Ă  l'information workLocation.notfound
    })
    .whenInformation("workingTime.exceeds13hWithoutBreak", () => {
        // code par rapport Ă  l'information workingTime.exceeds13hWithoutBreak
    })
    .whenInformation("workingTime.exceeds24Hours", () => {
        // code par rapport Ă  l'information workingTime.exceeds24Hours
    });

Dans cet exemple, nous utilisons la mĂ©thode whenInformation pour exĂ©cuter du code en fonction de l’information retournĂ©e par la requĂȘte.

  • Si l’information est timesheet.created, le premier callback sera exĂ©cutĂ©.
  • Si l’information est workLocation.notfound, le deuxiĂšme callback sera exĂ©cutĂ©.
  • Si l’information est workingTime.exceeds13hWithoutBreak, le troisiĂšme callback sera exĂ©cutĂ©.
  • Si l’information est workingTime.exceeds24Hours, le quatriĂšme callback sera exĂ©cutĂ©.

Les informations sont dĂ©finies par le serveur et peuvent ĂȘtre utilisĂ©es pour transmettre des messages d’erreur, des messages de succĂšs, des messages d’avertissement, etc. Cela Ă©vite d’avoir Ă  analyser le corps de la rĂ©ponse pour dĂ©terminer le rĂ©sultat de la requĂȘte.

RequĂȘtes POST

Pour effectuer une requĂȘte POST, utilisez la mĂ©thode post qui est disponible sur l’instance du client HTTP.

httpClient
    .post(
        "/products", 
        {
            body: {
                name: "shoes",
                price: 100
            }
        }
    )
    .whenResponseSuccess(() => {
        console.log("Product created");
    })
    .whenError(() => {
        console.log("Product not created");
    });

Dans cet exemple :

  • Nous utilisons la mĂ©thode post pour effectuer une requĂȘte HTTP POST.
  • La route /products est utilisĂ©e pour crĂ©er un nouveau produit.
  • Les donnĂ©es du produit sont dĂ©finies dans l’objet body.
  • La mĂ©thode whenResponseSuccess est utilisĂ©e pour exĂ©cuter du code en cas de succĂšs.
  • La mĂ©thode whenError est utilisĂ©e pour exĂ©cuter du code en cas d’erreur.

La fonctionnement des méthodes put, patch et delete est similaire à celle de la méthode post.

MĂ©thodes de l’instance du client HTTP

Une fois l’instance du client HTTP créée, vous pouvez utiliser les mĂ©thodes suivantes :

Méthode Description
setDefaultRequestParams DĂ©finit les paramĂštres par dĂ©faut pour toutes les requĂȘtes
setInterceptor Configure les intercepteurs de requĂȘtes ou de rĂ©ponses
request Effectue une requĂȘte HTTP
get Effectue une requĂȘte HTTP GET
post Effectue une requĂȘte HTTP POST
put Effectue une requĂȘte HTTP PUT
patch Effectue une requĂȘte HTTP PATCH
delete Effectue une requĂȘte HTTP DELETE

ParamĂštres par dĂ©faut de la requĂȘte

La mĂ©thode setDefaultRequestParams permet de dĂ©finir des paramĂštres par dĂ©faut qui seront appliquĂ©s Ă  toutes les requĂȘtes effectuĂ©es par le client HTTP. Ces paramĂštres peuvent ĂȘtre surchargĂ©s individuellement lors de l’appel des mĂ©thodes HTTP.

Utilisation basique

const httpClient = new HttpClient({
    baseUrl: "https://api.example.com"
});

httpClient.setDefaultRequestParams({
    headers: {
        "Content-Type": "application/json",
        get Authorization() {
            return `Bearer ${localStorage.getItem("token")}`;
        }
    },
    credentials: "include"
});

Dans cet exemple :

  • Les en-tĂȘtes Content-Type et Authorization seront ajoutĂ©s Ă  toutes les requĂȘtes.
  • Les credentials seront inclus dans toutes les requĂȘtes.

ParamĂštres disponibles

ParamĂštre Type Description
credentials "include" | "same-origin" | "omit" Gestion des credentials
mode "cors" | "no-cors" | "same-origin" | "navigate" Mode CORS
headers Record<string, string> En-tĂȘtes HTTP par dĂ©faut
params Record<string, string> ParamĂštres d’URL par dĂ©faut
query Record<string, string> ParamĂštres de requĂȘte par dĂ©faut
redirect "manual" | "follow" | "error" Gestion des redirections
referrer "no-referrer" | "client" Referrer
referrerPolicy "no-referrer" | "no-referrer-when-downgrade" | "origin" | "origin-when-cross-origin" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin" | "unsafe-url" Politique de referrer
integrity string Intégrité
signal AbortSignal | null Signal d’annulation
window any FenĂȘtre
keepalive boolean Keepalive
dispatcher Dispatcher | undefined Dispatcher
duplex "half" | undefined Duplex

Exemple complet

Pour montrer un cas d’utilisation complet, voici un exemple de configuration d’un client HTTP avec des paramĂštres par dĂ©faut ainsi que sont utilisation. Tout d’abord, nous crĂ©ons une instance du client HTTP et dĂ©finissons les paramĂštres par dĂ©faut :

export const httpClient = new HttpClient<HttpClientRoute>({
 baseUrl: "api.example.com",
});

httpClient.setDefaultRequestParams({
    headers: {
        "Content-Type": "application/json",
        "Accept-Language": "fr-FR"
    },
    mode: "cors",
    query: {
        "version": "1.0"
    }
});

Dans cet exemple :

  • L’URL de base de l’API est api.example.com.
  • Les en-tĂȘtes Content-Type et Accept-Language seront ajoutĂ©s Ă  toutes les requĂȘtes.
  • Le mode CORS est activĂ©.
  • Le paramĂštre de requĂȘte version est dĂ©fini Ă  1.0.
  • Les autres paramĂštres sont laissĂ©s Ă  leurs valeurs par dĂ©faut.

Ensuite nous utilisons le client HTTP pour effectuer une requĂȘte :

// Les paramÚtres par défaut sont appliqués automatiquement
const response = await httpClient
    .get(
        "/users/{userId}",
        {
            params: {
                userId: "1",
            },
        },
    )
    .iWantInformation("user.found");

// Les paramĂštres peuvent ĂȘtre surchargĂ©s
const specificResponse = await httpClient
    .get(
        "/users/{userId}",
        {
            params: {
                userId: "1",
            },
            headers: {
                "Accept-Language": "en-US", // Surcharge le paramÚtre par défaut
                "Authorization": "Bearer token" // Ajout d'un nouveau paramĂštre
            }
        },
    )
    .iWantInformation("user.found");

Dans cet exemple :

  • La premiĂšre requĂȘte utilise les paramĂštres par dĂ©faut dĂ©finis prĂ©cĂ©demment.
  • La deuxiĂšme requĂȘte surcharge le paramĂštre Accept-Language et ajoute un nouvel en-tĂȘte Authorization.
import { HttpClient } from "@duplojs/http-client";

export const httpClient = new HttpClient({
    baseUrl: "your.server.url",
});

httpClient.setDefaultRequestParams({
    credentials: "include", // include, same-origin, omit
    mode: "cors", // cors, no-cors, same-origin, navigate
    headers: {
        // set your default headers here
        // example:
        // "Content-Type": "application/json",
    },
    params: {
        // set your default query params here
        // example:
        // "api_key": "your_api_key",
    },
    query: {
        // set your default query params here
        // example:
        // "user_id": "your_user_id",
    },
    redirect: "follow", // manual, follow, error
    referrer: "client", // no-referrer, client
    referrerPolicy: "no-referrer-when-downgrade", // no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
    integrity: "",
    signal: null, // (type: AbortSignal | null)
    window: null,
    keepalive: false, // (type: boolean)
    dispatcher: undefined, // (type: Dispatcher | undefined)
    duplex: "half", // half, undefined
});

Intercepteurs

Les intercepteurs permettent d’intercepter et de modifier les requĂȘtes avant leur envoi ou les rĂ©ponses avant leur traitement. Cette fonctionnalitĂ© est particuliĂšrement utile pour :

  • Ajouter des en-tĂȘtes d’authentification dynamiques
  • Effectuer des transformations de donnĂ©es
  • Logger les requĂȘtes/rĂ©ponses
  • GĂ©rer les erreurs de maniĂšre centralisĂ©e

Exemple d’utilisation

// Intercepteur de requĂȘte
httpClient.setInterceptor("request", async (request) => {
    // Ajout d'un token d'authentification
    if (request.headers) {
        request.headers["Authorization"] = `Bearer ${await getToken()}`;
    }
    return request;
});

// Intercepteur de réponse
httpClient.setInterceptor("response", async (response) => {
    // Gestion centralisée des erreurs
    if (response.code === 401) {
        await refreshToken();
        // Relancer la requĂȘte...
    }
    return response;
});

Dans cet exemple :

  • L’intercepteur de requĂȘte ajoute un token d’authentification Ă  chaque requĂȘte
  • L’intercepteur de rĂ©ponse gĂšre le rafraĂźchissement du token en cas d’expiration

Types d’intercepteurs

Type Signature Description
request (request: RequestDefinition) => Promise<RequestDefinition> Modifie la requĂȘte avant son envoi
response (response: Response) => Promise<Response> Transforme la réponse avant son traitement

Cas d’utilisation courants

Authentification dynamique

httpClient.setInterceptor("request", async (request) => {
    const token = await getToken();
    if (request.headers) {
        request.headers["Authorization"] = `Bearer ${token}`;
    }
    return request;
});

Logging des requĂȘtes

httpClient.setInterceptor("request", (request) => {
    console.log(`[${new Date().toISOString()}] ${request.method} ${request.path}`);
    return request;
});

Transformation du corp des réponses

httpClient.setInterceptor("response", async (response) => {
    if (response.body) {
        response.body = {
            ...response.body,
            timestamp: new Date().toISOString(),
        }
    };
    return response;
});

Gestion globale des erreurs

httpClient.setInterceptor("response", async (response) => {
    if (!response.ok) {
        switch (response.code) {
            case 401:
                await refreshToken();
                break;
            case 403:
                redirectToLogin();
                break;
            default:
                // Ne rien faire pour laisser le traitement de l'erreur Ă  la route
                break;
        }
    }
    return response;
});

Hooks

Les hooks permettent d’exĂ©cuter du code en fonction de certaines conditions sur les rĂ©ponses HTTP. Contrairement aux intercepteurs qui modifient les requĂȘtes/rĂ©ponses, les hooks sont purement rĂ©actifs et ne peuvent pas modifier les rĂ©ponses.

Types de hooks disponibles

Type Description
code Se déclenche sur un code HTTP spécifique
general Se déclenche sur une plage de codes HTTP (200-299, 400-499, 500-599)
error Se déclenche quand response.ok est false
information Se déclenche sur une information spécifique

Exemples d’utilisation des hooks

Hook sur un code HTTP spécifique

httpClient.hooks.add({
    type: "code",
    value: 401,
    callback: async (response) => {
        await refreshToken();
    }
});

Hook sur une plage de codes HTTP

httpClient.hooks.add({
    type: "general",
    value: 200, // Se déclenche pour tous les codes 2XX
    callback: async (response) => {
        console.log("RequĂȘte rĂ©ussie:", response);
    }
});

Hook sur les erreurs

httpClient.hooks.add({
    type: "error",
    callback: async (response) => {
        console.error("Une erreur est survenue:", response);
        notifyError(response);
    }
});

Hook sur une information spécifique

httpClient.hooks.add({
    type: "information",
    value: "user.found",
    callback: async (response) => {
        updateUserCache(response.body);
    }
});

Points importants :

  • Les hooks sont exĂ©cutĂ©s aprĂšs le traitement de la rĂ©ponse
  • Plusieurs hooks peuvent ĂȘtre dĂ©finis pour le mĂȘme type/valeur
  • Les hooks ne peuvent pas modifier la rĂ©ponse
  • Les hooks sont exĂ©cutĂ©s de maniĂšre asynchrone

Cas d’utilisation courants des hooks

  1. Notification utilisateur
// Afficher une notification pour chaque erreur
httpClient.hooks.add({
    type: "error",
    callback: (response) => {
        showToast({
            type: "error",
            message: "Une erreur est survenue"
        });
    }
});

// Afficher un message de succÚs spécifique
httpClient.hooks.add({
    type: "information",
    value: "user.created",
    callback: () => {
        showToast({
            type: "success",
            message: "Utilisateur créé avec succÚs"
        });
    }
});
  1. RafraĂźchissement automatique
// Actualiser la liste des utilisateurs
httpClient.hooks.add({
    type: "information",
    value: "user.updated",
    callback: () => {
        refreshUserList();
    }
});
  1. Redirection
// Rediriger vers la page de connexion
httpClient.hooks.add({
    type: "code",
    value: 401,
    callback: () => {
        router.push("/login");
    }
});

Ces exemples illustrent des cas d’utilisation simples mais frĂ©quents dans une application web moderne.

// TODO: proposer un mini projet duplo/vue pour montrer comment utiliser le client http dans un vrai projet