79

The NestJS documentation showcases how to add DTOs to use in Controllers to validate request objects by using class-validator package. DTOs described there are TypeScript classes. Now, while controllers deal with DTOs(TS Classes), NestJS providers (or services), on the other hand, makes use of TypeScript interfaces. These DTOs and interfaces are pretty much of the same shape.

Now, I am seeing duplication of shape definition here. And wondering if the interfaces are needed at all?

Can we not make DTOs source of truth for the shape and validations? One of the approaches we were considering (to make the DTO source of truth) was, to have an openapi generator take the DTOs as input and generate openapi definition and from there another codegen can generate a set of typescript interfaces to be consumed by NestJS itself and which can be shared with another set of consumer applications like Angular too.

Have any people come across a similar problem? What do you think of the above? Feedback appreciated.

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Ajitabh
  • 791
  • 1
  • 5
  • 4

18 Answers18

34

According to the Nestjs docs:

But first (if you use TypeScript), we need to determine the DTO (Data Transfer Object) schema. A DTO is an object that defines how the data will be sent over the network. We could determine the DTO schema by using TypeScript interfaces, or by simple classes. Interestingly, we recommend using classes here. Why? Classes are part of the JavaScript ES6 standard, and therefore they are preserved as real entities in the compiled JavaScript. On the other hand, since TypeScript interfaces are removed during the transpilation, Nest can't refer to them at runtime. This is important because features such as Pipes enable additional possibilities when they have access to the metatype of the variable at runtime.

Matt
  • 2,651
  • 1
  • 21
  • 28
27

I'm no expert but I'm not using DTO's at all. I really couldn't see a use for them. In each module I have a service, module, entity, and controller.

Preston
  • 3,260
  • 4
  • 37
  • 51
  • 26
    Why the downvotes here? This post adds some balance to the discussion by pointing out that the additional complexity might not make sense for smaller projects. – Nick Painter Aug 04 '19 at 18:37
  • 6
    I realise this is > 2 years old, but a DTO can be useful to avoid exposing fields for an entity/model that clients shouldn't receive, but are still useful/needed at the service level. I could definitely envision situations where this isn't needed though, but for writing an enterprise API, it can be a useful pattern. – mikey Jun 24 '21 at 12:51
  • @mikey My Angular client exposes almost all the fields in my server side entity.ts so I don't see how a DTO could prevent that. For security I have to lock down the server really tight anyway. Maybe with other front ends a DTO would be useful but I can't think of which ones. Even templates with say .Net or Python show the fields to those willing to hack the code. At least that is my understanding. Everything a browser reads can be read by simple hacks. Do you know exceptions? – Preston Jun 25 '21 at 20:42
  • 3
    Apology if I've misunderstood. But consider an enterprise API that serves up raw data and doesn't need its own front end. We don't want to expose certain values to consumers, and the only visibility the client would have is the API contract of inputs and outputs. That is the use case I'm talking about. In that instance the entity can contain our private fields, and the dto omits them, so clients will never see these fields. – mikey Jun 25 '21 at 23:32
  • 2
    Thanks. I haven't thought of that and it probably will never be relevant to my work. Good to make that point in this post! – Preston Jun 30 '21 at 19:37
  • 2
    Use DTO's so that it is very easy to change method parameters later on. – Naveen Oct 19 '21 at 18:38
  • DTOs are very important in javascript rather than typescript, at least in typescript maybe you would have interfaces describes your received data in different layers, but in js you don't have something like this, you don't know what data expected to be received or data should be exposed, in mid complex application 100% you need DTOs to describe your layer integration and its useful if you want to include some data or exclude them – tarek salem May 09 '22 at 19:48
  • 1
    Suppose you are working with firebase as a db layer or event a document database without having a strict schema, and have endpoint accept some data to be saved in db in this case the consumer of this API will have the free to send any data he wants even if not defined in your schema which will makes your db sucks, you need some way to say I am accepting these specific fields in these specific operations with these criterias – tarek salem May 09 '22 at 19:50
  • 1
    Interested! I always use DTOs to accept the request and make the response. Because we can add more features such as class validator or custom methods you need something. – Liki Crus Jul 19 '22 at 20:47
11

Reason for using of DTO and Interface in NestJS

Basically in rest API we have to types of operation, One is Input and Another is Output. which is Request and Response

During response we doesn't need to validate return value. We just need to pass data based on interface

But in request we need to validate body

for example you want to create a user. Then the request body might be something like this

const body = {
 name: "Test Name",
 email: "test@gmail.com",
 phone: "0393939",
 age: 25
}

so during request we need to validate email, phone number or password is matched regex etc.

so in DTO we can do all validation

Here is one of my DTO example

