# Clients typés

> Traduction de la page originale en anglais. En cas de divergence, la [version anglaise](/docs/typed-client) fait foi.

DaloyJS propose **deux** façons d’appeler votre API avec une sûreté de typage complète. Utilisez celle qui convient à votre consommateur.

## 1. Client typé en processus (sans génération de code)

Pour les consommateurs TypeScript du même monorepo (tests, outils internes, server actions Next.js) :

```ts
import { createInProcessClient } from "@daloyjs/core/client";
import { app } from "./app.js";

const client = createInProcessClient(app);

const r = await client.getBookById({ params: { id: "1" } });
//    ^? { status: 200; body: { id: string; title: string } }
//      | { status: 404; body: ProblemJson }

if (r.status === 200) {
  console.log(r.body.title); // string, entièrement typé
}
```

Le client est indexé par `operationId`, renvoie une union discriminée de `{status, body, headers}` et infère tout à partir de la définition de route elle-même. Aucune étape de build.

### Paramètres de chemin

Les paramètres de chemin sont substitués segment entier par segment entier : `:id` ne correspond donc jamais à l’intérieur de `:idx`, et chaque valeur est encodée en pourcentage dans son propre segment. Depuis la 1.3.7, une méthode générée lève une `TypeError`, avant l’envoi de toute requête, lorsqu’un paramètre est manquant, vide, `.` ou `..`. L’analyse d’URL résoudrait un segment point (même sous la forme `%2E%2E`) et redirigerait silencieusement l’appel, par exemple en transformant `DELETE /orgs/:org/members/..` en `DELETE /orgs/:org`. Les routes joker (`*path`, ou un `*` seul nommé `wildcard`) acceptent une valeur séparée par des `/` ; chaque segment est encodé et vérifié de la même façon.

```ts
// app.get("/files/*path", { operationId: "getFile", ... })
await client.getFile({ params: { path: "docs/guide.md" } }); // GET /files/docs/guide.md

await client.getBookById({ params: { id: ".." } });
// TypeError: Invalid path parameter "id" for /books/:id: empty and "."/".." segments are not allowed
```

> [!TIP]
> **Composez des tuples de routes au lieu d’élargir l’App.**
> Exportez des fichiers de routes avec `defineRoute()` et enregistrez un tuple littéral avec `app.registerRoutes([...])`. Les appels `route()` chaînés fonctionnent aussi. Deux choses effacent délibérément l’inférence et réduisent le client à une surface peu typée :
>
> - Annoter l’instance avec un simple `const app: App = ...` (ou renvoyer `: App` depuis une fabrique). L’annotation élargit le type et efface les routes accumulées : laissez plutôt le type être inféré.
> - Enregistrer les routes par des instructions séparées sur une variable déjà déclarée, au lieu de renvoyer le résultat chaîné ou d’utiliser `registerRoutes()`.
>
> Le guide du monolithe modulaire montre une composition sur plusieurs fichiers avec des tuples de routes issus de plusieurs contextes délimités. `group()` en style callback et `register()` des plugins assurent toujours l’encapsulation à l’exécution, mais leurs callbacks ne peuvent pas élargir le générique TypeScript de la variable parente. Utilisez les tuples de routes comme frontière de composition du client typé.

## 2. SDK Hey API (multilangage, multidépôt, au build)

