AzuraJS Logo
AzuraJSFramework
v2.2 Beta

Validação

Valide entrada de requisições com schemas e DTOs

Validação ✅

AzuraJS fornece validação poderosa de dados de entrada usando schemas e DTOs.

Validação de Schema 📋

Use validateSchema para validar objetos contra schemas customizados.

import { Post } from "azurajs/decorators";
import { validateSchema } from "azurajs/validators";

const userSchema = {
  name: { type: "string", required: true, minLength: 2 },
  email: { type: "string", required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
  age: { type: "number", required: false, min: 0, max: 120 }
};

const result = validateSchema(data, userSchema);
if (!result.isValid) {
  console.log(result.errors);
}

Opções de Schema

interface SchemaField {
  type: "string" | "number" | "boolean" | "array" | "object";
  required?: boolean;
  minLength?: number;  // Para strings
  maxLength?: number;  // Para strings
  pattern?: RegExp;    // Para strings
  min?: number;        // Para numbers
  max?: number;        // Para numbers
  enum?: any[];        // Valores permitidos
  custom?: (value: any) => boolean | string;  // Validador customizado
}

Exemplo Completo

const postSchema = {
  title: {
    type: "string",
    required: true,
    minLength: 5,
    maxLength: 100
  },
  content: {
    type: "string",
    required: true,
    minLength: 10
  },
  status: {
    type: "string",
    required: false,
    enum: ["draft", "published", "archived"]
  },
  tags: {
    type: "array",
    required: false
  },
  publishedAt: {
    type: "string",
    required: false,
    custom: (value) => {
      const date = new Date(value);
      return !isNaN(date.getTime()) || "Data inválida";
    }
  }
};

@Post()
createPost(@Body() data: any, @Res() res: ResponseServer) {
  const validation = validateSchema(data, postSchema);
  
  if (!validation.isValid) {
    return res.status(400).json({ errors: validation.errors });
  }

  // Dados são válidos, criar post
  const post = createPost(data);
  res.status(201).json({ post });
}

Validação de DTO 🎯

Criar DTOs

DTOs (Data Transfer Objects) definem a estrutura dos dados:

// dtos/CreateUserDto.ts
export interface CreateUserDto {
  name: string;
  email: string;
  password: string;
  age?: number;
}

export const CreateUserSchema = {
  name: { type: "string", required: true, minLength: 2 },
  email: { 
    type: "string", 
    required: true,
    pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  },
  password: { type: "string", required: true, minLength: 8 },
  age: { type: "number", required: false, min: 0, max: 120 }
};

Usar DTOs em Controllers

import { CreateUserDto, CreateUserSchema } from "./dtos/CreateUserDto";

@Controller("/api/users")
export class UserController {
  @Post()
  create(@Body() data: CreateUserDto, @Res() res: ResponseServer) {
    // Validar
    const validation = validateSchema(data, CreateUserSchema);
    if (!validation.isValid) {
      return res.status(400).json({ errors: validation.errors });
    }

    // Criar usuário
    const user = {
      id: Date.now(),
      name: data.name,
      email: data.email,
      age: data.age
    };

    res.status(201).json({ user });
  }
}

Integração com Zod 🔷

Use Zod para validação poderosa com inferência de tipos:

import { z } from "zod";

// Definir schema
const CreateUserSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  password: z.string().min(8).max(100),
  age: z.number().int().positive().max(120).optional(),
  role: z.enum(["user", "admin", "moderator"]).default("user")
});

// Inferir tipo
type CreateUserDto = z.infer<typeof CreateUserSchema>;

@Controller("/api/users")
export class UserController {
  @Post()
  create(@Body() data: unknown, @Res() res: ResponseServer) {
    try {
      // Validar e parsear
      const validData = CreateUserSchema.parse(data);
      
      // validData é totalmente tipado
      const user = {
        id: Date.now(),
        ...validData
      };

      res.status(201).json({ user });
    } catch (error) {
      if (error instanceof z.ZodError) {
        return res.status(400).json({ 
          errors: error.errors.map(e => ({
            path: e.path.join("."),
            message: e.message
          }))
        });
      }
      throw error;
    }
  }
}

Validadores Customizados 🛠️

Criar Validador Reutilizável

function validateAndRespond<T>(
  schema: z.ZodSchema<T>,
  data: unknown,
  res: ResponseServer
): T | null {
  try {
    return schema.parse(data);
  } catch (error) {
    if (error instanceof z.ZodError) {
      res.status(400).json({ 
        errors: error.errors.map(e => ({
          field: e.path.join("."),
          message: e.message
        }))
      });
    }
    return null;
  }
}

