Skip to content

Validação

POST /tasks receberá JSON. O TypeScript protege seu código durante o build; um schema protege a aplicação durante a requisição.

Defina o contrato

Crie src/schemas/task.schema.ts:

ts
import { t, type Infer } from "empilha";

export const CreateTask = t.Object({
  title: t.String({ minLength: 1, maxLength: 120 }),
  description: t.Optional(t.String({ maxLength: 2_000 })),
});

export type CreateTask = Infer<typeof CreateTask>;

O schema existe em runtime. Infer extrai dele o tipo TypeScript.

Valide e injete o body

ts
import { Body, Post } from "empilha";
import { CreateTask, type CreateTask as CreateTaskInput } from "../schemas/task.schema";

@Post("/")
create(@Body(CreateTask) input: CreateTaskInput) {
  return {
    id: 1,
    ...input,
    done: false,
  };
}

@Body(CreateTask) faz duas coisas:

  1. valida o JSON;
  2. entrega o valor validado ao argumento.

Como este contrato espera um objeto JSON, envie o tipo de conteúdo correto:

http
Content-Type: application/json

O leitor também aceita text/*, application/x-www-form-urlencoded e multipart/form-data. Cada formato é convertido antes da validação: texto vira string, formulários viram objetos e arquivos multipart permanecem como File. Um tipo não suportado, como application/octet-stream, recebe 415 Unsupported Media Type.

Um body inválido recebe 400 antes de o método executar:

json
{
  "errors": [
    {
      "path": "/title",
      "message": "Expected string"
    }
  ]
}

Se o body exceder http.maxBodyBytes, a requisição recebe 413 Payload Too Large. O limite é aplicado também quando o cliente envia o body em chunks ou omite Content-Length.

Valide a query como um objeto

Para paginação, valores relacionados formam um único contrato:

ts
import { Get, QueryParams, Request, t, type RequestContext } from "empilha";

const TaskFilters = t.Object({
  page: t.Integer({ minimum: 1 }),
  limit: t.Integer({ minimum: 1, maximum: 100 }),
  done: t.Optional(t.Boolean()),
});

@Get("/")
@QueryParams(TaskFilters, { page: 1, limit: 20 })
list(@Request() request: RequestContext) {
  return {
    filters: request.query,
    items: [],
  };
}

@QueryParams converte números e booleanos, aplica defaults e valida o objeto completo.

Formatos de string nativos

O Empilha registra os formatos mais comuns usados por schemas TypeBox:

FormatoExemplo
date2026-08-13
date-time2026-08-13T12:30:00Z
emailana@example.com
uuid550e8400-e29b-41d4-a716-446655440000
urihttps://example.com/tasks/1
ipv4192.0.2.1
hostnameapi.example.com
ts
const TaskParams = t.Object({
  id: t.String({ format: "uuid" }),
});

Esses formatos funcionam na validação de request e resposta sem registro manual no FormatRegistry.

Body usado sem argumento

Uma rota SQL pode precisar validar o body sem recebê-lo no método:

ts
import { queryArtifacts } from "../queries/query-artifacts";

@Post("/")
@Body(CreateTask)
@Sql(queryArtifacts.taskCreate)
create() {}

Nesse formato, o body validado fica disponível para bindings como :body.title.

Schema e tipo devem vir juntos

Não escreva uma interface TypeScript separada que possa divergir do schema. Infira o tipo com Infer<typeof Schema>.

Feito para APIs que continuam simples quando crescem.