import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator';

export class RegisterUserRequest {
  @IsString()
  @IsNotEmpty()
  name: string;

  @IsEmail()
  @IsNotEmpty()
  email: string;

  @IsNotEmpty()
  @MinLength(6)
  password: string;
}

export class LoginUserRequest {
  @IsEmail()
  @IsNotEmpty()
  email: string;

  @IsNotEmpty()
  @MinLength(6)
  password: string;
}

And here is the interface example

import { UserRole } from './user.schema';

export interface UserType {
  _id?: string;
  email?: string;
  name?: string;
  role: UserRole;
  createdAt?: Date;
  updatedAt?: Date;
}

Hope you understand.

Thanks

10

I would like to explain the concept of DTO with the simplest example possible for your better understanding. DTO stands for Data Transfer Object. Now DTO's are used to reduce code duplication. It simply defines a schema which are passed in the parameters of functions to make it easy to get the required data from them. Here is an example of a DTO

export class AuthCredentialsDto {
    email: string;
    password: string;
}

Now if we make a method to check whether the password is correct or not

async password_check(usercredentials: AuthCredentialsDTO)
{
    //Destructuring in JAVASCRIPT
    const {email} = usercredentials;
    //Database Logic to find the email
    return user;
}

Now if we didn't make use of the DTO, then the code would have looked like

async password_check(email: string, password: string)
    {
        //Database Logic to find the email
        return user;
    }

also the point is that this is just one function, in a framework, Multiple function call multiple other functions which requires passing the parameters again and again. Just consider that a function requires 10 parameters. you would have to pass them multiple times. although it is possible to work without a DTO but it is not a development friendly practice. Once you get used to DTO you would love to use them as they save a lot of extra code and effort. Regards

Afaq Shah
  • 307
  • 2
  • 6
6

TLDR

The answer to your question is yes, you could use them for shape if you wanted to, but it might be unnecessary in some situations.


DTOs are a great solution for when you need to enforce some shape on your data(specially on the nestjs ecosystem where it interacts a lot with class-validator) or transform it somehow. Examples of that would be when you're recieving data from your client or from another service. In this case the DTOs are the way to go for setting contracts.

However when you're sending data for example, between two layers of the same application -- for instance between your controller and your usecase or between your usecase and your repository -- you might want to use an interface there since you know your data is comming in the correct format in this scenarios.

One key difference to understand is that the interface serves as a development tool, it keeps you for making mistakes like passing an object lacking a certain required property between two classes, while the DTO affects the application itself, it's an object that's going to be instantiated at runtime and might be used for validation and data transformation purposes, that`s the idea, but of course it also has the capacities of an interface.

There might be exceptions to this rule of thumb depending on the architecture you're going for. For example, on a project I'm working on it's very common to have the contract between domain and presentation layers equal to the contract between frontend and the API. On this project, to avoid having to create two similar contracts, I`ve chosen to use a DTO to set the contract between the presentation and domain layers. Now in most cases I just extend the DTO when setting the contracts between API and clients.

Luan Henning
  • 77
  • 1
  • 3
5

To extend @Victor's answer regarding the DTO concept and its role, I'd like to point that interfaces allow us to set a contract which represents something meaningful in our app. We can then implement and/or extend this contract in other places where needed as well e.g. entity definition for database objects - DAOs, data transfer objects - DTOs, and business models definitions notably.

Also interfaces for DTOs can be shared across a backend and a front-end so that both projects can avoid code duplicate and differences between objects exchanged for ease of development and maintainability.

A. Maitre
  • 2,985
  • 21
  • 25
5

one thing that dto provides more than interface is. with dto and class validator you can make the validations quickly at request level. But when it comes to interface you cannot add class validator to it. dtos are class in general. that means you have more to do with that than a interface.

Saniul Ahsan
  • 51
  • 1
  • 2
5

DTO has a little bit different mission. This is an additional abstraction for data transfer connection by the network between FE and BE layers of your application and at the same time, DTO gives a description of your data like it doing Interface. But the main mission is a distinct place for data connection and due to this you could implement many helpful things in your DTO layer it could be some transformation for DTO fields values, validation for them, checking with regular expression, etc. So you have a convenient place for attendant changes for data just on early receiving or sending to FE side

OleksiiGa
  • 51
  • 1
  • 7
3

In the nestjs-query packages there are two types of DTOs referenced. Read DTO - The DTO returned from queries and certain mutations, the read DTO does not typically define validation and is used as the basis for querying and filtering. Input DTOs - The DTO used when creating or updating records.

3

I read through all of the answers, and none really seem to answer your question. I believe that yes, although DTOs and interfaces serve different purposes, I don't see a reason why you need to create an additional interface for typing purposes.