Pour les consommateurs situés hors du monorepo ou écrits dans d’autres langages, générez un SDK fetch entièrement typé avec [@hey-api/openapi-ts](https://heyapi.dev/openapi-ts/get-started).

**Diagram: Chaîne de génération de code**

1. **Routes** - app.route(...)
2. **generateOpenAPI** - @daloyjs/core/openapi
3. **openapi.json** - spécification OpenAPI 3.1 sur disque
4. **openapi-ts** - générateur Hey API
5. **SDK typé** - sdk.gen.ts · types.gen.ts
6. **Consommateur** - appels fetch entièrement typés

pnpm gen exécute toute la chaîne : extraire la spécification de vos routes, puis laisser Hey API la transformer en SDK fetch typé. Relancez-la à chaque modification de route et le client reste en phase avec le contrat.

```bash
pnpm add -D @hey-api/openapi-ts prettier
```

```ts
// openapi-ts.config.ts
import { defineConfig } from "@hey-api/openapi-ts";

export default defineConfig({
  input: "./generated/openapi.json",
  output: { path: "./generated/client", postProcess: ["prettier"] },
  plugins: ["@hey-api/client-fetch", "@hey-api/typescript", "@hey-api/sdk"],
});
```

```json
// package.json
"scripts": {
  "gen:openapi": "node scripts/dump-openapi.ts",
  "gen:client":  "openapi-ts",
  "gen":         "pnpm gen:openapi && pnpm gen:client"
}
```

```bash
pnpm gen
# écrit :
#   generated/openapi.json
#   generated/client/{client.gen.ts, sdk.gen.ts, types.gen.ts, index.ts}
```

## Utiliser le SDK généré

```ts
import { client } from "./generated/client/client.gen.js";
import { getBookById } from "./generated/client/sdk.gen.js";

client.setConfig({ baseUrl: "https://api.example.com" });

const { data, error } = await getBookById({ path: { id: "1" } });
if (error) console.error(error);
else if (data) console.log(data.title);
```

## Lequel choisir ?

| Cas d’usage | Choix |
| --- | --- |
| Appelant TypeScript dans le même dépôt (tests, outils internes) | `createInProcessClient` |
| Application web / bundle mobile React Native dans un dépôt séparé | SDK Hey API |
| Consommateur non TypeScript (Python, Swift, Kotlin) | Document OpenAPI + son générateur préféré |
| SDK public pour des tiers | SDK Hey API, publié comme paquet à part |

## Vous venez de ts-rest ?

[ts-rest](https://ts-rest.com/) est une bibliothèque orientée contrat populaire qui fournit des types TypeScript de bout en bout **sans génération de code** en partageant un contrat (`initContract`) entre un serveur à base d’adaptateurs (Express, Fastify, NestJS, Next.js) et un client fetch (`initClient`). Si ce modèle vous plaît, DaloyJS vous semblera familier, à deux différences près.

D’abord, dans DaloyJS, **la définition de route est le contrat** : il n’y a pas d’objet de contrat séparé à garder synchronisé. Le `createClient` en processus présenté plus haut offre la même expérience sans génération de code, avec des types partagés, pour les appelants TypeScript du même dépôt.

Ensuite, la sûreté de typage de ts-rest est limitée à TypeScript et exige que le client importe le contrat. DaloyJS produit une spécification **OpenAPI 3.1** de premier plan et un SDK Hey API à partir des mêmes routes : les consommateurs qui ne peuvent pas importer vos types (autres dépôts, autres langages, SDK publics) sont donc couverts aussi. Dans ts-rest, OpenAPI est un module complémentaire facultatif (`@ts-rest/open-api`). DaloyJS est aussi le serveur et le runtime lui-même, portable sur Node, Bun, Deno, Cloudflare et Vercel, plutôt qu’une couche de typage montée sur un framework séparé.

|  | ts-rest | DaloyJS |
| --- | --- | --- |
| Source du contrat | Objet `initContract` séparé | La définition de route elle-même |
| Client typé sans génération de code | Oui (`initClient`, TypeScript uniquement) | Oui (`createClient`, TypeScript uniquement) |
| Clients multilangages / multidépôts | Module OpenAPI (`@ts-rest/open-api`) | OpenAPI 3.1 + SDK Hey API, de premier plan |
| Serveur | Adaptateur sur Express / Fastify / NestJS / Next.js | Intégré, portable entre runtimes |
| Validation à l’exécution | Standard Schema (Zod / Valibot / ArkType) | Standard Schema (Zod / Valibot / ArkType / TypeBox) |
| Sécurité par défaut | À vous de la fournir | En-têtes et limites de corps actifs par défaut ; CSRF et limitation de débit à une ligne près |

Besoin d’un contrat plus volumineux pour valider la sortie de votre générateur ? Utilisez la [grande démo REST factice](/docs/tutorials/fake-rest-api) (en anglais) comme cas de stress plutôt qu’une application de tutoriel minimale.

Dernière mise à jour : 30 octobre 2026

---

Source: https://daloyjs.dev/fr/docs/typed-client