Playing Next Lesson In
seconds

Let's Learn AdonisJS 6 #10.3

Uploading and Displaying User Avatars

In This Lesson

We'll learn how to validate and upload avatar images into our project's storage. We'll then learn how we can easily display images we have contained within our app's storage

Created by
@tomgobich
Published

🕰️ Chapters
00:00 - Adding Avatar File Input Field
01:06 - Adding AvatarUrl Hidden Field
01:40 - Validating Avatar and AvatarUrl Fields
03:05 - MultipartFile Options
03:52 - Moving the Uploaded Image to Storage
05:25 - Storing the Image's Storage Path
06:03 - Removing A User's Avatar
06:20 - Enctype Multipart Form Data
07:30 - Downloading Stored Avatar Images
10:10 - Displaying Stored Avatar Images
10:53 - Allowing Users to Remove Their Avatar
12:36 - Deleting the User's Avatar Image on Remove
13:40 - Testing Our Upload & Remove Flow
14:10 - A Few Things To Notes

Join the Discussion 9 comments

Create a free account to join in on the discussion
  1. @sp-dev

    Can you please tell me how to upload image in s3 in adonis js 6 please i am unable to do this because v5 methods are not working Thanks

    2
    1. Responding to sp-dev
      @tomgobich

      Answered on YouTube, but will answer here as well:

      Hey there! The updated version of Drive is coming soon to AdonisJS 6, and it's called FlyDrive! As of now, the package is ready, they're just working to add the tight integration directly into AdonisJS 6. So, you can configure it and use it in AdonisJS 6 now, then swap to the direct implementation once it's released if you wish. Alternatively, you can always directly use the S3 SDK provided directly from Amazon. You can find the documentation for it here: https://flydrive.dev/docs/introduction

      We do plan on doing lessons on FlyDrive in the future!

      0
  2. @guy-aloni

    Thanks Tom for the tutorial!
    I am trying to implement upload action that will work in localhost with file-system, and in production with s3. Can you please tell how to do it with minimum code? I guess that using DRIVE_DISK in .env file should be enough, but I do not manage to have the same code working for both s3 and local file system.

    1
    1. Responding to guy-aloni
      @tomgobich

      Thanks for watching, Guy!

      For that, I'd recommend using Drive (FlyDrive). Its goal is to provide a unified API regardless if you're working with the local disk, S3, GCS, etc. We, unfortunately, don't have any lessons on FlyDrive of yet but the documentation is pretty great! With it, the only thing you should need to change between local and production is that DRIVE_DISK environment variable and the APIs between local and S3 will be the same (bar any advanced situations).

      Drive is a wrapper around FlyDrive specifically integrating FlyDrive with AdonisJS. Both, written and maintained by the AdonisJS core team.

      0
      1. Responding to tomgobich
        @guy-aloni

        Thanks. I tried it but got problems with relative and absolute path, and could not manage to have the same code working with both fs and s3 depending on the environment argument DRIVE_DISK. I understand that basically the same code should work for both, I will give it another try :-)

        0
  3. @memsbdm

    Hi Tom,

    Quick question concerning storage on a cloud provider. In an inertia action/dto architecture, where is the best place to get the complete url from drive ?

    I can get it in DTO constructor but it would transform it to an async controller and it is annoying to have multiple async ones… I thought about maybe a Lucid model hook but I'm not sure it is a good place or not!

    1
    1. Responding to memsbdm
      @tomgobich

      Hi @memsbdm! Good question, and you have a few options! You could use a model hook for this. If you're early on in your project architecture and you'd like to take this approach, I'd recommend maybe binding all your images via an Asset or Image model. That way, you can add the hook and the hook will only run when the user.asset is lazy/eager loaded.

      For example

      // the hook would run anytime the user is queried, which...
      // think of all the POST, PUT, DELETE requests that use your user
      const directUrl = user.avatarUrl
      
      // here the hook only runs when the asset is lazy/eager loaded
      // onto the user model, limiting the number of times the hook runs
      await user.load('asset')
      const relatedUrl = user.asset.url
      Copied!

      Alternatively, you could add a method a method to your DTO to populate the URL, something like:

      export default UserDto extends BaseModel {
        declare id: number
        declare username: string
        declare avatarUrl: string | null
      
        constructor(user: User) {
          this.id = user.id
          this.username = user.username
        }
      
        async fromModel(user: User) {
          const dto = new UserDto(user)
          
          dto.avatarUrl = await drive.use('s3').getUrl(user.avatar)
      
          return dto
        }
      }
      Copied!

      You could also switch from DTOs to a presenter style approach:

      export default class UserPresenter {
        async fromModel(user: User) {
          return {
            id: user.id,
            username: user.username,
            avatarUrl: await drive.use('s3').getUrl(user.avatar)
          }
        }
      }
      Copied!

      Lastly, if you're dealing with public images, you could set up your own endpoint to serve the image. Something like /imgs/:key and this endpoint can directly serve the image. Then, if your site is behind something like Cloudflare, you can set it up to cache /imgs/* to set it up as a sort of CDN endpoint. Your image src could then just be:

      <img src="/imgs/my-image-key.jpeg">
      Copied!

      Hope this helps!!

      1
      1. Responding to tomgobich
        @memsbdm

        Thanks for all these ideas!

        Before seeing that I added this to the DTO to avoid multiple calls when we didn't need the image :

        async preloadImage() {

        if (this.imageKey) {

        this.imageUrl = await drive.use('r2').getUrl(this.imageKey)

        }

        }


        I saw a few times the "presenter" term and didn't really look at it, now that I checked this is clearly what I need so I'll change the DTO architecture for that!

        1
        1. Responding to memsbdm
          @tomgobich

          Anytime! Awesome, looks like what you had was a great alternative. I think the presenter approach will fit your use-case well! AdonisJS 7 will be introducing HTTP Transformers as an official solution to fill this space as well, which will be nice.

          1