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.

Validação

O DaloyJS valida as entradas por meio do Standard Schema, uma interface mínima exposta por validadores como Zod, Valibot, ArkType e TypeBox (via um adaptador Standard Schema). Escolha o validador que combina com o seu projeto. O contrato do DaloyJS é o mesmo.

Uma interface, quatro validadores
contrato do frameworkStandard Schemaa propriedade ~standard
padrãoZodz.object({ ... })
tree-shakeableValibotv.object({ ... })
ArkTypetype({ ... })
TypeBoxvia adaptador
Cada validador expõe a mesma propriedade ~standard, então o DaloyJS infere os tipos do handler, gera o OpenAPI e devolve erros problem+json por um único caminho do framework.

O que é validado

Para cada rota você pode declarar schemas para:

  • request.params: parâmetros de caminho decodificados. Eles começam como strings. Faça a coerção no seu schema se quiser números, UUIDs ou enums.
  • request.query: valores da query string. Chaves repetidas viram arrays antes da validação.
  • request.headers: cabeçalhos da requisição com nomes em minúsculas.
  • request.body: corpos de requisição já interpretados. O corpo só é lido quando a rota declara um schema de corpo.
  • responses[status].body: corpos de resposta tipados e validados.

Query strings, campos de formulário e campos multipart descartam chaves de poluição de protótipo como __proto__, constructor e prototype antes da validação.

Limite os campos numéricos (dinheiro, quantidades, IDs)

Um z.number() puro aceita 1e308, -Infinity depois de erros de coerção e outros valores que funcionam para floats IEEE, mas são desastrosos para dinheiro, estoque ou paginação. Sempre coloque limites de domínio nos campos numéricos da requisição:

ts
import { z } from "zod";

// Dinheiro: finito, positivo, com um teto de negócio.
const Money = z.number().finite().positive().max(1_000_000);

// Quantidades: inteiros dentro de uma faixa razoável.
const Qty = z.number().int().positive().max(10_000);

// Prefira coerce só quando o valor chega como string (query / formulário).
const Page = z.coerce.number().int().min(1).max(1000);

// Corpos estritos rejeitam campos extras de mass assignment.
const CreatePayment = z
  .object({ amount: Money, currency: z.enum(["USD", "EUR", "NOK"]) })
  .strict();

O Zod 4 já rejeita NaN / números não finitos em z.number() em muitos casos, mas um teto (.max()) e uma restrição de sinal (.positive() / .nonnegative()) continuam sendo sua última linha de defesa contra fraude de reembolso e overflow. Combine isso com schemas de resposta para que campos sensíveis nunca vazem na saída.

Exemplo de ponta a ponta

ts
import { App } from "@daloyjs/core";
import { z } from "zod";

const CreateLineItem = z.object({
  sku: z.string().min(1),
  qty: z.coerce.number().int().positive().max(10_000),
});

const LineItem = z.object({
  id: z.string(),
  tenantId: z.string(),
  sku: z.string(),
  qty: z.number().int().positive(),
  dryRun: z.boolean(),
});

export const app = new App().post(
  "/orders/:orderId/items",
  {
    operationId: "createOrderItem",
    request: {
      params: z.object({ orderId: z.uuid() }),
      query: z
        .object({
          dryRun: z
            .enum(["true", "false"])
            .transform((value) => value === "true")
            .optional(),
        })
        .optional(),
      headers: z.object({ "x-tenant": z.string().min(1) }),
      body: CreateLineItem,
    },
    responses: {
      201: { description: "Created", body: LineItem },
      422: { description: "Validation error" },
    },
  },
  async ({ params, query, headers, body }) => {
    const tenantId = headers["x-tenant"];
    const dryRun = query?.dryRun ?? false;

    return {
      status: 201,
      body: {
        id: `${params.orderId}:li_1`,
        tenantId,
        ...body,
        dryRun,
      },
    };
  },
);

Escolha seu validador

  • Zod (em inglês): o padrão para a maioria das equipes. API encadeável, ecossistema grande, fácil de aprender.
  • Valibot (em inglês): modular e tree-shakeable. Ótimo para runtimes de edge e contratos enviados ao navegador.

