Capítulo 10 de 35

Validação Blindada de Dados: Zod + Server Actions

A Confiança é o Inimigo da Segurança

Na engenharia de software corporativa, existe uma lei fundamental: Nunca confie no usuário. Nunca confie no frontend.

Se você montar um formulário HTML bonitinho e pegar os dados diretamente para inserir no Supabase, estará abrindo as portas do seu banco de dados para:

  1. E-mails mal formatados (teste@.com).
  2. Números de telefone com caracteres bizarros ou injeções de SQL.
  3. CPFs com tamanhos inválidos ou dados nulos.

Para garantir que o Supabase receba apenas dados puros e imaculados, adotamos a arquitetura de Validação Blindada usando Next.js Server Actions e a biblioteca Zod.


O Fluxo Amador vs O Fluxo Profissional

O Modo Amador (Proibido) ❌

Criar um formulário, pegar os dados no próprio Client Component (via onChange) e enviar direto pro Supabase usando o cliente do navegador. Além de ser lento, permite manipulação fácil de dados pelo console do navegador, burlando qualquer verificação básica que você colocou no botão "Enviar".

O Padrão Ouro B2B (Zod + Server Actions) ✅

  1. O formulário roda no frontend (Client Component) ou no Servidor sem validação local complexa.
  2. O botão de enviar dispara uma Server Action (uma função segura, isolada no servidor da Vercel).
  3. Essa Server Action passa o dado por um "raio-X matemático" (o Schema Zod).
  4. Se o Zod rejeitar (ex: "A senha precisa ter 8 caracteres"), a Server Action aborta a missão e devolve o erro para o usuário ver.
  5. Apenas se o Zod aprovar com 100% de sucesso, os dados são enviados ao Supabase.

Como Implementar a Blindagem

Aqui está o código exato que as IAs e desenvolvedores devem seguir ao arquitetar o envio de dados.

1. Defina as Regras Matemáticas (Zod Schema)

O Zod é a nossa polícia de fronteira. Ele define exatamente o formato que os dados devem ter.

import { z } from 'zod'

// Definimos o contrato de dados:
export const UserRegistrationSchema = z.object({
  email: z.string().email("Formato de e-mail inválido."),
  name: z.string().min(3, "O nome deve ter no mínimo 3 letras."),
  age: z.coerce.number().min(18, "Você precisa ter mais de 18 anos.") 
})

2. Crie a Server Action

A Action ("use server") recebe os dados do formulário e joga no Zod antes de sequer encostar no banco.

"use server"

import { createClient } from '@/utils/supabase/server'
import { UserRegistrationSchema } from './schemas' // Nosso schema Zod

// Essa função só executa no Servidor Vercel
export async function registerUserAction(formData: FormData) {
  // 1. Extraímos os dados brutos e tentamos validar com o Zod
  const rawData = {
    email: formData.get('email'),
    name: formData.get('name'),
    age: formData.get('age'),
  }

  // 2. O RAIO-X (safeParse impede que o app quebre se houver erro)
  const validation = UserRegistrationSchema.safeParse(rawData)

  // 3. Se falhou, voltamos imediatamente
  if (!validation.success) {
    return {
      status: "error",
      errors: validation.error.flatten().fieldErrors // Envia os erros pro formulário ler
    }
  }

  // 4. Chegou aqui? Os dados estão higienizados, blindados e perfeitamente tipados.
  const cleanData = validation.data
  const supabase = createClient()

  const { error } = await supabase.from('users').insert([cleanData])

  if (error) {
     return { status: "error", message: "Erro interno no servidor." }
  }

  return { status: "success", message: "Usuário registrado com sucesso!" }
}

Resumo Operacional

Sempre que a sua Inteligência Artificial for programar a lógica de salvamento de um formulário:

  1. Proíba o envio direto para o Supabase no lado do cliente.
  2. Construa um Schema Zod descrevendo os campos.
  3. Escreva uma Server Action que recebe o FormData e roda um .safeParse().
  4. A operação no banco de dados acontece única e exclusivamente usando os dados limpos extraídos de dentro do Zod (validation.data).

Com essa estrutura, sua base de dados nunca mais conhecerá a sensação de armazenar lixo ou dados corrompidos.