Background
Validation is performed inline today, often via FluentValidation called from inside the endpoint handler. Laravel's Form Requests bundle (a) parameter binding, (b) authorization, (c) validation rules, and (d) prepared/normalized data into a single class injected into the controller. The handler receives an already valid request object.
Motivation
- Removes boilerplate from endpoint bodies
- Centralizes shape + rules per request type (re-usable across endpoints, easy to test)
- Plays well with the source generator (TS interfaces auto-emitted)
- Forces a consistent 422 error envelope
Design sketch
[FormRequest]
public sealed class CreateProductRequest : FormRequest
{
public string Name { get; init; } = "";
public decimal Price { get; init; }
public string? Sku { get; init; }
public override bool Authorize(ClaimsPrincipal user) => user.HasPermission("Products.Create");
public override IRuleBuilder Rules() => Rules
.For(x => x.Name).NotEmpty().MaxLength(200)
.For(x => x.Price).GreaterThan(0)
.For(x => x.Sku).Optional().Matches("^[A-Z0-9-]+$");
public override void Prepare()
{
Sku = Sku?.Trim().ToUpperInvariant();
}
}
Endpoint usage:
public IResult Handle(CreateProductRequest request, IProductService svc) =>
Results.Ok(svc.Create(request));
- Source generator emits a binder + validator wrapper that runs
Authorize → Bind → Prepare → Validate and short-circuits with 403/422 automatically
- Failed validation returns the standard problem+json envelope
{ "errors": { "name": ["..."] } }
- TS interface auto-emitted from
[FormRequest] (same path as [Dto])
- Existing endpoints can opt in incrementally; not a breaking change
Acceptance criteria
References
Background
Validation is performed inline today, often via FluentValidation called from inside the endpoint handler. Laravel's Form Requests bundle (a) parameter binding, (b) authorization, (c) validation rules, and (d) prepared/normalized data into a single class injected into the controller. The handler receives an already valid request object.
Motivation
Design sketch
Endpoint usage:
Authorize → Bind → Prepare → Validateand short-circuits with403/422automatically{ "errors": { "name": ["..."] } }[FormRequest](same path as[Dto])Acceptance criteria
FormRequestbase +[FormRequest]attribute inSimpleModule.CoreReferences