Ready to get started?

Join Adocasts Plus for $8/mo, or sign into an existing Adocasts Plus account, to get access to all of our lessons.

robot mascot smiling

Typing Lucid Models in Inertia with DTOs

In This Lesson

We'll learn how we can specify types for our Lucid Models easily using DTOs we'll generate directly from our models.

Created by
@tomgobich
Published

Chapters

00:00 - Why We'll Use DTOs
02:15 - Generating our DTOs from our Models
05:10 - Adding Our Organizations to the DTOs
09:10 - Using Our DTOs

Join the Discussion 24 comments

Create a free account to join in on the discussion
  1. @gribbl

    I don't know why, if it's just me or Windows, but I'm getting an error when installing the package. For those encountering the same issue, you need to add quotes.

    node ace add '@adocasts.com/dto'
    Copied!

    This whole DTO thing seems tedious, especially if we had to write everything by hand. Thankfully, your package exists. But is there a better way to handle this in the future, or is it a technical constraint that forces us to do like this? By the way, I think it must be the same issue with an API, right?

    3
    1. Responding to gribbl
      @tomgobich

      Are you using NPM directly within Powershell? Powershell reserved the @ character for their splatting feature. Which means you'll either need to escape that character or wrap it in strings, as you found.

      We're using DTOs because getting an accurate serialized type for Lucid models isn't currently possible. If you use the model as a type directly in Inertia, the type will include things you don't have access to in your frontend, like .merge({}), .save(), .related('organizations'), etc.

      If you use just the model's attributes, that won't include relationships, extras, and also won't take into account serialization alterations on the model like serializeAs.

      So, currently, it is due to technical constraints in Lucid and would require some hefty refactors to remedy. Yeah, if you were trying to do end-to-end types with an API you'd run into the same constraint.

      2
      1. Responding to tomgobich
        @gribbl

        Thank you for taking the time to explain this to me, it's much clearer now. 👌

        1
        1. Responding to gribbl
          @tomgobich

          Awesome, I'm glad to hear that, gribbl!! Anytime!!

          0
  2. @tibormarias

    until this part i was amazed how flawlessly AdonisJS works with InertiaJS, but this DTO part for me makes it a little bit non-DRY, I understand the generator package that we installed but still.

    1
    1. Responding to tibormarias
      @tomgobich

      I agree, it does make things less DRY, but to my knowledge, in other Inertia-supported environments, you would need to define the type specifically for the frontend as well. At least with AdonisJS, we can convert it to the DTO on the server side to ensure what we're passing matches expectations on the frontend.

      It would be awesome if we could directly access a serialized type of the model, but that would take quite the doing!

      1
      1. Responding to tomgobich
        @tibormarias

        By the way thank you for these videos you have great teaching skills. Is it against TS best practices if I create a types/models.d.ts file inside inertia folder and declare my models as global interfaces/types? This way I don't have to import them inside my vue files, just use them.

        1
        1. Responding to tibormarias
          @tomgobich

          That means a lot, thank you, tibormarias! Also, thank you for watching & being an Adocasts Plus member, it's greatly appreciated! Yeah, if you'd rather not use DTOs, I think that's a perfectly viable alternative approach.

          0
  3. @emilien

    Hi Tom, tried using your package to generate dtos, but it gives weird names to my dtos files, it take my whoe path, for example : c_users_emilien_documents_dev_sojatask_app_models_board.ts
    Am I doing something wrong ?

    1
    1. Responding to emilien
      @tomgobich

      Hi emilien! Terribly sorry about that! I had missed normalizing the slashes where the model's file name is determined which resulted in the whole file path being used on Windows.

      Should be all fixed up in v0.0.7

      1
      1. Responding to tomgobich
        @emilien

        No problem, don't apologize. Updated your package and everything is working fine now, thanks a lot !

        1
        1. Responding to emilien
          @tomgobich

          Awesome! Glad to hear everything is working for ya now! 😊

          1
          1. Responding to tomgobich
            @emilien

            Sorry to bother you again. Some of my model's columns are not "generated" in the dto, do you have any idea why ? Must be pretty difficult without context, do you want me to open an issue on github ?

            1
            1. Responding to emilien
              @tomgobich

              Yeah, if you could create an issue that'd be great, thank you! Currently, this package reads the models as plaintext, line-by-line. So, there's room for error based on formatting. I'm hoping to refactor to instead use ts-morph which should fix those oddities up.

              1
              1. Responding to tomgobich
                @emilien

                Issue created. First time I write an issue, let me know if something is missing.

                1
                1. Responding to emilien
                  @tomgobich

                  Looks good, thank you emilien! I'll try and take a look into it today after work.

                  1
  4. @aaron-ford

    The import for the UserDto works fine in my inertia.js file, but it can't be found in the AppLayout.vue file. Did I miss something? I tried going back over it and I think I followed all the steps.

    1
    1. Responding to aaron-ford
      @tomgobich

      Hi Aaron! Maybe try giving VSCode a reload by hitting cmd + shift + p then select for "Developer: Reload Window"

      There's a bug in VSCode that creeps up every once in a while that causes Vue files to lose intellisense & auto imports. Even You (Vue's creator) posted about it a few months ago; you might be getting caught in that bug here. Reloading usually resolves it when I run into it.

      0
  5. @cgregoire

    Could you suggest a good way to manage hidden fields like user password in dtos ? I tried to add properties like « WithPassword » in my dto user class, but i m not sur about it

    1
    1. Responding to cgregoire
      @tomgobich

      Hi cgregoire! Not sure I fully follow what you're after, but I wouldn't advise including the password in any DTO being sent to the client. I'm working to update this lesson now to include a note about that, apologies I missed that in the DTO that was generated.

      If you're looking for a way to signal whether the user has a password, for example maybe you also have social authentication where the user may not have a password, then a flag in the DTO would be a sufficient solution! You could name that property however you'd like, but I would go with something like isPasswordSet or hasPassword.

      export default class UserDto extends BaseDto {
        // ...
      
        isPasswordSet: boolean
      
        constructor(user: User) {
          // ...
      
          this.isPasswordSet = !!user.password
        }
      }
      Copied!

      Hope this helps! If I missed the mark on what you were asking here, please do correct me!

      0
      1. Responding to tomgobich
        @cgregoire

        Thanks for the reply, sorry I wasn't clear enought
        I can explain a bit here about what I am looking for :

        No mater if the password is set or not, I wanted to hide it, because in most of case I fetch users and some of their informations. I followed the steps about the serialization / DTOs but there is another issue :
        In my project I tried to find a way to display only specific fields according to the case, I mean, sometimes a rest endpoint provides too much informations in the payload than needed,

        The dtos seems to be be the go to for, but, it multiply the "variant" of a DTO for example : UserPublic, UserPrivate, UserWithPassword, etc (that s just example but not exactly my solution for now), there is another issue when I have to use fromArray, or fromPaginator, I couldn't use my "dto variant" with this methods

        Another way I am trying now is to use Graphql to limit the fields returned by the api, with a hybrid setup = Rest API + graphql server side using lucid to request database.

        I hope it will be clearer, thanks a lot for the knowledge you share, it helps a lot !

        0
        1. Responding to cgregoire
          @cgregoire

          What I wanted to explain :

          1
          1. Responding to cgregoire
            @tomgobich

            Ah - I see what you mean now, thank you! Unfortunately, being DRY isn't the best when it comes to DTOs and it's best to just be simple and create a separate DTO per property list you need. For example, you would have a separate DTO listing all their needed properties for:

            • UserPublicDto

            • UserPrivateDto

            • UserWithoutPasswordDto

            Now, typing here will get better in AdonisJS 7 as it'll introduce HTTP Transformers. What you have in your screenshot can be used now as well, though! You should be able to define a type using the object the method returns, for example something like:

            export type UserBorrowerPresenter = 
              ReturnType<typeof UserPresenter.borrower>
            
            export type UserBorrowerProfilePresenter = 
              ReturnType<typeof UserPresenter.borrowerProfile>
            
            export default UserPresenter {
              static borrower(user: User) {
                return {
                  id: user.id,
                  firstname: user.firstname,
                  lastname: user.lastname,
                  email: user.email,
                  phone: user.phone
                }
              }
            
              static borrowerProfile(user: User) {
                return {
                  id: user.id,
                  firstname: user.firstname,
                  lastname: user.lastname,
                  email: user.email,
                  phone: user.phone,
                  address: user.address
                }
              }
            }
            Copied!

            I would still recommend explicitly defining the properties for each, though, for clarity sake! It'll help down the road, you'll be able to just glance at it to see exactly what it sends rather than having to piece it together from multiple different methods.

            Then, you can use the methods to serialize and the types to type on the client side.

            // serializing
            const borrower = UserPresenter.borrower(user)
            
            // type
            defineProps<{
              borrower: UserBorrowerPresenter
            }>()
            Copied!

            If I were recreating this series today, this is the approach I'd have taken over DTOs. DTOs are the go-to in C# which I also use heavily and that originally swayed my decision making. Either work just fine though.

            Hope this helps!!

            1
            1. Responding to tomgobich
              @cgregoire

              Yes that's it ! I was close to this approach, but I can't use the variant with the fromArray / fromPaginator, I had types issues too

              for example :

              this.borrowers = UserDto.fromArray(organization.borrowers) => this will generate borrowers with all fields including password etc…

              An other question : Could we make the class UserPresenter extends UserDto ? Does it makes sens ? It could keep all variations in the presenter with the access to all user properties without creating many methods in the dto ?

              I m not familiar with POO so, I try to understand why every methods are static too

              Your answer was helpfull !

              0