Nestjs and Class Validator - at least one field should not be empty - nestjs

I have NestJS API, which has a PATCH endpoint for modifying a resource. I use the class-validator library for validating the payload. In the DTO, all fields are set to optional with the #IsOptional()decorator. Because of that, if I send an empty payload, the validation goes through and then the update operation errors.
I am wondering if there is a simple way to have all fields set to optional as I do and at the same time make sure that at least one of them is not empty, so the object is not empty.
Thanks!

I don't know if it is possible using DTO.
For that purpose I use Pipes. Like this:
Injectable()
export class ValidatePayloadExistsPipe implements PipeTransform {
transform(payload: any): any {
if (!Object.keys(payload).length) {
throw new BadRequestException('Payload should not be empty');
}
return payload;
}
}

Related

What type of data should be passed to domain events?

I've been struggling with this for a few days now, and I'm still not clear on the correct approach. I've seen many examples online, but each one does it differently. The options I see are:
Pass only primitive values
Pass the complete model
Pass new instances of value objects that refer to changes in the domain/model
Create a specific DTO/object for each event with the data.
This is what I am currently doing, but it doesn't convince me. The example is in PHP, but I think it's perfectly understandable.
MyModel.php
class MyModel {
//...
private MediaId $id;
private Thumbnails $thumbnails;
private File $file;
//...
public function delete(): void
{
$this->record(
new MediaDeleted(
$this->id->asString(),
[
'name' => $this->file->name(),
'thumbnails' => $this->thumbnails->toArray(),
]
)
);
}
}
MediaDeleted.php
final class MediaDeleted extends AbstractDomainEvent
{
public function name(): string
{
return $this->payload()['name'];
}
/**
* #return array<ThumbnailArray>
*/
public function thumbnails(): array
{
return $this->payload()['thumbnails'];
}
}
As you can see, I am passing the ID as a string, the filename as a string, and an array of the Thumbnail value object's properties to the MediaDeleted event.
How do you see it? What type of data is preferable to pass to domain events?
Updated
The answer of #pgorecki has convinced me, so I will put an example to confirm if this way is correct, in order not to change too much.
It would now look like this.
public function delete(): void
{
$this->record(
new MediaDeleted(
$this->id,
new MediaDeletedEventPayload($this->file->copy(), $this->thumbnail->copy())
)
);
}
I'll explain a bit:
The ID of the aggregate is still outside the DTO, because MediaDeleted extends an abstract class that needs the ID parameter, so now the only thing I'm changing is the $payload array for the MediaDeletedEventPayload DTO, to this DTO I'm passing a copy of the value objects related to the change in the domain, in this way I'm passing objects in a reliable way and not having strange behaviours if I pass the same instance.
What do you think about it?
A domain event is simply a data-holding structure or class (DTO), with all the information related to what just happened in the domain, and no logic. So I'd say Create a specific DTO/object for each event with the data. is the best choice. Why don't you start with the less is more approach? - think about the consumers of the event, and what data might they need.
Also, being able to serialize and deserialize the event objects is a good practice, since you could want to send them via a message broker (although this relates more to integration events than domain events).

transform value if falsy

I'm validating my DTOs with the class-validator package. I enabled the transformation via
app.useGlobalPipes(
new ValidationPipe({
transform: true,
}),
);
in my main.ts file as described in the docs
https://docs.nestjs.com/techniques/validation#transform-payload-objects
I have a optional configuration field in my DTO. This field should be transformed to an empty object if it doesn't exist. The transformation decorator is described here
https://docs.nestjs.com/techniques/serialization#transform
I was hoping to come up with this solution:
export class MyDTO {
#IsObject()
#IsOptional()
#Transform(configuration => configuration || {})
public configuration: object;
}
When I call my API route
#Post()
public create(#Body() myDTO: MyDTO): void {
console.log(myDTO);
}
with an empty body, so without the field configuration my MyDTO instance is
{}
although I would expect it to be
{
configuration: {}
}
What is wrong or what am I missing? I tried to debug the code and it never hits the transformation function. So the #Transform doesn't trigger.
Update
It seems I have to do this
#IsObject()
#IsOptional()
#Transform(configuration => configuration || {}) // will be used if the field exists
public configuration: object = {}; // will be used if the field doesn't exist
The initial value will be used if you pass in an empty body. The transformation only runs if you pass in the field but assign a value like null to it.
Gonna go ahead n put this here too: why not just let typescript manage the default value with setting the value like
export class MyDTO {
#IsObject()
#IsOptional()
public configuration: object = {};
}
That way, if you get a value, great, and if it isn't there, class-transform will put the correct value there.
Looks like there is more discussion about solutions here.

Auto-fill DTO fields with other data than request body data

