SHOPin Logo
Skip to main documentation content

BFF input validation

The NestJS BFF (apps/bff) validates incoming requests with Zod via decorators and ZodValidationPipe. Failures become FrontendInputValidationException400 with issues (see Error handling).

Decorators are exported from common/validation:

TypeScript
import { Controller, Get, Post } from '@nestjs/common'
import { ZodBody, ZodQuery, ZodParam } from '../../common/validation'
import { z } from 'zod'

const CreateExampleBodySchema = z.object({ title: z.string().min(1) })

@Controller('example')
export class ExampleController {
  @Get(':productSlug')
  getOne(
    @ZodParam(z.string().min(1), 'productSlug') productSlug: string,
    @ZodQuery(z.object({ page: z.coerce.number().optional() }))
    query: { page?: number }
  ) {
    return { productSlug, query }
  }

  @Post()
  create(@ZodBody(CreateExampleBodySchema) body: z.infer<typeof CreateExampleBodySchema>) {
    return body
  }
}
  • ZodValidationPipe runs on those parameters; invalid input → 400 with Zod issues.
  • Schemas are imported from @core/contracts (or inline z where the codebase does).

Outgoing (responses to the presentation)

Parse or strip service results with the same contract schemas so you never return shapes the client cannot handle:

TypeScript
import { z } from 'zod'

const MainNavigationResponseSchema = z.object({ items: z.array(z.object({ id: z.string() })) })

async function getNavigationExample(
  navigationService: { getNavigation: () => Promise<unknown> }
): Promise<z.infer<typeof MainNavigationResponseSchema>> {
  return MainNavigationResponseSchema.strip().parse(await navigationService.getNavigation())
}

Real usage: e.g. navigation.service.ts patterns.


Related


Back to Input validation · Back to Validation & resilience · Back to How to work with SHOPin