Skip to content

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.

Full Atscript + Moost docs

For the complete guide — installation, configuration, and all available options — see the @atscript/moost-validator documentation.

How it works

  1. Generate annotated DTOs with Atscript (*.as source files) and consume them from your application code.
  2. 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 runs validator().validate(value).
  3. Optionally register the error interceptor to convert rich validation failures into 400 Bad Request responses.
ts
import { Moost } from 'moost'
import { MoostHttp } from '@moostjs/event-http'
import { validatorPipe, validationErrorTransform } from '@atscript/moost-validator'
import { UsersController } from './users.controller'

const app = new Moost()
void app.adapter(new MoostHttp()).listen(3000)
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

atscript
@meta.label "Create User"
export interface CreateUserDto {
  @meta.label "Display name"
  name: string

  @expect.pattern "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", "u", "Invalid email"
  email: string

  @meta.label "Password"
  password: string

  @meta.description "Optional role list"
  roles?: string[]
}

Atscript emits the metadata at build time. Check the Atscript documentation 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().

ts
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
  }
}

Configuring the validator

validatorPipe(opts) accepts any subset of TValidatorOptions — the same options work in @UseValidatorPipe(opts) for per-controller/per-method scoping. For instance, to allow unknown properties and collect up to 50 errors (the default limit is 10):

ts
app.applyGlobalPipes(
  validatorPipe({
    unknownProps: '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:

json
{
  "statusCode": 400,
  "message": "email: Invalid email",
  "error": "Bad Request",
  "_body": [
    { "path": "email", "message": "Invalid email" },
    { "path": "password", "message": "Length must be >= 12" }
  ]
}

The message is the first failing rule ("<path>: <message>"), and the full error list is exposed under the _body key.

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.

Released under the MIT License.