Validation
Arkos supports two validation libraries — Zod and class-validator. Both have equivalent generation commands, and both generate files from your Prisma schema automatically. The generated schemas and DTOs are ready to pass directly into a RouteHook or ArkosRouter route config.
Schemas are generated to src/modules/<n>/schemas/ and DTOs to src/modules/<n>/dtos/.
Model Validation
These commands generate validation for standard Prisma model routes. They read your Prisma schema and produce typed schemas or DTOs that match your model's fields, automatically handling optional fields, relations, enums, and composite types.
Create
arkos generate create-schema --module post
arkos g cs -m postOutput: src/modules/post/schemas/create-post.schema.ts
Fields marked as @id, timestamps (createdAt, updatedAt, deletedAt), and foreign key columns are excluded. Relation fields are converted to nested objects containing just the reference field (e.g. { id: z.string() }). Optional fields and fields with defaults get .optional(). Enum fields use z.nativeEnum().
import { z } from "zod";
const CreatePostSchema = z.object({
title: z.string(),
content: z.string().optional(),
published: z.boolean().optional(),
author: z.object({ id: z.string().min(1) }),
});
export default CreatePostSchema;
export type CreatePostSchemaType = z.infer<typeof CreatePostSchema>;arkos generate create-dto --module post
arkos g cd -m postOutput: src/modules/post/dtos/create-post.dto.ts
The same field exclusion and relation logic applies. Each field gets the appropriate class-validator decorators. Only the decorators actually needed for the model are imported.
import { IsNotEmpty, IsOptional, IsString, IsBoolean, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
class AuthorForCreatePostDto {
@IsNotEmpty()
@IsString()
id!: string;
}
export default class CreatePostDto {
@IsNotEmpty()
@IsString()
title!: string;
@IsOptional()
@IsNotEmpty()
@IsString()
content?: string;
@IsOptional()
@IsBoolean()
published?: boolean;
@IsOptional()
@ValidateNested()
@Type(() => AuthorForCreatePostDto)
author?: AuthorForCreatePostDto;
}Update
arkos generate update-schema --module post
arkos g us -m postOutput: src/modules/post/schemas/update-post.schema.ts
All fields are made optional for patch semantics. The same relation and enum handling applies.
import { z } from "zod";
const UpdatePostSchema = z.object({
title: z.string().optional(),
content: z.string().optional(),
published: z.boolean().optional(),
author: z.object({ id: z.string().min(1) }).optional(),
});
export default UpdatePostSchema;
export type UpdatePostSchemaType = z.infer<typeof UpdatePostSchema>;arkos generate update-dto --module post
arkos g ud -m postOutput: src/modules/post/dtos/update-post.dto.ts
Every field gets @IsOptional(). All other decorators still apply.
import { IsOptional, IsNotEmpty, IsString, IsBoolean, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
class AuthorForUpdatePostDto {
@IsNotEmpty()
@IsString()
id!: string;
}
export default class UpdatePostDto {
@IsOptional()
@IsNotEmpty()
@IsString()
title?: string;
@IsOptional()
@IsNotEmpty()
@IsString()
content?: string;
@IsOptional()
@IsBoolean()
published?: boolean;
@IsOptional()
@ValidateNested()
@Type(() => AuthorForUpdatePostDto)
author?: AuthorForUpdatePostDto;
}Query
arkos generate query-schema --module post
arkos g qs -m postOutput: src/modules/post/schemas/query-post.schema.ts
Includes page, limit, sort, and fields pagination fields, then adds filter schemas per field type — string fields get icontains, number fields get equals/gte/lte, datetime fields get equals/gte/lte.
import { z } from "zod";
const StringFilterSchema = z.object({
icontains: z.string().optional()
});
const QueryPostSchema = z.object({
page: z.coerce.number().optional(),
limit: z.coerce.number().max(100).optional(),
sort: z.string().optional(),
fields: z.string().optional(),
title: StringFilterSchema.optional(),
content: StringFilterSchema.optional(),
published: z.boolean().optional(),
createdAt: StringFilterSchema.optional(),
updatedAt: StringFilterSchema.optional(),
});
export default QueryPostSchema;
export type QueryPostSchemaType = z.infer<typeof QueryPostSchema>;arkos generate query-dto --module post
arkos g qd -m postOutput: src/modules/post/dtos/query-post.dto.ts
Same structure, using class-validator filter classes instead of Zod filter schemas.
import { IsOptional, IsString, IsNumber, IsBoolean, ValidateNested, Max, IsNotEmpty } from "class-validator";
import { Type, Transform } from "class-transformer";
class StringFilter {
@IsOptional()
@IsString()
@Type(() => String)
icontains?: string;
}
export default class PostQueryDto {
@IsOptional()
@IsNumber()
@Transform(({ value }) => (value ? Number(value) : undefined))
page?: number;
@IsOptional()
@IsNumber()
@Max(100)
@Transform(({ value }) => (value ? Number(value) : undefined))
limit?: number;
@IsOptional()
@IsNotEmpty()
@IsString()
@Type(() => String)
sort?: string;
@IsOptional()
@IsNotEmpty()
@IsString()
@Type(() => String)
fields?: string;
@IsOptional()
@ValidateNested()
@Type(() => StringFilter)
title?: StringFilter;
@IsOptional()
@IsBoolean()
published?: boolean;
}Base
The base schema/DTO includes all fields from the model without create or update semantics — useful as a reference type or for response shapes.
arkos generate schema --module post
arkos g sc -m postOutput: src/modules/post/schemas/post.schema.ts
Relation fields are excluded. All other fields are included as-is with their optionality from the Prisma schema.
arkos generate dto --module post
arkos g d -m postOutput: src/modules/post/dtos/post.dto.ts
Auth Validation
These commands generate validation specifically for the authentication routes (login, signup, updateMe, updatePassword). They are always scoped to the auth module and always write to src/modules/auth/schemas/ or src/modules/auth/dtos/.
All commands require -m auth:
arkos g ls -m auth # login-schema
arkos g ss -m auth # signup-schema
arkos g ums -m auth # update-me-schema
arkos g ups -m auth # update-password-schemaLogin
arkos generate login-schema -m auth
arkos g ls -m authOutput: src/modules/auth/schemas/login.schema.ts
The generated username fields are driven by your authentication.login.allowedUsernames config. If you allow multiple username fields (e.g. email and username) each is generated as optional, with a comment indicating at least one is required. Defaults to username if not configured.
import { z } from "zod";
// At least one of: email, username is required
const LoginSchema = z.object({
email: z.string().email().optional(),
username: z.string().min(1).optional(),
password: z.string().min(8)
});
export default LoginSchema;
export type LoginSchemaType = z.infer<typeof LoginSchema>;arkos generate login-dto -m auth
arkos g ld -m authOutput: src/modules/auth/dtos/login.dto.ts
import { IsOptional, IsEmail, IsString, IsNotEmpty, MinLength, Matches } from "class-validator";
export default class LoginDto {
@IsOptional()
@IsEmail()
email?: string;
@IsOptional()
@IsString()
username?: string;
@IsNotEmpty()
@IsString()
@MinLength(8)
@Matches(/[a-z]/, { message: "Must contain lowercase" })
@Matches(/[A-Z]/, { message: "Must contain uppercase" })
@Matches(/[0-9]/, { message: "Must contain number" })
password!: string;
}Signup
arkos generate signup-schema -m auth
arkos g ss -m authOutput: src/modules/auth/schemas/signup.schema.ts
Generated from your User Prisma model. Restricted fields (roles, isActive, isStaff, isSuperUser, passwordChangedAt, and similar internal fields) are excluded. The password field always gets strong regex validation.
import { z } from "zod";
const SignupSchema = z.object({
username: z.string(),
email: z.string().email(),
password: z.string().min(8)
.regex(/[a-z]/, "Must contain lowercase")
.regex(/[A-Z]/, "Must contain uppercase")
.regex(/[0-9]/, "Must contain number"),
});
export default SignupSchema;
export type SignupSchemaType = z.infer<typeof SignupSchema>;arkos generate signup-dto -m auth
arkos g sd -m authOutput: src/modules/auth/dtos/signup.dto.ts
import { IsNotEmpty, IsString, IsEmail, MinLength, Matches } from "class-validator";
export default class SignupDto {
@IsNotEmpty()
@IsString()
username!: string;
@IsNotEmpty()
@IsEmail()
email!: string;
@IsNotEmpty()
@IsString()
@MinLength(8)
@Matches(/[a-z]/, { message: "Must contain lowercase" })
@Matches(/[A-Z]/, { message: "Must contain uppercase" })
@Matches(/[0-9]/, { message: "Must contain number" })
password!: string;
}Update Me
arkos generate update-me-schema -m auth
arkos g ums -m authOutput: src/modules/auth/schemas/update-me.schema.ts
Like signup but all fields are optional, and password is excluded (password changes go through the dedicated updatePassword route).
import { z } from "zod";
const UpdateMeSchema = z.object({
username: z.string().optional(),
email: z.string().email().optional(),
});
export default UpdateMeSchema;
export type UpdateMeSchemaType = z.infer<typeof UpdateMeSchema>;arkos generate update-me-dto -m auth
arkos g umd -m authOutput: src/modules/auth/dtos/update-me.dto.ts
import { IsOptional, IsNotEmpty, IsString, IsEmail } from "class-validator";
export default class UpdateMeDto {
@IsOptional()
@IsNotEmpty()
@IsString()
username?: string;
@IsOptional()
@IsEmail()
email?: string;
}Update Password
arkos generate update-password-schema -m auth
arkos g ups -m authOutput: src/modules/auth/schemas/update-password.schema.ts
import { z } from "zod";
const UpdatePasswordSchema = z.object({
currentPassword: z.string().min(1),
newPassword: z.string().min(8)
.regex(/[a-z]/, "Must contain lowercase")
.regex(/[A-Z]/, "Must contain uppercase")
.regex(/[0-9]/, "Must contain number")
});
export default UpdatePasswordSchema;
export type UpdatePasswordSchemaType = z.infer<typeof UpdatePasswordSchema>;arkos generate update-password-dto -m auth
arkos g upd -m authOutput: src/modules/auth/dtos/update-password.dto.ts
import { IsNotEmpty, IsString, MinLength, Matches } from "class-validator";
export default class UpdatePasswordDto {
@IsNotEmpty()
@IsString()
currentPassword!: string;
@IsNotEmpty()
@IsString()
@MinLength(8)
@Matches(/[a-z]/, { message: "Must contain lowercase" })
@Matches(/[A-Z]/, { message: "Must contain uppercase" })
@Matches(/[0-9]/, { message: "Must contain number" })
newPassword!: string;
}Notes
Model validation commands (create-schema, update-schema, etc.) are only available for modules that exist in your Prisma schema. Running them for an unknown module produces an error.
Auth validation commands are only available with -m auth.
All generated files use the field types, optionality, and enum values from your actual Prisma schema — regenerate after schema changes.