Skip to content

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:

typescript
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:

typescript
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.

Validate PATCH Payloads ​

For partial updates, pass Atscript validator options to the pipe:

typescript
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:

typescript
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 ​

typescript
import { Controller } from 'moost'
import { UseValidatorPipe } from '@atscript/moost-validator'

@UseValidatorPipe()
@Controller('users')
export class UsersController {}

Per Handler ​

typescript
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 are often strings, so string-based Atscript types fit best there

Good example:

atscript
export type EmailQuery = string.email
typescript
import { 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 validation, parse it before it reaches this pipe or validate it as a string-shaped contract instead.

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.

typescript
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:

atscript
export type Email = string.email

Then use those types directly in handlers:

typescript
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 validation
  • replace — value-replacement hook used by validator plugins
  • plugins — 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 ​

Released under the MIT License.