Schemas compatíveis com ArkType e TypeBox também funcionam quando expõem a mesma propriedade ~standard, mas o DaloyJS só traz documentação e templates oficiais para Zod e Valibot.

Lado a lado

ts
// Zod
import { z } from "zod";
const Body = z.object({
  sku: z.string(),
  qty: z.number().int().positive(),
});

// Valibot
import * as v from "valibot";
const Body = v.object({
  sku: v.string(),
  qty: v.pipe(v.number(), v.integer(), v.minValue(1)),
});

// ArkType
import { type } from "arktype";
const Body = type({ sku: "string", qty: "1<=number.integer" });

// TypeBox
import { Type } from "@sinclair/typebox";
const Body = Type.Object({ sku: Type.String(), qty: Type.Integer({ minimum: 1 }) });
// Envolva com o adaptador Standard Schema do seu projeto antes de passar ao DaloyJS.

Quando um schema expõe ~standard, o DaloyJS infere os tipos do handler, gera o OpenAPI e devolve erros problem+json do mesmo jeito, qualquer que seja o validador que o produziu.

Erros

Interpretar e depois ramificar pelo resultado
  1. não confiávelRequisição brutaparams · query · headers · body
  2. standard schemaParse do schemavalidate(schema, input)
  3. em caso de falha422 problem+jsonerrors: [{ path, message }]
  4. em caso de sucessoHandler tipadoctx.body / params / query / headers
Todo schema declarado roda antes do seu handler. Entradas inválidas nunca chegam ao código do handler: o fluxo é interrompido com uma resposta 422 RFC 9457. Entradas válidas chegam totalmente tipadas.

Para entradas inválidas, o DaloyJS devolve 422 Unprocessable Entity como problem+json RFC 9457, com um array errors de registros path e message por problema. Você não escreve um tratador de erro para isso: já vem embutido. Veja Erros e problem+json.

Validação de respostas

Quando um schema de resposta é declarado, o DaloyJS valida o retorno do handler antes de serializá-lo. O valor validado é o que vai para a rede, então schemas de objeto que removem chaves desconhecidas também impedem que campos não declarados vazem para os clientes. Se a validação falhar, o cliente recebe uma resposta de problema 500 ocultada em produção.

Limites de corpo e content types

A opção bodyLimitBytes do App (padrão 1 MiB) limita o Content-Length declarado e o tamanho transmitido de todo corpo de requisição -> 413. Desde a 1.5.4 isso vale em todas as rotas, incluindo corpos que o próprio handler lê de ctx.request; o limite é verificado de forma preguiçosa, quando o corpo é lido.

Quando uma rota declara request.body, o DaloyJS também aplica:

  • Content-Type conferido com a lista accepts da rota, ou com allowedContentTypes global, se definido -> 415.
  • Tipos de corpo aceitos por padrão: application/json, application/x-www-form-urlencoded e multipart/form-data.
  • Parsing protegido contra poluição de protótipo para JSON, query strings, formulários urlencoded e formulários multipart.

Corpos JSON viram objetos, corpos urlencoded passam por URLSearchParams e corpos multipart passam pela API Request.formData() da plataforma. Se você habilitar um media type de texto personalizado em uma rota, o DaloyJS passa o texto decodificado para o seu schema de corpo.

ts
app.post(
  "/legacy-form",
  {
    operationId: "legacyForm",
    accepts: ["application/x-www-form-urlencoded"],
    request: {
      body: z.object({
        email: z.email(),
        qty: z.coerce.number().int().positive(),
      }),
    },
    responses: {
      200: { description: "ok", body: z.object({ ok: z.boolean() }) },
    },
  },
  async ({ body }) => ({ status: 200, body: { ok: body.qty > 0 } }),
);

Misturando validadores

Você pode combinar validadores por rota. Um schema Zod em um arquivo e um schema Valibot em outro são ambos válidos, o que ajuda ao migrar uma base de código existente ou ao consumir schemas de um pacote compartilhado.

Última atualização: