Validation Pipe
validatorPipe(opts?) is the main entry point in @atscript/moost-validator.
It checks a handler argument's runtime type. If that type is an Atscript annotated type, it runs the model's validator before your handler executes.
The Most Common Setup
Register it globally:
import { Moost } from 'moost'
import { validatorPipe } from '@atscript/moost-validator'
const app = new Moost()
app.applyGlobalPipes(validatorPipe())That is the best default for most apps.
Validate Request Bodies
This is the most common use case:
import { Controller } from 'moost'
import { Body, Post } from '@moostjs/event-http'
import { CreateUserDto } from './create-user.dto.as'
@Controller('users')
export class UsersController {
@Post()
async create(@Body() dto: CreateUserDto) {
// dto has already been validated
return this.users.create(dto)
}
}If validation fails, the pipe throws ValidatorError. For HTTP apps, pair it with Error Handling.
Normalized Input
When the parameter's type declares @normalize.* normalizers, the pipe applies them first (with normalizeForType()), validates the normalized value and passes it to your handler:
export interface CreateUserDto {
@normalize.trim
@normalize.lowercase
email: string.email
@normalize.trim
name: string.required
}@Post()
async create(@Body() dto: CreateUserDto) {
// a body of { email: ' Ann@Example.COM ', name: ' Ann ' } arrives as
// { email: 'ann@example.com', name: 'Ann' }
}This applies to every parameter source, request bodies included. When union members normalize a value differently, the pipe settles it with its own validator options (partial, unknownProps, …), so a partial update is normalized as the member it validates as. The request's original object is not changed: the handler gets a normalized copy (or the same object when nothing needed normalizing). Together with the Coercion Pipe, the order is coerce → normalize → validate.
Validate PATCH Payloads
For partial updates, pass Atscript validator options to the pipe:
import { Controller } from 'moost'
import { Patch, Body } from '@moostjs/event-http'
import { UseValidatorPipe } from '@atscript/moost-validator'
import { UpdateUserDto } from './update-user.dto.as'
@Controller('users')
export class UsersController {
@Patch(':id')
@UseValidatorPipe({ partial: true })
async patch(@Body() dto: UpdateUserDto) {
return this.users.patch(dto)
}
}Use partial: 'deep' when nested objects should also allow missing fields.
Strip Unknown Properties
This is useful for request bodies that may contain extra fields:
app.applyGlobalPipes(
validatorPipe({
unknownProps: 'strip',
})
)Options:
'error'— reject unknown props'ignore'— keep them'strip'— remove them from the value
Apply It Per Controller Or Handler
Global registration is usually best, but the decorator form is useful when only part of the app uses Atscript DTOs.
Per Controller
import { Controller } from 'moost'
import { UseValidatorPipe } from '@atscript/moost-validator'
@UseValidatorPipe()
@Controller('users')
export class UsersController {}Per Handler
import { Controller } from 'moost'
import { Body, Post } from '@moostjs/event-http'
import { UseValidatorPipe } from '@atscript/moost-validator'
import { CreateUserDto } from './create-user.dto.as'
@Controller('users')
export class UsersController {
@Post()
@UseValidatorPipe()
async create(@Body() dto: CreateUserDto) {}
}UseValidatorPipe(opts?) is sugar for @Pipe(validatorPipe(opts)).
Validate Params And Query Values Carefully
The pipe validates the value it receives. It does not coerce strings into numbers, booleans, or dates.
That means:
- body payloads are usually a good fit because JSON parsing already gives you numbers, booleans, arrays, and objects
- raw HTTP params and query values arrive as strings, so on their own only string-based Atscript types fit there
Good example:
export type EmailQuery = string.emailimport { Controller } from 'moost'
import { Get, Query } from '@moostjs/event-http'
import { EmailQuery } from './queries.as'
@Controller('users')
export class UsersController {
@Get('search')
async search(@Query('email') email: EmailQuery) {
return this.users.searchByEmail(email)
}
}If a param or query needs numeric or boolean validation, register the Coercion Pipe ahead of this one — it converts "42" to 42 before validation runs, so numeric .as aliases work directly as param types.
Optional Params
Parameters marked with Moost's @Optional() decorator are skipped by the pipe when their value is undefined or null. This matters for slots that are genuinely missing at the framework level — optional query strings, optional CLI flags — where the underlying Atscript type itself is not nullable.
import { Controller, Optional } from 'moost'
import { Get, Query } from '@moostjs/event-http'
import { WikiName } from './types.as'
@Controller('search')
export class SearchController {
@Get()
async search(@Query('wiki') @Optional() wiki?: WikiName) {
// `wiki` is undefined when the query string is absent — no validation runs.
// When present, `WikiName` constraints (minLength, pattern, ...) are enforced.
}
}The pipe still validates the value whenever one is provided, so ?wiki= (empty) or ?wiki=bad name will fail with ValidatorError as usual.
For request bodies, this is rarely needed — bodies are usually required at the framework level, and individual properties can be marked optional inside the Atscript interface itself.
Use Reusable Validated Primitive Types
This is one of the nicest patterns in Atscript + Moost.
Define a validated primitive once:
export type Email = string.emailThen use those types directly in handlers:
import { Controller } from 'moost'
import { Body, Post } from '@moostjs/event-http'
import { Email } from './types.as'
@Controller('newsletter')
export class NewsletterController {
@Post('subscribe')
async subscribe(@Body() email: Email) {
return this.newsletter.subscribe(email)
}
}That is hard to model with class-validator, because its validation model is centered on decorated classes and their properties. Atscript validates the type itself, so standalone reusable primitives work naturally.
Options At A Glance
validatorPipe(opts?) accepts the same Partial<TValidatorOptions> object as Atscript's runtime validator.
Most useful options:
partial— allow missing properties (PATCH)unknownProps—'strip' | 'ignore' | 'error'errorLimit— stop after N errors
Advanced options
These are available but rarely needed in HTTP request validation. Reach for them only when you understand the underlying validator behavior:
skipList— paths to skip during validationreplace— value-replacement hook used by validator pluginsplugins— custom validator plugin functions
If you already know the TypeScript validator API, the same options work here. See Validation Reference for the full low-level option details.
Beyond HTTP
The pipe is not tied to HTTP. It works anywhere Moost resolves handler arguments.
What is HTTP-specific is the built-in error transform, because that part returns HttpError(400).
Next Steps
- Coercion Pipe — coerce string params/query before this pipe validates them
- Error Handling — convert
ValidatorErrorinto HTTP responses - Validation Guide — deeper validator behavior and options
- Why Atscript In Moost? — when this package is the right fit