@Post()
createUser(@Body() data: unknown, @Res() res: ResponseServer) {
  const validData = validateAndRespond(CreateUserSchema, data, res);
  if (!validData) return;  // Já respondeu com erro

  const user = createUser(validData);
  res.status(201).json({ user });
}

Middleware de Validação

function validateBody<T>(schema: z.ZodSchema<T>) {
  return (req: RequestServer, res: ResponseServer, next: () => void) => {
    try {
      req.validatedBody = schema.parse(req.body);
      next();
    } catch (error) {
      if (error instanceof z.ZodError) {
        return res.status(400).json({ errors: error.errors });
      }
      next(error);
    }
  };
}

// Usar como middleware
app.post("/api/users", validateBody(CreateUserSchema), (req, res) => {
  const data = req.validatedBody;  // Dados validados e tipados
  const user = createUser(data);
  res.status(201).json({ user });
});

Validação de Query Params 🔍

const SearchQuerySchema = z.object({
  q: z.string().min(1),
  limit: z.string().transform(Number).pipe(z.number().int().positive().max(100)).default("10"),
  offset: z.string().transform(Number).pipe(z.number().int().min(0)).default("0"),
  sort: z.enum(["asc", "desc"]).default("asc")
});

@Get("/search")
search(@Query() query: unknown, @Res() res: ResponseServer) {
  try {
    const params = SearchQuerySchema.parse(query);
    
    const results = searchUsers({
      query: params.q,
      limit: params.limit,
      offset: params.offset,
      sort: params.sort
    });

    res.json({ results });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return res.status(400).json({ errors: error.errors });
    }
    throw error;
  }
}

Validação de Params 🎯

const IdParamSchema = z.object({
  id: z.string().uuid()
});

@Get("/:id")
getUser(@Param() params: unknown, @Res() res: ResponseServer) {
  try {
    const { id } = IdParamSchema.parse(params);
    
    const user = findUserById(id);
    if (!user) {
      return res.status(404).json({ error: "Usuário não encontrado" });
    }

    res.json({ user });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return res.status(400).json({ error: "ID inválido" });
    }
    throw error;
  }
}

Schemas Complexos 🔨

Validação Aninhada

const AddressSchema = z.object({
  street: z.string().min(1),
  city: z.string().min(1),
  state: z.string().length(2),
  zipCode: z.string().regex(/^\d{5}(-\d{4})?$/)
});

const UserWithAddressSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  address: AddressSchema
});

type UserWithAddress = z.infer<typeof UserWithAddressSchema>;

Arrays e Objetos

const CreatePostSchema = z.object({
  title: z.string().min(5).max(100),
  content: z.string().min(10),
  tags: z.array(z.string()).min(1).max(5),
  metadata: z.record(z.string(), z.any()).optional()
});

Validação Condicional

const CreateAccountSchema = z.object({
  type: z.enum(["personal", "business"]),
  name: z.string(),
  // Campos condicionais
  taxId: z.string().optional(),
  companyName: z.string().optional()
}).refine(
  (data) => {
    if (data.type === "business") {
      return !!data.taxId && !!data.companyName;
    }
    return true;
  },
  {
    message: "Business accounts require taxId and companyName"
  }
);

Transformação de Dados 🔄

Zod pode transformar dados durante validação:

const UserInputSchema = z.object({
  email: z.string().email().transform(s => s.toLowerCase()),
  name: z.string().transform(s => s.trim()),
  age: z.string().transform(Number).pipe(z.number().int().positive()),
  createdAt: z.string().transform(s => new Date(s))
});

const input = {
  email: "[email protected]",
  name: "  John Doe  ",
  age: "25",
  createdAt: "2024-01-01"
};

const result = UserInputSchema.parse(input);
// {
//   email: "[email protected]",
//   name: "John Doe",
//   age: 25,
//   createdAt: Date object
// }

Mensagens de Erro Personalizadas 💬

const CreateUserSchema = z.object({
  email: z.string().email({ message: "Formato de email inválido" }),
  password: z.string()
    .min(8, { message: "Senha deve ter pelo menos 8 caracteres" })
    .regex(/[A-Z]/, { message: "Senha deve conter pelo menos uma letra maiúscula" })
    .regex(/[0-9]/, { message: "Senha deve conter pelo menos um número" }),
  age: z.number({
    required_error: "Idade é obrigatória",
    invalid_type_error: "Idade deve ser um número"
  }).int().positive().max(120, { message: "Idade deve ser menor que 120" })
});

Melhores Práticas ✨

Valide na entrada: Sempre valide dados antes de processá-los

Use Zod para schemas complexos: Zod fornece inferência de tipos e validação poderosa

Crie DTOs reutilizáveis: Defina schemas uma vez, use em vários lugares

Não confie em dados do cliente: Sempre valide, mesmo se o frontend validou

Próximos Passos 📖

On this page