Validation Pipe with Atscript
Moost’s validation story is now powered by Atscript. Every DTO generated by Atscript exposes a validator(opts)
factory that @atscript/moost-validator
uses at runtime.
By combining the Moost pipeline system with these runtime hooks you get type-safe validation without writing imperative checks or maintaining parallel schemas.
How it works
- Generate annotated DTOs with Atscript (
*.as
source files) and consume them from your application code. - Register the validation pipe provided by
@atscript/moost-validator
. The pipe inspects every handler argument and, when the declared type was annotated by Atscript, it runsvalidator().validate(value)
. - Optionally register the error interceptor to convert rich validation failures into
400 Bad Request
responses.
import { Moost } from 'moost'
import { MoostHttp, Body, Post } from '@moostjs/event-http'
import { validatorPipe, validationErrorTransform } from '@atscript/moost-validator'
import { UsersController } from './users.controller'
const app = new Moost()
app.adapter(new MoostHttp())
app.applyGlobalPipes(validatorPipe())
app.applyGlobalInterceptors(validationErrorTransform())
app.registerControllers(UsersController)
await app.init()
With this setup every request body, query param, or DI value whose type exposes validator()
is validated before your handler executes.
Example DTO
@label "Create User"
export interface CreateUserDto {
@label "Display name"
name: string
@expect.pattern "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", "u", "Invalid email"
email: string
@label "Password"
password: string
@labelOptional
roles?: string[]
}
Atscript emits the metadata at build time. Check the Atscript reference for the full authoring experience, including decorators, CLI options, and IDE integration.
Applying the pipe locally
You can scope validation to a specific controller or method using the @UseValidatorPipe()
decorator, or stack it with other pipes via @Pipe()
.
import { Controller, Pipe } from 'moost'
import { Body, Post } from '@moostjs/event-http'
import { UseValidatorPipe, UseValidationErrorTransform } from '@atscript/moost-validator'
import { CreateUserDto } from './create-user.dto.as'
@UseValidatorPipe()
@UseValidationErrorTransform()
@Controller('users')
export class UsersController {
@Post()
async create(@Body() dto: CreateUserDto) {
// dto is guaranteed to satisfy the Atscript schema
}
}
Per-parameter configuration
validatorPipe(opts)
accepts any subset of TValidatorOptions
. For instance, to allow unknown properties and collect all errors:
app.applyGlobalPipes(
validatorPipe({
unknwonProps: 'ignore',
errorLimit: 50,
}),
)
The options are passed straight to Atscript’s validator, so you get feature parity with the standalone runtime.
Handling validation errors
When validation fails validator.validate()
throws ValidatorError
. Without the interceptor Moost would treat the error as unhandled and return 500 Internal Server Error
. The provided interceptor (validationErrorTransform()
or @UseValidationErrorTransform()
) converts it into HttpError(400)
with the underlying details exposed in the response body:
{
"statusCode": 400,
"message": "Validation failed",
"errors": [
{ "path": "email", "message": "Invalid email" },
{ "path": "password", "message": "Length must be >= 12" }
]
}
Adapters such as @moostjs/event-http
serialise that object as JSON automatically.
Validating beyond HTTP
Because the pipe hooks into Moost’s generic pipeline system you can reuse it for:
- CLI handlers in
@moostjs/event-cli
- Workflow steps in
@moostjs/event-wf
- Any custom event adapter you build
Wherever Moost can resolve parameters, validatorPipe()
can run.