Pular para o conteúdo
Documentação em português

Tradução da página original em inglês. Em caso de divergência, vale a versão em inglês.

Clientes tipados

O DaloyJS oferece duas formas de chamar sua API com tipagem completa. Use a que combina com o seu consumidor.

1. Cliente tipado em processo (zero geração de código)

Para consumidores TypeScript no mesmo monorepo (testes, ferramentas internas, server actions do 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, totalmente tipado
}

O cliente é indexado por operationId, devolve uma união discriminada de {status, body, headers} e infere tudo a partir da própria definição da rota. Sem etapa de build.

Parâmetros de caminho

Os parâmetros de caminho são substituídos por segmento inteiro, então :id nunca corresponde dentro de :idx, e cada valor é codificado com percent-encoding no próprio segmento. Desde a 1.3.7, um método gerado lança um TypeError, antes de qualquer requisição ser enviada, quando um parâmetro está ausente, vazio, ou é . ou ... O parsing de URL resolveria um segmento de ponto (mesmo como %2E%2E) e redirecionaria a chamada silenciosamente, por exemplo transformando DELETE /orgs/:org/members/.. em DELETE /orgs/:org. Rotas curinga (*path, ou um * sozinho chamado wildcard) aceitam um valor separado por /; cada segmento é codificado e verificado da mesma forma.

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

2. SDK Hey API (entre linguagens, entre repositórios, em tempo de build)

Para consumidores fora do monorepo ou em outras linguagens, gere um SDK fetch totalmente tipado com o @hey-api/openapi-ts.

Pipeline de geração de código
  1. 01Rotasapp.route(...)
  2. 02generateOpenAPI@daloyjs/core/openapi
  3. 03openapi.jsonespecificação OpenAPI 3.1 em disco
  4. 04openapi-tsgerador Hey API
  5. 05SDK tipadosdk.gen.ts · types.gen.ts
  6. 06Consumidorchamadas fetch totalmente tipadas
pnpm gen roda a cadeia inteira: extrai a especificação das suas rotas e deixa o Hey API transformá-la em um SDK fetch tipado. Rode de novo sempre que uma rota mudar e o cliente acompanha o contrato.
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
# grava:
#   generated/openapi.json
#   generated/client/{client.gen.ts, sdk.gen.ts, types.gen.ts, index.ts}

Usando o SDK gerado

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);

Qual devo usar?

Caso de usoEscolha
Chamador TypeScript no mesmo repositório (testes, ferramentas internas)createInProcessClient
Aplicativo web / bundle mobile React Native em outro repositórioSDK Hey API
Consumidor que não usa TypeScript (Python, Swift, Kotlin)Documento OpenAPI + o gerador preferido dele
SDK público para terceirosSDK Hey API, publicado como pacote próprio

Vindo do ts-rest?

O ts-rest é uma biblioteca popular orientada a contrato que oferece tipos TypeScript de ponta a ponta sem geração de código, compartilhando um contrato (initContract) entre um servidor baseado em adaptadores (Express, Fastify, NestJS, Next.js) e um cliente fetch (initClient). Se você gosta desse modelo, o DaloyJS vai parecer familiar, com duas diferenças.

Primeiro, no DaloyJS a definição da rota é o contrato: não existe um objeto de contrato separado para manter em sincronia. O createClient em processo mostrado acima oferece a mesma experiência sem geração de código e com tipos compartilhados para chamadores TypeScript no mesmo repositório.

Segundo, a tipagem do ts-rest é só TypeScript e exige que o cliente importe o contrato. O DaloyJS emite uma especificação OpenAPI 3.1 de primeira classe e um SDK Hey API a partir das mesmas rotas, então consumidores que não conseguem importar seus tipos (outros repositórios, outras linguagens, SDKs públicos) também ficam cobertos. No ts-rest, o OpenAPI é um complemento opcional (@ts-rest/open-api). O DaloyJS também é o próprio servidor e runtime, portável entre Node, Bun, Deno, Cloudflare e Vercel, e não uma camada de tipagem montada sobre outro framework.

ts-restDaloyJS
Origem do contratoObjeto initContract separadoA própria definição da rota
Cliente tipado sem geração de códigoSim (initClient, só TypeScript)Sim (createClient, só TypeScript)
Clientes entre linguagens / entre repositóriosComplemento OpenAPI (@ts-rest/open-api)OpenAPI 3.1 + SDK Hey API, de primeira classe
ServidorAdaptador sobre Express / Fastify / NestJS / Next.jsEmbutido, portável entre runtimes
Validação em tempo de execuçãoStandard Schema (Zod / Valibot / ArkType)Standard Schema (Zod / Valibot / ArkType / TypeBox)
Padrões de segurançaPor sua contaCabeçalhos e limites de corpo ativos por padrão; CSRF e rate limit a uma linha de distância

Precisa de um contrato maior para validar a saída do seu gerador? Use a grande demo REST falsa (em inglês) como caso de estresse em vez de um aplicativo mínimo de tutorial.

Última atualização: