Documentation Index
Fetch the complete documentation index at: https://mintlify.com/nelrondon/backend-proyecto-estructuras/llms.txt
Use this file to discover all available pages before exploring further.
Every write operation in the Estructuras Backend API — registering a user, updating a profile, creating a property listing — passes through a Zod schema before any database operation takes place. Zod parses each incoming request body against a strict shape definition and returns a typed result object that controllers inspect before proceeding. If validation fails, the controller immediately returns a 422 Unprocessable Entity response with a plain-language error message extracted from Zod’s error array, so clients always receive actionable feedback rather than a raw database error.
How Validation Works in Controllers
All schemas expose both a full validator and a partial validator. Controllers follow the same pattern every time: call the appropriate validator, check result.success, and short-circuit on failure. The error extraction differs slightly between the user and property controllers.
User controller error pattern (parses result.error directly):
// User controller error extraction pattern
const result = validateUser(req.body); // or validatePartialUser
if (!result.success) {
const [message] = JSON.parse(result.error);
return res.status(422).json({ error: message.message });
}
Property controller error pattern (parses result.error.message):
// Property controller error extraction pattern
const result = validateProperty(req.body); // or validatePartialProperty
if (!result.success) {
const [message] = JSON.parse(result.error.message);
return res.status(422).json({ error: message.message });
}
Both patterns extract the first validation issue Zod found, keeping error responses concise. The difference is where the JSON string lives — result.error for user validation and result.error.message for property validation.
User Schema
The user schema is defined in src/schemas/user.schema.js and validates the five fields that make up a user record.
import { z } from "zod";
const phoneRegex = new RegExp(
/^(\+?\d{1,3}[-. ]?)?\(?\d{3}\)?[-. ]?\d{3}[-. ]?\d{4}$/
);
const containsNumberRegex = new RegExp(/[0-9]/);
const userSchema = z.object({
name: z
.string({
invalid_type_error: "El nombre debe ser texto.",
required_error: "EL nombre es requerido.",
})
.refine(
(value) => !containsNumberRegex.test(value),
"El nombre no debe contener números"
),
email: z
.string({
required_error: "EL email es requerido.",
})
.email("Formato de email invalido"),
phone: z
.string({
required_error: "EL teléfono es requerido.",
})
.regex(phoneRegex, "Formato de teléfono inválido"),
username: z.string({
invalid_type_error: "El username debe ser texto",
required_error: "El username es requerido",
}),
password: z.string({
invalid_type_error: "La contraseña debe ser texto",
required_error: "La contraseña es requerida",
}),
});
export function validateUser(input) {
return userSchema.safeParse(input);
}
export function validatePartialUser(input) {
return userSchema.partial().safeParse(input);
}
User field rules
| Field | Type | Rules |
|---|
name | string | Required. Must not contain any digit character (/[0-9]/). |
email | string | Required. Must pass Zod’s built-in .email() format check. |
phone | string | Required. Must match the international phone regex (+1 (555) 123-4567, 555-123-4567, etc.). |
username | string | Required. No additional format constraint beyond being a string. |
password | string | Required. No additional format constraint beyond being a string (hashing is done by the controller). |
When to use each function
validateUser(input) — Use on registration (POST /api/register). All five fields are required.
validatePartialUser(input) — Use on login (POST /api/login) and profile update routes. Every field becomes optional, so only the fields present in the request body are validated. A login body of { username, password } passes cleanly.
validatePartialUser is powered by Zod’s .partial() method, which wraps every field in the schema with .optional(). The shape rules (regex, email format, etc.) still apply to any field that is provided — partial only means absent fields are not required.
Property Schema
The property schema is defined in src/schemas/property.schema.js and validates all fields for a real estate listing.
import { z } from "zod";
const statues = ["Venta", "Alquiler", "Ambas"];
const types = ["Casa", "Apartamento", "Terreno", "Comercial"];
const propertySchema = z.object({
title: z.string({
invalid_type_error: "El título debe ser un texto",
required_error: "El título es requerido",
}),
description: z.string({
invalid_type_error: "La descripción debe ser un texto",
}),
status: z.enum(statues, {
errorMap: (issue, ctx) => {
if (issue.code === z.ZodIssueCode.invalid_type) {
return {
message: "El estado de la propiedad debe ser una cadena de texto.",
};
}
if (issue.code === z.ZodIssueCode.invalid_enum_value) {
return {
message: `El estado de propiedad '${
ctx.data
}' no es válido. Los estados permitidos son: ${statues.join(", ")}.`,
};
}
return { message: ctx.defaultError };
},
}),
property_type: z.enum(types, {
errorMap: (issue, ctx) => {
if (issue.code === z.ZodIssueCode.invalid_type) {
return { message: "El tipo de propiedad debe ser una cadena de texto" };
}
if (issue.code === z.ZodIssueCode.invalid_enum_value) {
return {
message: `El tipo de propiedad '${
ctx.data
}' no es válido. Los tipos permitidos son: ${types.join(", ")}.`,
};
}
},
}),
address: z.string({
invalid_type_error: "La dirección debe ser un texto",
required_error: "La dirección es requerida",
}),
city: z.string({
invalid_type_error: "La ciudad debe ser un texto",
required_error: "La ciudad es requerida",
}),
state: z.string({
invalid_type_error: "El estado debe ser un texto",
required_error: "El estado es requerida",
}),
price: z.number({
invalid_type_error: "El precio debe ser un número",
required_error: "El precio es requerido",
}),
bedrooms: z.number({
invalid_type_error: "La cantidad de cuartos debe ser un número",
}),
parking_lots: z.number({
invalid_type_error: "La cantidad de estacionamientos debe ser un número",
}),
bathrooms: z.number({
invalid_type_error: "La cantidad de baños debe ser un número",
}),
square_feet: z.number({
invalid_type_error: "Los pies cuadrados deben ser un número",
}),
});
export function validateProperty(input) {
return propertySchema.safeParse(input);
}
export function validatePartialProperty(input) {
return propertySchema.partial().safeParse(input);
}
Property field rules
| Field | Type | Rules |
|---|
title | string | Required. |
description | string | Optional (no required_error). |
status | enum | Must be one of: "Venta", "Alquiler", "Ambas". Custom error messages distinguish between wrong type and invalid enum value. |
property_type | enum | Must be one of: "Casa", "Apartamento", "Terreno", "Comercial". Same custom error mapping as status. |
address | string | Required. |
city | string | Required. |
state | string | Required. |
price | number | Required. Must be a JavaScript number (not a numeric string). |
bedrooms | number | Optional. Must be a number if provided. |
parking_lots | number | Optional. Must be a number if provided. |
bathrooms | number | Optional. Must be a number if provided. |
square_feet | number | Optional. Must be a number if provided. |
When to use each function
validateProperty(input) — Use on property creation (POST /api/properties). Required fields must be present.
validatePartialProperty(input) — Use on property updates (PUT /api/properties/:id). Allows sending only the fields that changed.
Numeric fields (price, bedrooms, parking_lots, bathrooms, square_feet) must arrive as JSON numbers, not strings. A body parsed with express.json() preserves number types from JSON automatically, but form-encoded bodies (application/x-www-form-urlencoded) turn everything into strings. Always send Content-Type: application/json when posting property data.
Full Controller Example
The pattern below shows exactly how a creation controller combines validation with database persistence:
import { validateProperty } from "../schemas/property.schema.js";
export const createProperty = async (req, res) => {
// 1. Validate the incoming body
const result = validateProperty(req.body);
if (!result.success) {
// 2. Parse result.error.message to extract the first Zod error
const [message] = JSON.parse(result.error.message);
return res.status(422).json({ error: message.message });
}
// 3. Proceed with the validated input
const data = {
...req.body,
userId: req.user.id, // injected by authRequired middleware
};
const saved = await PropiertyModel.register(data);
res.json({ message: "Datos Recibidos" });
};
Validation error response shape
{
"error": "El estado de propiedad 'Renta' no es válido. Los estados permitidos son: Venta, Alquiler, Ambas."
}