I have a class CreateFolderDto with two readonly fields:
export class CreateFolderDto {
public readonly name: string
public readonly user_id: number
}
I have a controller which is:
#UseGuards(AuthGuard('jwt'))
#Post()
public create(#Request() req, #Body() createFolderDto: CreateFolderDto) {
return this.folderService.create(createFolderDto)
}
The request send to my controller is a good one, I only send the name in json format with an accessToken in the header. The accessToken permit me to get my user_id from the request with req.user.id.
The DTO field user_id is not automatically filled. I would like to fill it automatically.
Is it a way to auto-fill my createFolderDto.user_id variable ?
#Body only wraps actual request body into instance of the CreateFolderDto class. As the body which comes to your endpoint has no such a field, you need to add it manually.
Normally, aggregated fields could be added with custom constructor of your DTO:
export class CreateFolderDto {
public readonly name: string
public readonly session_uuid: string
constructor(bodyValue: any = {}) {
this.name = bodyValue.name
this.session_uuid = generateUuid()
}
}
But in your case, user is attached to request itself, so I believe you have the following options:
Check out your code which attaches the user to request itself. If you are using JWT Auth described in NestJS docs, you cannot do this that way.
You can write custom Interceptor:
Injectable()
export class ExtendBodyWithUserId implements NestInterceptor {
async intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest()
request.body.user_id = request.user
return next.handle()
}
}
// usage
#UseGuards(AuthGuard('jwt'))
#UseInterceptors(ExtendBodyWithUserId)
#Post()
public create(#Request() req, #Body() createFolderDto: CreateFolderDto) {
return this.folderService.create(createFolderDto)
}
Last but not least, some personal recommendation. Consider how much you will use this interceptor as an extension, as too many of 'extras' like this bloat the codebase.
I would recommend to change the folderService signature to:
create(createFolderDto: CreateFolderDto, user: User), where folder dto has only the name, without user-related entry. You keep the consistency, separation and clear intentions. In the implementation of create you can just pass user.id further.
And going this way, you don't have to write custom interceptors.
Pick your way and may the consistency in your codebase be with you!

Automatically parse query parameter to object when defined in NestJS

I am writing a NestJS application. Some of the endpoints support sorting e.g. http://127.0.0.1:3000/api/v1/members?sort=-id&take=100 Which means sort by id descending.
This parameter arrives as a #Query parameter and is passed to my service. This service transforms it into an object which is used by TypeORM:
{
id: 'DESC'
}
I don't want to call this conversion method manually every time I need sorting.
I've tried an intercepter but this one could not easily change the request parameters into the desired object.
A pipe worked but then I still need to add #Query(new SortPipe()) for every endpoint definition.
Another option is in the repository itself. The NestJS documentation is very well written, but misses guidance in where to put what.
Is there someone who had a similar issue with converting Query parameters before they are used in NestJS, and can explain what approach is the best within NestJS?
This question might look like an opinion based question, however I am looking for the way it is supposed to be done with the NestJS philosophy in mind.
Pipes are probably the easiest way to accomplish this. Instead of adding your pipe for every endpoint definition you can add a global pipe that will be called on every endpoint. In your main.ts:
async function bootstrap() {
...
app.useGlobalPipes(new SortPipe());
...
}
You can then create a pipe like this:
import { PipeTransform, Injectable, ArgumentMetadata } from '#nestjs/common';
#Injectable()
export class SortPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
const { type } = metadata;
// Make sure to only run your logic on queries
if (type === 'query') return this.transformQuery(value);
return value;
}
transformQuery(query: any) {
if (typeof query !== 'object' || !value) return query;
const { sort } = query;
if (sort) query.sort = convertForTypeOrm(sort);
return query;
}
}
If you do not want sort value on ALL endpoints to be automatically converted, you can pass custom parameter to #Query(), for example #Query('sort'). And then:
transform(value: any, metadata: ArgumentMetadata) {
const { type, data } = metadata;
// Make sure to only run your logic on queries when 'sort' is supplied
if (type === 'query' && data === 'sort') return this.transformQuery(value);
return value;
}

ServiceStack - Request Binding JSON encoded parameter

I have an existing application that sends a Request with a parameter named 'filters'. The 'filters' parameter contains a string that is JSON encoded. Example:
[{"dataIndex":"fieldName", "value":"fieldValue"}, {"dataIndex":"field2", "value":"value2"}].
Using ServiceStack, I would like to bind this as a property on a C# object (class Grid). Is there a preferred method to handle this? Here are the options I can think of. I don't think either 'feel' correct.
Option 1:
I do have a 'ServiceModel' project and this would create a dependency on it which I don't really like.
In AppHost.Configure() method add
RequestBinders[typeof(Grid)] => httpReq => {
return new Grid() {
Filters = new ServiceStack.Text.JsonSerializer<IList<Filter>>().DeserializeFromString(httpReq.QueryString["filters"])
}
}
Option 2:
Seems kind of 'hacky'
public class Grid
{
private string _filters;
public dynamic Filters {
get
{
ServiceStack.Text.JsonSerializer<IList<Filter().DeserializeFromString(_filters);
}
set
{
_filters = value;
}
}
}
You can send Complex objects in ServiceStack using the JSV Format.
If you want to send JSON via the QueryString you can access it from inside your Service of Request filters with something like:
public object Any(Request req) {
var filters = base.Request.QueryString["Filters"].FromJson<List<Filter>>();
}
Note: Interfaces on DTOs are bad practice.

Resources