Happy to be proven wrong here, but none of the answers really address the OP's point, which is that the DTO serves a validation purpose, but you also get the typing for free.

  • OP also said "codegen can generate a set of typescript interfaces to be consumed by NestJS itself and which can be shared with another set of consumer applications like Angular too." so not just for type safety, OP intends to share the shape of the data with consumer applications too, so i would think having both DTO and interfaces would be necessary, i wouldn't mess with openApi code generation – some_groceries Jun 01 '23 at 12:29
1

Basically you can validate request input without Dto. But imagination, you have to work with body payload, route params, query params or even header values. Without Dto you have to put your validation code inside each controller's methods to handle the request. With Class Validation and Class Transformer, you can use decorator to do that. Your mission is defining your Dto Classes and add the validation annotations for each property. You can find out the details here How to validate request input in nestjs and How to use pipe in nestjs

gmmiso88
  • 17
  • 1
1

For example, if the Dto you created for the incoming request needs to be checked for the incoming data, the best way to do this is to create the Dto as a class. Because after typescript is compiled, they continue to exist in your javascript code. In this way, you can add various validations. For example "IsNotEmpy", "IsString" etc. If the data doesn't need to be validated, you can create Dto using interface. So here, rather than a single correct or correct method, it's about what you need.

Alesko
  • 39
  • 2
  • 5
1

I think the NestJs documentation answered this precisely:

A DTO is an object that defines how the data will be sent over the network. We could determine the DTO schema by using TypeScript interfaces, or by simple classes. Interestingly, we recommend using classes here. Why? Classes are part of the JavaScript ES6 standard, and therefore they are preserved as real entities in the compiled JavaScript. On the other hand, since TypeScript interfaces are removed during the transpilation, Nest can't refer to them at runtime. This is important because features such as Pipes enable additional possibilities when they have access to the metatype of the variable at runtime.

Link to this pragraph: https://docs.nestjs.com/controllers#request-payloads

  • So what stops use using the DTO instead of creating a new Interface? I can imagine that perhaps its for the sake of flexibility. – goldfishalpha May 04 '23 at 16:06
  • because you will use DTO in validation, which will be in runtime, and in runtime the interfaces don't exist, so you can't validate the user inputs. – Abdelraouf dz Jul 15 '23 at 09:43
1

IMO, this has nothing to do with NestJS or any other framework. This is clean architecture proposed and nicely described in his books by Robert Martin.

Clean Architecture

DTOs sits in WEB/UI (outer most) layer, while entities (or like you are referring to them as Interfaces) are in the core layer of the clean architecture.

It is a coincidence that you have them in pretty similar shape, most likely you have a small application and at this stage you will hardly see the value from this separation. But the more the app grows the more difference you will see and more important this separation will become.

Imagine you have a form on the UI that creates something, let's say blog post. In this form you will have Author, Tags, Header image, and of course blog post content. All this can be in a single DTO object, you can validate this object as a whole (all required and valid things provided). But at the controllers level, you will construct multiple Tag entities, Author Entity, ImageHeaderFile entity, BlogPost entity and work with them. You don't want you lower level components to depend on higher level components.

Here are a couple links to start with:

Vladimir Prudnikov
  • 6,974
  • 4
  • 48
  • 57
0

BTW, even despite on DTO is a Java convention it can't solve the problem of Generic fields, e.g.:

@Get(url/${variable})
@Reponse({
   [$variable: string]: $value
})

TS Interfaces can solve this issue only, but you cant describe it in DTO And to show it you will pass some hardcoded example

class ResponseDto {
  @ApiProperty({
    ...
    example: [...]
  })
  [$variable]: SomeTSInterface[]
}

@Reponse({ status: 200, type: ResponseDto })
kosiakMD
  • 923
  • 7
  • 22
0

DTOs represent the structure of data transferred over the network it is meant to be for a specific use case whereas interfaces are more generalized specificity helps with better readability and optimizations. Interfaces don't exist after transpiling but nest accommodates dtos to be useful even after the transpilation face.

gridlock
  • 1
  • 1
0

In my opinion, DTO = Data Transfer Object. Dtos are like interfaces but their whole goal is to transfer data and validate it. They are mostly used in routers/controllers.

You can simplify your API body and query validation logic by using them. For instance, you have a AuthDto which automatically maps the user email and password to an object dto to enforce validations.

Where as the interface is just to declare how your response or a particular data model will be.

  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 28 '23 at 09:24
-3

I'm not an expert but I do not understand why we use Dto When we can use the schema model - what is the need for Dto and additional objects

Reza Salari
  • 101
  • 2
  • 8
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/29797454) – Stefano Sansone Sep 11 '21 at 11:15