Usage
Validate request data inputs such as req.body, req.query, and req.params declaratively on any route — no middleware boilerplate, no manual error handling. Drop a Zod schema or class-validator DTO into the validation config, and Arkos handles the rest: error responses and automatic OpenAPI spec generation.
This validation system works the same way across both ArkosRouter and RouteHook.
Request Body Validation
Applied on routes that receive a request body — typically POST, PUT, and PATCH.
import { ArkosRouter } from "arkos";
import z from "zod";
import postController from "./post.controller";
const router = ArkosRouter();
const CreatePostSchema = z.object({
title: z.string().min(1),
content: z.string().min(1),
published: z.boolean().optional(),
authorId: z.string().uuid(),
});
router.post(
{
path: "/api/posts",
validation: {
body: CreatePostSchema,
},
},
postController.createPost
);
export default router;RouteHook is the new name for export const config: RouterConfig. If you have existing code using the old name it still works but will log a deprecation warning. See Route Hook for full details.
Validation error response:
{
"status": "error",
"message": "Invalid Data",
"code": 400,
"errors": [
{
"property": "authorId",
"constraints": {
"isUuid": "authorId must be a valid UUID"
}
}
]
}Request Query & Params Validation
Applied on routes that receive URL query strings or path parameters.
Query and params values arrive as strings from the URL. Use z.coerce or @Type() to cast them to the correct type — or use the CLI code generation which handles this automatically.
import { ArkosRouter } from "arkos";
import z from "zod";
import userController from "./user.controller";
const router = ArkosRouter();
router.get(
{
path: "/api/users",
validation: {
query: z.object({
role: z.enum(["admin", "user"]).optional(),
active: z.coerce.boolean().optional(),
limit: z.coerce.number().int().min(1).max(100).optional(),
}),
},
},
userController.getUsers
);
router.get(
{
path: "/api/users/:id",
validation: {
params: z.object({
id: z.string().uuid("Invalid user ID"),
}),
},
},
userController.getUser
);
router.patch(
{
path: "/api/users/:id",
validation: {
params: z.object({
id: z.string().uuid(),
}),
body: z.object({
name: z.string().min(1).optional(),
email: z.string().email().optional(),
}),
query: z.object({
notify: z.coerce.boolean().optional(),
}),
},
},
userController.updateUser
);
export default router;Validation error response:
{
"status": "error",
"message": "Invalid Data",
"code": 400,
"errors": [
{
"property": "id",
"constraints": {
"isUuid": "id must be a valid UUID"
}
}
]
}Validation With File Uploads
When combining validation with file uploads, only pass text fields to validation.body — file fields are handled separately by the uploads config.
router.post(
{
path: "/api/users/:id/avatar",
validation: {
params: z.object({ id: z.string().uuid() }),
body: z.object({ caption: z.string().optional() }),
// no avatar field here — handled by uploads
},
experimental: {
uploads: { type: "single", field: "avatar", required: true },
},
},
userController.uploadAvatar
);required | Behavior |
|---|---|
true | Returns 400 if no file is uploaded |
false | Proceeds without a file |
See File Upload guide for full configuration.
Accessing Validated Data
Arkos automatically types req.body, req.query, and req.params from your validation schema — every handler and middleware in the route stack shares the same typed req with no manual generic declarations needed.
import { ArkosRouter } from "arkos";
import z from "zod";
import userController from "@/src/modules/user/user.controller";
const router = ArkosRouter();
const UpdateUserBody = z.object({
name: z.string().min(1).optional(),
email: z.string().email().optional(),
});
const UpdateUserParams = z.object({
id: z.string().uuid(),
});
const UpdateUserQuery = z.object({
notify: z.coerce.boolean().optional(),
});
router.patch(
{
path: "/api/users/:id",
validation: {
params: UpdateUserParams,
body: UpdateUserBody,
query: UpdateUserQuery,
},
},
logMiddleware, // req.params, req.body, req.query all typed here
userController.updateOne // and here — same signature, no extra work
);
export default router;RouteHook is the new name for export const config: RouterConfig. If you have existing code using the old name it still works but will log a deprecation warning. See Route Hook for full details.
import { ArkosRequest, ArkosResponse } from "arkos";
const updateOne = async (req: ArkosRequest, res: ArkosResponse) => {
const { id } = req.params; // string, validated UUID
const { notify } = req.query; // boolean, coerced
const { name, email } = req.body; // typed, validated
};
export default { updateOne };