Pitch: is-kit vs Zod: uma comparação prática sob 3 perspectivas
Olá, pessoal!
Sou engenheiro frontend e, às vezes, me pergunto:
Sou eu que estou usando a IA… ou é a IA que está me usando?
Quando precisamos escrever validações em runtime no TypeScript, o Zod geralmente é uma das primeiras opções que vêm à mente.
Existem outras alternativas, como Valibot e ArkType, mas hoje quero falar sobre algo um pouco diferente:
is-kit
O is-kit é um toolkit leve e sem dependências para criar type guards reutilizáveis em TypeScript.
Ele permite criar pequenas funções no estilo isFoo, combiná-las para construir verificações mais completas em runtime e manter o narrowing de tipos integrado ao fluxo de controle normal do TypeScript.
É seguro em runtime, composable e simples de usar, sem exigir a adoção de um sistema completo baseado em schemas.
Com ele, você pode:
- criar e reutilizar type guards tipados;
- combinar guards usando
and,or,noteoneOf; - validar estruturas de objetos e coleções;
- interpretar ou verificar valores
unknownsem utilizar um framework de schemas maior.
À primeira vista, is-kit e Zod podem parecer semelhantes.
Porém, suas filosofias de design são bem diferentes:
is-kit→ um toolkit para combinar type guards;Zod→ uma biblioteca para definir schemas e validar dados.
Em vez de perguntar “qual é melhor?”, vamos compará-los de uma perspectiva prática:
Em quais situações cada um é mais simples de escrever?
Vamos analisar três aspectos:
- tamanho do código;
- legibilidade;
- tratamento dos tipos.
O mesmo exemplo
Vamos validar um objeto User simples.
is-kit
import {
and,
isInteger,
isString,
oneOfValues,
optionalKey,
predicateToRefine,
safeParse,
struct,
} from "is-kit";
const isPositiveInt = and(
isInteger,
predicateToRefine<number>((v) => v > 0),
);
const isUser = struct({
id: isPositiveInt,
name: isString,
role: oneOfValues(["admin", "member"] as const),
nickname: optionalKey(isString),
});
const result = safeParse(isUser, input);
if (result.valid) {
result.value.role;
}
Zod
import { z } from "zod";
const UserSchema = z.object({
id: z.number().int().positive(),
name: z.string(),
role: z.enum(["admin", "member"]),
nickname: z.string().optional(),
});
const result = UserSchema.safeParse(input);
if (result.success) {
result.data.role;
}
Já podemos observar a principal diferença:
is-kit→ combina pequenos guards baseados em funções;Zod→ define um schema de maneira declarativa.
1. Tamanho do código
O código com Zod frequentemente parece mais curto.
Por quê?
Porque é possível encadear várias restrições em um único lugar:
z.string().min(1).max(20).regex(...)
z.number().int().positive()
Esse modelo funciona muito bem para:
- formulários;
- validação de APIs;
- validação nas fronteiras de entrada da aplicação.
Por outro lado, o is-kit se destaca quando queremos reutilizar os mesmos guards em diferentes partes da lógica:
if (isUser(value)) {
// O tipo já foi refinado
}
const users = values.filter(isUser);
Os guards podem ser utilizados diretamente no fluxo de controle do TypeScript.
Com Zod, geralmente passamos por métodos como safeParse, então a operação se parece mais com uma chamada explícita de validação.
Resumo
- Quer regras de validação centralizadas →
Zod; - Quer guards reutilizáveis dentro da lógica →
is-kit.
2. Legibilidade
Isso depende do que consideramos “legível”.
Zod
Todas as regras ficam concentradas em um único lugar:
const UserSchema = z.object({ ... });
É possível entender imediatamente:
Qual estrutura de dados é considerada válida?
Isso funciona muito bem nas fronteiras da aplicação.
is-kit
O is-kit se parece mais com o fluxo de controle normal do TypeScript.
Também é possível combinar e refinar os guards gradualmente:
import { hasKey, narrowKeyTo } from "is-kit";
const hasRole = hasKey("role");
if (hasRole(value)) {
value.role;
}
const byRole = narrowKeyTo(isUser, "role");
const isAdmin = byRole("admin");
if (isAdmin(value)) {
value.role;
value.name;
}
Nesse exemplo, podemos:
- refinar toda a estrutura usando
isUser; - focar em uma propriedade específica, como
role; - refinar ainda mais o valor para
admin.
Esse modelo combina naturalmente com o seguinte fluxo:
Receber um valor
unknown→ refiná-lo passo a passo.
Resumo
- Quer uma especificação clara dos dados válidos →
Zod; - Quer narrowing integrado ao fluxo de controle →
is-kit.
3. Como os tipos são tratados
É aqui que aparece a maior diferença entre as duas abordagens.
Zod: Schema → Tipo
type User = z.infer<typeof UserSchema>;
O fluxo é:
- definir o schema;
- gerar o tipo a partir dele.
Essa abordagem funciona muito bem quando queremos:
Manter a validação e os tipos definidos em um único lugar.
is-kit: Guard → Narrowing
O is-kit se concentra principalmente no fluxo de controle:
if (isUser(input)) {
input.role;
}
O TypeScript refina automaticamente o tipo de input.
Também é possível extrair o tipo correspondente ao guard:
import type { GuardedOf } from "is-kit";
type User = GuardedOf<typeof isUser>;
No entanto, a principal vantagem do is-kit não é a geração de tipos.
É isto:
O TypeScript entende a lógica enquanto refinamos os valores.
Diferença central
Zod→ forte no fluxo schema → tipo;is-kit→ forte no fluxo guard → narrowing dentro do controle da aplicação.
Resumo da comparação
| Perspectiva | Zod | is-kit |
|---|---|---|
| Tamanho do código | Excelente para schemas | Excelente para reutilização |
| Legibilidade | Regras de validação centralizadas | Fluxo de controle natural |
| Tratamento dos tipos | Tipos derivados de schemas | Narrowing baseado no fluxo de controle |
Quando usar cada um
Minha abordagem pessoal é:
Use Zod para entradas externas
Por exemplo:
- respostas de APIs;
- formulários;
- variáveis de ambiente.
Use is-kit para a lógica interna
Por exemplo:
- ramificações condicionais;
- filtragem de arrays;
- composição de type guards.
Eles não são concorrentes diretos
Essas ferramentas não precisam competir entre si.
Na verdade, elas podem funcionar muito bem juntas.
Uma separação clara seria:
Zod→ validar dados nas fronteiras da aplicação;is-kit→ refinar valores dentro da aplicação.
Conclusão
O is-kit não é uma versão mais leve do Zod.
Ele se concentra em outra ideia:
Fazer com que type guards pareçam uma parte natural do TypeScript.
O Zod, por outro lado, continua sendo extremamente poderoso para:
Validação schema-first com segurança de tipos.
Portanto, a pergunta principal não é:
Qual deles é melhor?
Mas sim:
Em qual parte da arquitetura você quer garantir a segurança dos tipos?
Caso tenha interesse, você pode conhecer o projeto aqui: