Playing Next Lesson In
seconds

Transcript

  1. (upbeat music) So it's pretty common for applications to allow users to upload a photo representation of themselves

  2. into our application that we refer to as an avatar. So today we're gonna cover how we can go about adding a field for that into our user profile here. So let's go ahead and hide our browser away

  3. and jump into our profiles edit page. Now with the way that we set up our form inputs, we can specify a type and a valid input type is file.

  4. So if we wanted to add in a file input, we could do @form input, specify the type here as input, give it a label of something like avatar

  5. and a name of avatar. And now on our actual user model here, if we jump into here, scroll down a little bit, we have the storage column for this

  6. referred to as our avatar URL. Rather than taking our avatar image as a base 64 and storing it directly inside of a database, what we're gonna do is take that image, move it somewhere,

  7. and then store a reference of where actually we've moved it to inside of our database as our avatar URL here. So in terms of a default value,

  8. if a user already has an uploaded avatar, we're not gonna have an actual file to be able to append in here as the value, but we can still have that represented inside of our form as a hidden input.

  9. So we could do input type hidden name avatar URL value equals and then we could set the default value to our auth user.avatarURL.

  10. That way, if they do have an avatar URL, it will still allow us to discern whether or not they wanna keep it or remove it altogether as we send up data with this form. And my bad, I've wrote input here as the type

  11. that should be file. Okay, so if we take a look at this real quick, inside of our browser, we should now see an avatar field that when clicked allows our user to jump into their file system

  12. and select an image to upload. Cool, let's hide that back away. Let's next jump into our profile validator and we're gonna add in two properties. First, we're gonna add in our avatar.

  13. This is the representation of the actual image that we're uploading. So we'll do vine dot and there is a file type here and it accepts in a number of validation options.

  14. Some of the more prominent ones that you might want to validate against would be extension names. So these would be actual file extensions that we want to allow users to upload.

  15. So that might be something like JPEG, PNG, JPEG, and any other file types applicable that you would want to accept. Essentially, this allows us to say

  16. that we don't want users to be able to upload a PDF as their avatar image. And then we can also add in a size to limit the file size that the user can upload with. So if we wanted to limit our upload

  17. to something like five megabytes, we could do five MB there to discern that here with our avatar upload. And then of course, we also want to make this field optional altogether.

  18. We're not gonna require users to have an avatar. It's gonna be completely optional. And then we also have our avatar URL. This is what we use to mostly discern whether or not a user wants to remove their avatar.

  19. Since our actual avatar field is only ever gonna have a value if they want to upload an avatar, we can use the avatar URL to persist whether or not they want to keep

  20. a previously uploaded avatar image as their current avatar. Or if our avatar URL were to come up empty, then we would know that they are intent on removing their avatar image.

  21. Okay, so file. And this is just gonna be a string because this is a string representation of where we've stored their avatar. And it too is gonna be optional. Cool, so let's give that a save.

  22. And then we'll jump into our profiles controller. We now have from our validator an avatar and an avatar URL that we can extract out. Now, a thing to note before we get started

  23. is our avatar comes through since we have it as a file on our validator as a multi-part file. So this is gonna have additional information on it

  24. like the file name that the user's uploaded the file with. And it's gonna have options that we can take with that file, like being able to move it somewhere else. When we upload a file via a form like this,

  25. the body parser is gonna move this into a temporary folder called TMP inside of our application. So that allows us to then move that file wherever we want.

  26. That could be uploading it to a service like S3, Google Cloud Storage, Delogean Spaces, and the like, or somewhere inside of our application structure that we want to persist those images.

  27. The way we can do this is if we scroll down here, let's do this just underneath where we're querying our profile. If we have an avatar that's come up, we can await, reach for our avatar.

  28. And here's a quick autocomplete list of all of the properties and methods that the multi-part file has that we can make use of. So you can see it comes with a file size, subtype, temporary path location,

  29. whether or not it's been validated. We scroll up a little bit further. There's the file path, file name, field name, extension names, whether it came up with any errors and the like.

  30. But what we care about right now is a method called move. So if we call this, it's going to accept in as its first argument, a location that we actually want to move the avatar to.

  31. Remember by default, this is going to upload to a TMP directory in the root of our application. So let's say instead we want to move it to a folder inside of our application called storage,

  32. and then a sub folder inside of storage specifically for our avatars. We can use the app module. So if we type out app here and hit tab to auto import this from AdonisJS core services app,

  33. we can call a method called make path that allows us to make and or get a reference to a path inside of our application structure. So if we wanted to move this avatar

  34. into a folder called storage, we could do storage. And if we wanted a sub folder called avatar, we could do slash avatars. And now if storage slash avatars already exists

  35. inside of the root of our application, then this will just return back a reference to that location. Otherwise it will create a folder called storage and a sub folder called avatars if needed.

  36. Additionally, with this move method, we also have some other options. So we can rename the file and specify whether or not we want to allow it to overwrite a file already inside that location.

  37. We'll leave both of these off for now. Cool, so this will take care of storing our avatar where we want it inside of our application. Now what we need to do is store that location

  38. as our avatar URL path on our user. So we can do auth user avatar URL equals. And now this app dot make path is going to be an absolute

  39. path in our file system to this location. And that's not what we want to store as our avatar URL, but rather we just care about the location where we're gonna be able to access this going forward.

  40. So we might save this as something like avatars slash, and then the avatar file name. And the file name is also going to include the file extension, so we can leave that off.

  41. And then that will get persisted with our usual merge, save call right down here. So we don't need to call save here too. Lastly, if a user is not uploading an avatar,

  42. then they may be trying to remove their avatar. So we can do else if not avatar URL, then we just want to clear that on our user.

  43. So auth user dot avatar URL equals, and we can just null that out. Cool, so we should have most everything set up. There is an additional thing that we want to do,

  44. but I want to show how things behave before we make that change. So let's jump back into our browser and let's attempt to upload an avatar here. So let's select an image. I have an image right here. We'll just go ahead and open it up.

  45. Okay, you can see it's selected right here as the file name. If we go and just hit update avatar, you'll see that we're gonna get back an invalid validation

  46. for our file type, the avatar must be a file. Now, the reason that DonetsJS isn't able to recognize our avatar as an actual file is because we haven't specified on our form

  47. that we want to allow this to happen via an anti-multi-part form data. So if we had our browser back away, go back into our edit page, scroll up to where we have our form.

  48. What we want to do is add an attribute called ENC type and set this to the multi-part form data value. This is gonna allow our form to upload in multiple parts

  49. so that our server can actually digest all the bits of the image and actually recognize it as an image. So with that applied, if we give this a save and jump back into our browser and give this a try once more.

  50. So let's go ahead and select our avatar image here, hit open and update our profile. You'll see that we get redirected right back here with no errors applied on our avatar field anymore.

  51. Furthermore, if we hide this away real quick and we add a quick inspect of our auth user avatar URL, jump back into our browser,

  52. you can see that we have now avatars/av.jpg, exactly as we are. So next, what we want to do is display that avatar above our avatar field and allow the user to hit X on it

  53. to clear it out, which would then clear out our avatar URL field. Before we do that though, we need to give ourselves a way to access our storage path. If we had stored the image inside of our public directory,

  54. it would have been immediately accessible just via slash whatever the file path is. But we've stored this inside of storage because public is prone to getting cleared out

  55. and updated every time that we deploy out our application inside of a production-based environment. But since storage is not publicly accessible, like our public directory is,

  56. we need to jump into our routes file. And I'm gonna put this up at the top of all of our routes. So scroll up to where we have our home route, we'll put it just underneath there. So we'll do router.get.

  57. And since we uploaded our image itself inside of storage/avatars, we'll assume the storage part and just specify that we want to reach for avatar images

  58. by doing /avatars here, and then we can do /filename. So that anytime that we attempt to reference just the storage path that we've saved our avatar URL as, it'll come up exactly like this.

  59. We'll have avatars/filename. Actually, let's also go ahead and prefix the uploaded image. So let's jump back into our profiles controller here and prefix the avatars URL here with a slash two.

  60. Okay, and we also forgot to take a look at what actually uploaded. So if we scroll down here a little bit, we're gonna see a storage directory now inside of our application. If we click on this, there's our avatars

  61. and there's our av.jpg right there. You also see this temp directory. This is where this image was temporarily stored as we were uploading it with our multi-part form data.

  62. Whenever we called avatar.move, that's where it moved it from that temp directory into our storage avatars folder. Now we'll need to retest our upload here.

  63. So I'm gonna go ahead and right click on the image, delete it out and move to trash. This will just allow us to persist that extra slash on there by resubmitting our form. Okay, let's jump back into our routes now

  64. and let's make a new controller for our avatars. So hit command + shift + P, make controller, and we'll call this avatar. Hit enter there.

  65. It's not a resource, so we'll hit no there. And there we go. And we'll go ahead and add a show method onto there. We'll circle back to here momentarily. Let's go ahead and finish defining our route.

  66. Okay, so we have our avatars controller and we have our show method inside of there is avatars show. Now let's jump back into our avatars controller

  67. and we'll want the response as well as our route parameters. For this, all that we need to do is download the image and return it back as the response.

  68. So we can return response, download, and then we can use our app module again. Hit tab.import that, make path, reach for storage/avatars.

  69. And we can specify a comma here and add in our params.filename to add that into our make path. Let's give that a save. Jump back into our edit page.

  70. And now we should be able to replace this inspect here with an actual image. So let's do div class relative and then plot the image inside of there

  71. with a source, auth, user, avatar URL. We'll do class width full since we have this form section here pretty small, and that should suffice.

  72. We'll also want to wrap this in an if. So if auth user does have an avatar URL, just like so, and our if, and let's go ahead and see what we get. So let's jump back to our browser.

  73. And all right, we deleted it. So let's go back into browse, hit our image, open, update, and there we go. So now we see our image because we've re-uploaded it and fixed the path.

  74. I don't quite like seeing myself that big, so I'm gonna go ahead and do width one third and mix auto on that relative div there. And now what we want to do is add in a button.

  75. So we'll do button type, button class, and then we'll absolutely position this to the top zero, right zero,

  76. add a padding of maybe two or three, rounded full, and we'll just do and times there to add a little X. So let's start by breaking our button down here

  77. and let's add an on click attribute to it. Whenever we click, what we want to do is both clear out the value of our avatar URL

  78. as well as remove the parent element to this button, which would be the div containing our image preview. The easiest way to get a direct reference to our avatar URL field is through the form.

  79. So what I'm gonna do is scroll on up to our form, add an ID, we'll just call this something like edit form, and then we can scroll back down to our button. And this will allow us to do document.forms,

  80. reach for our edit form, which allows us to reach directly for our avatar URL field and its value so that we can set it back to an empty string. Let's go ahead and wrap that in parentheses.

  81. Then once we've cleared out our avatar value, what we want to do is an or, since this won't necessarily come back truthy, and reach for this, which is our actual button element, .parent element,

  82. which is now our div surrounding our button, .remove to actually remove the div and all of its subcontents from the document. Just so that we can see this all evidently happen,

  83. let's go and switch our avatar URL from type hidden to type text, just so that we can see it directly inside of our document. Jump back into our browser. There we go. I'm gonna give this a hard refresh to make sure nothing's going on there.

  84. Here's our little X that will allow us to clear everything out. If we go ahead and give that a click, there goes our avatar URL value, as well as our avatar preview. And now if we go ahead and update our profile,

  85. our avatar is gone, and we should also probably remove the file from our file system and set this with a default value of an empty string. So first let's set this to a default value of an empty string

  86. if we don't have an avatar URL, just like so. And go ahead and switch this back to a type of hidden since we've seen it in action. And let's jump back into our profiles controller where we have our else if,

  87. and we're clearing out our avatar URL. If you take a look at our storage avatars, we still have our image in here, despite it no longer being bound to our auth user. Well, what I wanna do is bring the FS module

  88. back into our application, so we can import a method called unlink from FS and FS promises. Give that a save real quick. I wanted a node prefix, there we go.

  89. Okay, if we scroll back down now, we can do a wait, call unlink, which accepts in a path, which allows us to delete out our file as noted right here.

  90. So what we wanna do is provide a path to that file, and we'll wanna do this before we remove our avatar URL value. So we'll do app make path to get an absolute path

  91. to where we're storing our avatars, prefix this with storage, and then provide in our auth user avatar URL as the second portion to that. Now it's not quite happy

  92. because avatar URL here could be null. So to our else if, we'll also want to check and end auth user avatar URL

  93. to verify that the user no longer wants an avatar URL, but did have an avatar URL to start with, fixing the nullable reference here on our unlink call. Let's give that a save.

  94. I'm gonna go ahead and clear out our AV just so that we can walk through the flow one more time. Delete that out, move it to the trash, there we go. Since we've already destroyed the reference to our avatar URL on our user,

  95. let's go ahead and jump back into our browser, browse for our file, let's give it a select, open it up, update, there we go. If we jump back into our text editor, there it is once more. If we go ahead and hit on our X,

  96. update our profile once more. Now if we jump back into our text editor, there we go, our storage avatars no longer contains our image because it was removed.

  97. Awesome, so there are a couple of things that you're gonna wanna keep in mind here before we round things out. First and foremost, we do have our storage directly inside of our application. So depending on how your deployment

  98. for your application works, you may need to literally move that storage from the old location to the new location every time that you deploy out, which could be time consuming and resource intensive.

  99. Instead, what you may wanna do is dot dot slash on your storage, which would give you a fixed location outside of your application route that you could store your files. Another thing that you're gonna wanna keep in mind as you're working with files

  100. and especially your directory system is ensuring that you're not giving users access to be able to search through your file system. So here we have a route specifically to /avatars

  101. with a file name route parameter that specifically reaches into our storage avatars location. This could potentially open you up to the possibility of somebody reaching inside of your application

  102. going to something like say localhost3333/avatars to reach a valid avatar URL slash. A user could attempt to do dot dot slash to go back a directory,

  103. which would then be our storage dot dot slash again to go into the root of our application and then reach for something that we have inside of there, say something like readme.d.

  104. With how we've written things in AdonisJS itself, we're protected against this, but it is something to keep in mind. So like if we were to hit enter here, those dot dot slashes and everything before it

  105. are gonna get stripped out and we're not gonna be able to find a route for this, but just keep that possibility in mind, be sure to check against it as well.

Uploading and Displaying User Avatars

@tomgobich
Published by
@tomgobich
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

🕰️ 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 5 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