O Gerador De CPF O Gerador De CPF

Validar CPF em Node.js e Express: Middleware com Zod

Validação de CPF em Node.js com Express, usando middleware dedicado e Zod para validação de schema. A lógica segue o algoritmo de módulo 11. Este artigo foca na integração com o ecossistema Node.

Função de validação

function isValidCpf(value) {
  const cpf = value.replace(/\D/g, "");

  if (cpf.length !== 11) return false;
  if (/^(\d)\1{10}$/.test(cpf)) return false;

  for (let t = 9; t < 11; t++) {
    let sum = 0;
    for (let c = 0; c < t; c++) {
      sum += Number(cpf[c]) * ((t + 1) - c);
    }
    const digit = ((10 * sum) % 11) % 10;
    if (Number(cpf[t]) !== digit) return false;
  }

  return true;
}

Mesma lógica da implementação JavaScript pura, extraída como função reutilizável para o back-end.

Middleware Express

function validateCpf(field) {
  return (req, res, next) => {
    const value = req.body[field];

    if (!value || typeof value !== "string") {
      return res.status(400).json({
        errors: [{ field, message: "CPF é obrigatório" }],
      });
    }

    if (!isValidCpf(value)) {
      return res.status(400).json({
        errors: [{ field, message: "CPF inválido" }],
      });
    }

    // Normaliza para apenas dígitos
    req.body[field] = value.replace(/\D/g, "");
    next();
  };
}

Análise do código

isValidCpf é uma função pura sem dependências do Express. O middleware validateCpf recebe o nome do campo como parâmetro, extrai o valor de req.body, valida e normaliza (remove pontuação) antes de passar para o próximo handler com next().

A normalização no middleware garante que o controller sempre receba o CPF como 11 dígitos, independente do formato enviado pelo cliente.

Uso com Express

import express from "express";

const app = express();
app.use(express.json());

app.post("/api/pessoas", validateCpf("cpf"), (req, res) => {
  // req.body.cpf já está validado e normalizado (apenas dígitos)
  res.status(201).json({ cpf: req.body.cpf });
});

Integração com Zod

Para projetos que já usam Zod, a validação de CPF encaixa como refine no schema:

import { z } from "zod";

const cpfSchema = z
  .string()
  .transform((v) => v.replace(/\D/g, ""))
  .refine((v) => v.length === 11, "CPF deve ter 11 dígitos")
  .refine((v) => !/^(\d)\1{10}$/.test(v), "CPF inválido")
  .refine((v) => {
    for (let t = 9; t < 11; t++) {
      let sum = 0;
      for (let c = 0; c < t; c++) {
        sum += Number(v[c]) * ((t + 1) - c);
      }
      if (Number(v[t]) !== ((10 * sum) % 11) % 10) return false;
    }
    return true;
  }, "CPF inválido");

const pessoaSchema = z.object({
  nome: z.string().min(1),
  cpf: cpfSchema,
  email: z.string().email(),
});

Middleware genérico para validar qualquer schema Zod:

function validate(schema) {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);
    if (!result.success) {
      return res.status(400).json({
        errors: result.error.issues.map((i) => ({
          field: i.path.join("."),
          message: i.message,
        })),
      });
    }
    req.body = result.data;
    next();
  };
}

app.post("/api/pessoas", validate(pessoaSchema), (req, res) => {
  res.status(201).json(req.body);
});

Integração com express-validator

Alternativa usando express-validator com validador customizado:

import { body, validationResult } from "express-validator";

const cpfValidation = body("cpf")
  .notEmpty().withMessage("CPF é obrigatório")
  .custom((value) => {
    if (!isValidCpf(value)) throw new Error("CPF inválido");
    return true;
  });

app.post("/api/pessoas", cpfValidation, (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  res.status(201).json({ cpf: req.body.cpf });
});

Testes

import { describe, it, expect } from "vitest";

describe("isValidCpf", () => {
  it("retorna true para CPF válido com máscara", () => {
    expect(isValidCpf("529.982.247-25")).toBe(true);
  });

  it("retorna true para CPF válido sem máscara", () => {
    expect(isValidCpf("52998224725")).toBe(true);
  });

  it("retorna false para CPF com dígitos repetidos", () => {
    expect(isValidCpf("111.111.111-11")).toBe(false);
  });

  it("retorna false para dígito verificador incorreto", () => {
    expect(isValidCpf("529.982.247-26")).toBe(false);
  });

  it("retorna false para tamanho incorreto", () => {
    expect(isValidCpf("123.456.789")).toBe(false);
  });

  it("valida CPF com zeros à esquerda", () => {
    expect(isValidCpf("000.000.001-91")).toBe(true);
  });
});

describe("Zod cpfSchema", () => {
  it("aceita CPF válido e normaliza", () => {
    const result = cpfSchema.safeParse("529.982.247-25");
    expect(result.success).toBe(true);
    if (result.success) expect(result.data).toBe("52998224725");
  });

  it("rejeita CPF inválido", () => {
    const result = cpfSchema.safeParse("111.111.111-11");
    expect(result.success).toBe(false);
  });
});

Use o gerador de CPF válido para criar números de teste em lote.

Veja também: validar CPF em JavaScript puro.