Validators provide field-level data integrity checks that run automatically during create and update operations. The @Validator decorator attaches a custom validation function to a field, while @Mandatory ensures that specific fields are present in request bodies for given operations. The @Optional decorator explicitly marks a field as not mandatory.
The @Validator decorator accepts a function that receives the field value and returns true if valid or false if invalid. The function can also return a Promise<boolean> for asynchronous validation.
import { Validator } from "@antelopejs/interface-data-api/metadata";
@RegisterDataController()
class UserAPI extends DataController(
User,
DefaultRoutes.All,
Controller("/users"),
) {
@ModelReference()
@Model(UserModel, "my-database")
declare userModel: UserModel;
@Access(AccessMode.ReadWrite)
@Validator((value) => typeof value === "string" && value.trim().length > 0)
declare name: string;
@Access(AccessMode.ReadWrite)
@Validator((value) => typeof value === "number" && value >= 0 && value <= 120)
declare age: number;
}type ValidatorFunction = (value: unknown) => boolean | Promise<boolean>;The function:
- Receives the raw field value from the request body.
- Returns
truewhen the value is valid. - Returns
falsewhen the value is invalid, which triggers a400 Bad Requestresponse. - Should handle unexpected types gracefully (e.g., check
typeofbefore accessing properties).
// Minimum length
@Validator((value) => typeof value === "string" && value.length >= 2)
declare firstName: string;
// Email format
const emailRegex = /^[\w](\.?[\w-]+)*@(?:[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,}$/;
@Validator((value) => typeof value === "string" && emailRegex.test(value))
declare email: string;
// Enum-like constraint
const VALID_ROLES = ["admin", "editor", "viewer"];
@Validator((value) => typeof value === "string" && VALID_ROLES.includes(value))
declare role: string;// Range
@Validator((value) => typeof value === "number" && value >= 0 && value <= 120)
declare age: number;
// Integer only
@Validator((value) => typeof value === "number" && Number.isInteger(value))
declare quantity: number;
// Positive
@Validator((value) => typeof value === "number" && value > 0)
declare price: number;// Valid date string
@Validator((value) => typeof value === "string" && !isNaN(Date.parse(value)))
declare birthDate: string;
// Future date only
@Validator((value) =>
typeof value === "string" &&
!isNaN(Date.parse(value)) &&
new Date(value) > new Date()
)
declare appointmentDate: string;// Check uniqueness against the database
@Validator(async (value) => {
if (typeof value !== "string") return false;
const existing = await lookupByEmail(value);
return !existing;
})
declare email: string;The @Mandatory decorator specifies which operations require a field to be present in the request body. Unlike @Validator, which checks field content, @Mandatory only verifies that the field exists in the payload.
import { Mandatory } from "@antelopejs/interface-data-api/metadata";Pass one or more operation names as arguments. Common operation names are "new" (create) and "edit" (update).
@RegisterDataController()
class UserAPI extends DataController(
User,
DefaultRoutes.All,
Controller("/users"),
) {
@ModelReference()
@Model(UserModel, "my-database")
declare userModel: UserModel;
@Access(AccessMode.ReadWrite)
@Mandatory("new", "edit")
declare email: string;
@Access(AccessMode.WriteOnly)
@Mandatory("new")
declare password: string;
@Access(AccessMode.ReadWrite)
@Mandatory("edit")
declare status: string;
@Access(AccessMode.ReadWrite)
declare notes: string; // Not mandatory for any operation
}In this example:
emailmust be provided in both create and update requests.passwordis required only when creating a new record.statusis required only when editing an existing record.notesis always optional.
Use DefaultRoutes.WithOptions with the noMandatory option to create an endpoint that skips mandatory field checks.
const routes = {
edit: DefaultRoutes.Edit,
patchEdit: DefaultRoutes.WithOptions(DefaultRoutes.Edit, {
noMandatory: "true",
}),
};
@RegisterDataController()
class UserAPI extends DataController(User, routes, Controller("/users")) {
@Access(AccessMode.ReadWrite)
@Mandatory("edit")
declare status: string;
}Requests to /users/edit require status, while requests to /users/patchEdit do not.
The @Optional decorator explicitly marks a field as not mandatory for any operation. Use it on fields that have no other decorators but still need to be recognized by the Data API metadata system.
import { Optional } from "@antelopejs/interface-data-api/metadata";
@Optional()
declare notes: string;When validation or mandatory checks fail, the API returns a 400 Bad Request response with a plain text message describing the issue.
- Mandatory failure:
"Missing mandatory fields: email, password" - Validator failure:
"Invalid field type(s): age, email"
All fields are checked, and the error message lists every field that failed validation.
See the listable fields documentation to learn how to control field visibility in list operations and configure pagination.