Fabrizio Feitosa

Fabrizio Feitosa

Exploro o universo do desenvolvimento com artigos, tutoriais e reflexões sobre tecnologia — compartilhando aprendizados reais e dicas práticas do dia a dia.


Voltar

Por que você deve usar Zod com TypeScript: Um guia completo para aplicações web modernas

Descubra como Zod pode revolucionar sua validação de dados em TypeScript, oferecendo type safety em tempo de execução e desenvolvimento.


O TypeScript revolucionou o desenvolvimento JavaScript ao adicionar tipagem estática, mas ainda enfrentamos desafios quando se trata de validação de dados em tempo de execução. É aqui que o Zod entra em cena, oferecendo uma solução elegante e poderosa para validação de esquemas.

O problema da validação de dados

Em aplicações web modernas, frequentemente lidamos com dados vindos de APIs externas, formulários de usuário ou configurações. Mesmo com TypeScript, não temos garantias de que os dados em tempo de execução correspondam aos tipos que definimos.

interface User {
  id: number;
  name: string;
  email: string;
  age?: number;
}
 
// Este código pode falhar em tempo de execução
function processUser(data: any): User {
  return data; // Sem validação!
}

O que é Zod?

Zod é uma biblioteca de validação de esquemas TypeScript-first que permite definir esquemas de validação que também geram tipos TypeScript automaticamente. É uma ferramenta essencial para qualquer desenvolvedor que queira garantir type safety tanto em tempo de desenvolvimento quanto em tempo de execução.

Vantagens do Zod

1. Type Safety Completo

import { z } from "zod";
 
// Define o esquema
const UserSchema = z.object({
  id: z.number(),
  name: z.string().min(1, "Nome é obrigatório"),
  email: z.string().email("Email inválido"),
  age: z.number().min(0).optional(),
});
 
// TypeScript infere o tipo automaticamente
type User = z.infer<typeof UserSchema>;

2. Validação em Tempo de Execução

// Validação segura
function processUser(data: unknown): User {
  const validatedData = UserSchema.parse(data);
  return validatedData;
}
 
// Ou com tratamento de erro
function processUserSafe(data: unknown) {
  const result = UserSchema.safeParse(data);
 
  if (!result.success) {
    console.error("Dados inválidos:", result.error);
    return null;
  }
 
  return result.data;
}

Casos de Uso Comuns

Validação de Formulários

const LoginFormSchema = z.object({
  email: z.string().email("Email inválido"),
  password: z.string().min(8, "Senha deve ter pelo menos 8 caracteres"),
  rememberMe: z.boolean().optional(),
});
 
function handleLogin(formData: FormData) {
  const data = Object.fromEntries(formData);
  const validatedData = LoginFormSchema.parse(data);
 
  // Agora temos certeza de que os dados são válidos
  loginUser(validatedData);
}

Validação de APIs

const ApiResponseSchema = z.object({
  success: z.boolean(),
  data: z.array(UserSchema),
  total: z.number(),
  page: z.number().positive(),
});
 
async function fetchUsers(page: number) {
  const response = await fetch(`/api/users?page=${page}`);
  const rawData = await response.json();
 
  // Valida a resposta da API
  const validatedData = ApiResponseSchema.parse(rawData);
  return validatedData;
}

Transformação de Dados

const StringToNumberSchema = z.string().transform((val) => parseInt(val, 10));
const DateStringSchema = z.string().transform((val) => new Date(val));
 
const ConfigSchema = z.object({
  port: StringToNumberSchema,
  createdAt: DateStringSchema,
  features: z.string().transform((val) => val.split(",")),
});

Recursos Avançados

Validação Condicional

const ConditionalSchema = z.object({
  type: z.enum(["admin", "user"]),
  permissions: z.array(z.string()).optional(),
}).refine((data) => {
  if (data.type === "admin") {
    return data.permissions && data.permissions.length > 0;
  }
  return true;
}, {
  message: "Admins devem ter pelo menos uma permissão",
});

Schemas Recursivos

const TreeNodeSchema: z.ZodType<TreeNode> = z.lazy(() =>
  z.object({
    id: z.number(),
    name: z.string(),
    children: z.array(TreeNodeSchema).optional(),
  })
);

Validação de Arrays

const TagsSchema = z.array(
  z.string().min(1).max(50)
).min(1, "Pelo menos uma tag é obrigatória")
 .max(10, "Máximo 10 tags permitidas");

Integração com Frameworks

Next.js

// app/api/users/route.ts
import { z } from "zod";
 
const CreateUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
});
 
export async function POST(request: Request) {
  try {
    const body = await request.json();
    const validatedData = CreateUserSchema.parse(body);
 
    // Processa dados válidos
    const user = await createUser(validatedData);
    return Response.json({ success: true, user });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return Response.json({
        success: false,
        errors: error.errors
      }, { status: 400 });
    }
    throw error;
  }
}

React Hook Form

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
 
const FormSchema = z.object({
  name: z.string().min(1, "Nome é obrigatório"),
  email: z.string().email("Email inválido"),
});
 
type FormData = z.infer<typeof FormSchema>;
 
function MyForm() {
  const form = useForm<FormData>({
    resolver: zodResolver(FormSchema),
  });
 
  // Resto do componente...
}

Performance e Bundle Size

O Zod é otimizado para performance e tem um bundle size relativamente pequeno (~12KB gzipped). Para projetos que precisam de ainda menos código, você pode usar tree-shaking:

// Importe apenas o que você precisa
import { z } from "zod";
import { string, number, object } from "zod";

Boas Práticas

1. Reutilize Schemas

// schemas/user.ts
export const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});
 
// schemas/api.ts
export const ApiResponseSchema = <T extends z.ZodTypeAny>(schema: T) =>
  z.object({
    success: z.boolean(),
    data: schema,
    message: z.string().optional(),
  });

2. Use Error Handling

function validateData<T>(schema: z.ZodSchema<T>, data: unknown): T | null {
  try {
    return schema.parse(data);
  } catch (error) {
    if (error instanceof z.ZodError) {
      console.error("Erro de validação:", error.errors);
    }
    return null;
  }
}

3. Combine com TypeScript

// Use Zod para validação de runtime
const ConfigSchema = z.object({
  apiUrl: z.string().url(),
  timeout: z.number().positive(),
});
 
// Use TypeScript para tipagem estática
type Config = z.infer<typeof ConfigSchema>;
 
// Valide em tempo de execução
function loadConfig(): Config {
  const config = process.env.CONFIG ? JSON.parse(process.env.CONFIG) : {};
  return ConfigSchema.parse(config);
}

Conclusão

O Zod é uma ferramenta essencial para desenvolvedores TypeScript que querem garantir type safety em tempo de execução. Ele oferece:

  • Type Safety: Inferência automática de tipos
  • Validação Robusta: Validação em tempo de execução
  • Flexibilidade: Transformação e validação condicional
  • Performance: Bundle size otimizado
  • Integração: Funciona perfeitamente com frameworks populares

Ao incorporar Zod em seu workflow de desenvolvimento, você elimina uma grande classe de bugs relacionados à validação de dados e melhora significativamente a confiabilidade de suas aplicações.

Dica: Comece com schemas simples e gradualmente adicione validações mais complexas conforme necessário. O Zod é poderoso, mas também pode ser simples quando você precisa.

Espero que este guia tenha ajudado você a entender os benefícios do Zod com TypeScript. Até a próxima! 🚀