Usar Ajv no Node.js permite validar objetos e documentos JSON com JSON Schema ou JSON Type Definition. A biblioteca compila schemas em funções JavaScript eficientes, que podem ser reutilizadas em APIs, filas, arquivos de configuração, webhooks e contratos entre serviços.
JSON Schema é independente de linguagem e pode ser compartilhado com gateways, clientes, testes e documentação. Porém, a implementação segura exige strict mode, schemas compilados uma vez, limites de tamanho, rejeição de campos desconhecidos, formatos configurados explicitamente e cuidado com coerção ou remoção automática de dados.
Neste guia, você aprenderá a instalar Ajv 8, criar schemas, compilar validators, formatar erros, integrar com TypeScript, validar APIs, combinar schemas, usar referências, gerar código standalone e testar contratos.
O que é Ajv?
Ajv é um validador de JSON Schema e JTD. A documentação oficial do Ajv explica que schemas são compilados e armazenados em cache. A documentação oficial do JSON Schema apresenta drafts e palavras-chave do padrão.
Quando usar?
- APIs REST;
- webhooks;
- mensagens de fila;
- arquivos de configuração;
- contratos entre linguagens;
- validação em gateways;
- OpenAPI;
- eventos versionados.
Ajv versus Zod
Ajv trabalha com JSON Schema ou JTD, padrões que podem ser consumidos fora do TypeScript. Zod cria schemas em código TypeScript com ótima inferência. O site já possui o artigo Zod no TypeScript: Validação de Dados.
- Ajv: contrato independente de linguagem e compilação eficiente.
- Zod: experiência TypeScript direta e transforms em código.
Instalando
npm install ajv ajv-formatsO pacote de formats adiciona validações como e-mail, URI e date-time conforme a configuração.
Instância básica
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
const ajv = new Ajv({
allErrors: true,
strict: true
});
addFormats(ajv);strict ajuda a detectar keywords desconhecidas e ambiguidades no schema.
Schema de usuário
const CreateUserSchema = {
$id: 'CreateUser',
type: 'object',
additionalProperties: false,
required: ['name', 'email'],
properties: {
name: {
type: 'string',
minLength: 1,
maxLength: 100
},
email: {
type: 'string',
format: 'email',
maxLength: 254
},
age: {
type: 'integer',
minimum: 18,
maximum: 150
}
}
};Compilando
const validateCreateUser = ajv.compile(
CreateUserSchema
);Compile no startup e reutilize. Compilar em toda requisição desperdiça CPU e pode aumentar latência.
Validando
const valid = validateCreateUser(input);
if (!valid) {
throw new ValidationError(
formatValidationErrors(
validateCreateUser.errors
)
);
}A propriedade errors é sobrescrita na próxima execução da função. Copie os dados quando precisar preservá-los.
additionalProperties
Definir false rejeita campos não declarados. Isso reduz mass assignment:
{
"name": "Ana",
"email": "ana@example.com",
"isAdmin": true
}Sem uma política clara, campos inesperados podem chegar a repositories ou modelos.
required
Uma propriedade em properties não é obrigatória automaticamente. Inclua em required.
String vazia
type: string aceita string vazia. Use minLength quando o campo precisa de conteúdo.
Inteiros
Use integer para valores sem fração. Ainda defina limites de negócio.
Arrays
tags: {
type: 'array',
minItems: 0,
maxItems: 20,
uniqueItems: true,
items: {
type: 'string',
minLength: 1,
maxLength: 50
}
}Limitar quantidade impede payloads enormes e trabalho excessivo.
Objetos aninhados
address: {
type: 'object',
additionalProperties: false,
required: ['city', 'country'],
properties: {
city: { type: 'string', maxLength: 100 },
country: {
type: 'string',
pattern: '^[A-Z]{2}$'
}
}
}Regex e ReDoS
Patterns precisam ser simples e limitados. Consulte ReDoS no Node.js.
Formatos
Formats não significam automaticamente que o valor é seguro para uso. Uma URL válida ainda pode apontar para rede privada.
Consulte SSRF no Node.js.
date-time
createdAt: {
type: 'string',
format: 'date-time'
}Depois da validação, converta para Date e confirme regras como intervalo e fuso quando necessário.
Enum
status: {
enum: ['draft', 'active', 'disabled']
}Use enums explícitos em vez de strings livres.
const
eventType: {
const: 'user.created'
}É útil em envelopes de eventos.
oneOf
oneOf: [
{ $ref: '#/$defs/cardPayment' },
{ $ref: '#/$defs/pixPayment' }
]Os schemas precisam ser mutuamente claros. Alternativas que validam simultaneamente criam ambiguidades.
Discriminator
Uma propriedade como type pode selecionar a variante. Confira suporte e configuração da versão usada.
$defs
$defs: {
uuid: {
type: 'string',
format: 'uuid'
}
}Reutilize definições dentro do schema.
$ref
userId: {
$ref: '#/$defs/uuid'
}Referências evitam duplicação, mas schemas cíclicos e remotos precisam de governança.
Registrando schemas
ajv.addSchema(UserSchema);
ajv.addSchema(AddressSchema);
const validate = ajv.getSchema('CreateUser');Use IDs únicos e versionados quando houver vários contratos.
Schemas remotos
Não permita que um schema não confiável faça a aplicação buscar URLs arbitrárias. Carregue contratos de uma origem aprovada ou bundle local.
SSRF em loadSchema
Uma função de carregamento remoto precisa de allowlist e limites. A opção mais segura é empacotar schemas no deploy.
allErrors
Com true, Ajv coleta vários erros, melhorando UX. Porém, payloads enormes podem aumentar trabalho. Combine com body limits.
Primeiro erro
Em endpoints de alto volume, parar no primeiro erro pode ser suficiente. Meça o custo.
strict mode
Strict mode ajuda a detectar:
- keywords desconhecidas;
- tipos contraditórios;
- tuplas sem limites;
- formats indefinidos;
- schemas ambíguos.
Não desabilite globalmente apenas para silenciar um schema mal definido.
Coerção de tipos
const ajv = new Ajv({
coerceTypes: true
});Essa opção pode transformar strings em números ou booleanos. Ela facilita query parameters, mas também muda o input. Prefira conversão explícita em contratos sensíveis.
removeAdditional
Ajv pode remover campos desconhecidos. Rejeitar é frequentemente mais transparente, pois o cliente descobre o erro. Remoção silenciosa pode esconder tentativas e bugs.
useDefaults
Defaults podem ser aplicados durante validação. Isso modifica o objeto. Em APIs, considere criar um objeto novo depois de validar.
Validação pura
Manter validators sem mutação simplifica testes e auditoria. Normalize em uma etapa separada.
TypeScript
import type { JSONSchemaType } from 'ajv';
interface UserInput {
name: string;
email: string;
}
const schema: JSONSchemaType<UserInput> = {
type: 'object',
additionalProperties: false,
required: ['name', 'email'],
properties: {
name: { type: 'string', maxLength: 100 },
email: { type: 'string', format: 'email' }
}
};A tipagem ajuda a alinhar schema e interface, mas a validação runtime continua sendo a fonte para dados externos.
Type guard
Depois de uma validação tipada bem-sucedida, TypeScript pode estreitar o tipo conforme a API utilizada.
API Express
function validateBody(validate) {
return (req, res, next) => {
if (!validate(req.body)) {
return res.status(400).json({
code: 'VALIDATION_ERROR',
errors: formatErrors(validate.errors)
});
}
next();
};
}Fastify
Fastify usa JSON Schema em rotas e compiladores. Ajv pode ser configurado conforme a integração, mas alterações globais devem ser testadas.
Consulte Fastify com Node.js.
Hono
Crie um middleware que valida o body e coloca o resultado no context. Consulte Hono no Node.js.
NestJS
Nest pode usar pipes customizados para Ajv. Avalie se JSON Schema precisa ser o contrato central. Veja NestJS no Node.js.
tRPC
tRPC costuma usar validators TypeScript-first, mas também aceita adapters compatíveis. Consulte tRPC no Node.js.
OpenAPI
OpenAPI usa Schema Object semelhante, mas não é idêntico a todos os drafts JSON Schema. Confirme compatibilidade e conversão.
Consulte OpenAPI com Node.js.
Mensagens de fila
Valide o envelope antes de processar:
{
"messageId": "...",
"type": "invoice.created",
"version": 2,
"data": {}
}Mensagens inválidas devem ir para DLQ ou fluxo controlado, não causar retry infinito.
Eventos versionados
Mantenha schema por versão e upcasters quando necessário. Consulte Event Sourcing no Node.js.
Webhooks
Primeiro valide assinatura nos bytes brutos; depois parseie e valide o JSON. Consulte Assinaturas HMAC no Node.js.
Formato de erros
function formatErrors(errors = []) {
return errors.map(error => ({
path: error.instancePath || '/',
keyword: error.keyword,
message: error.message
}));
}Não retorne schema interno completo ou valores sensíveis.
Localização de mensagens
O servidor pode retornar códigos e paths, enquanto a interface traduz. Bibliotecas adicionais oferecem mensagens localizadas.
Dados nos erros
Não inclua o valor rejeitado quando ele pode ser senha, token ou dado pessoal.
Keywords customizadas
Ajv permite extensões, mas keywords executam código durante validação. Mantenha implementações pequenas, puras e testadas.
Async validation
É possível validar com operações assíncronas, porém consultas de banco dentro do schema misturam contrato e regra de negócio. Prefira duas etapas.
Validação sintática e de domínio
- Schema: tipo, formato, tamanho e estrutura.
- Domínio: e-mail único, saldo, estado do pedido, permission.
Standalone code
Ajv pode gerar validators standalone, úteis em ambientes com restrições de eval ou para reduzir compilação no startup.
Content Security Policy
Alguns ambientes proíbem geração dinâmica de funções. Use código standalone ou configuração compatível.
Compilação no build
Gerar validators no CI detecta schemas inválidos antes do deploy.
Performance
Compile uma vez e reutilize. Não crie uma nova instância Ajv por request.
Cache de schemas
Ajv mantém validators compilados. Use o mesmo objeto de schema ou IDs registrados.
Payload grande
A validação não substitui body limit. Rejeite o corpo antes de JSON.parse quando ultrapassar o máximo.
Profundidade
Objetos extremamente aninhados podem consumir recursos. Limite profundidade no contrato ou parser quando necessário.
Prototype Pollution
Rejeite campos desconhecidos e atualize bibliotecas. Consulte Prototype Pollution no Node.js.
Logs
Registre schema ID, rota, quantidade de erros e request ID. Não registre o payload completo.
Consulte Logs com Pino no Node.js.
Métricas
Monitore rejeições por schema, keyword, versão, rota e duração. Evite path dinâmico como label de alta cardinalidade.
Auditoria
Alterações em schemas críticos devem ter revisão, versão e histórico. Mudanças de contrato podem afetar parceiros e filas.
Teste válido
test('aceita usuário válido', () => {
const input = {
name: 'Ana',
email: 'ana@example.com'
};
assert.equal(validateCreateUser(input), true);
});Teste de campo extra
Envie isAdmin e confirme rejeição.
Teste de limites
Teste exatamente mínimo, máximo, abaixo e acima.
Teste de arrays
Inclua vazios, duplicados, muitos itens e item inválido.
Teste de versões
Mantenha fixtures válidas e inválidas para cada versão de evento.
Teste de schema inválido
O startup ou build deve falhar se o schema possui keyword incorreta.
Property-based testing
Gere objetos aleatórios para verificar que valores fora do contrato nunca são aceitos ou causam exceção inesperada.
Erros comuns
- Compilar por request: CPU é desperdiçada.
- additionalProperties aberto: campos inesperados passam.
- Desabilitar strict mode: erros de schema são ignorados.
- Coerção silenciosa: input muda sem clareza.
- Format tratado como segurança: URL válida pode ser SSRF.
- Erro com valor: dados sensíveis vazam.
- Schema remoto livre: aplicação busca destinos arbitrários.
Boas práticas
- Use Ajv 8 e strict mode.
- Compile no startup ou build.
- Reutilize validators.
- Feche additionalProperties.
- Defina limites.
- Evite mutação automática.
- Versione schemas.
- Separe regra de domínio.
- Formate erros com segurança.
- Teste bordas e versões.
Conclusão
Usar Ajv no Node.js oferece validação eficiente e contratos JSON independentes de linguagem. Schemas compilados podem proteger APIs, webhooks, filas e arquivos com o mesmo padrão.
A implementação segura usa strict mode, limites e campos fechados, sem confiar em coerção ou formats como barreira completa. Com schemas versionados, validators reutilizados e testes de contrato, Ajv mantém dados externos previsíveis antes que alcancem a regra de negócio.


