- 00:00:02 welcome back to this
- 00:00:03 series where we're building a restful
- 00:00:06 api with node.js
- 00:00:08 we added so many features now i want to
- 00:00:10 add another crucial one
- 00:00:12 uploading files to be precise uploading
- 00:00:15 images here
- 00:00:16 and of course i will show you the server
- 00:00:18 side of it how can we accept
- 00:00:20 and store files that is what i want to
- 00:00:22 do in this video
- 00:00:23 so let's start with it
- 00:00:27 so let's implement file uploading and
- 00:00:30 let's say we want to do this for
- 00:00:31 products so in the product routes here
- 00:00:34 for posting a new product
- 00:00:36 it might make sense to be able to accept
- 00:00:38 an image a product image which we then
- 00:00:40 store
- 00:00:40 on our server and what we then also
- 00:00:43 store an
- 00:00:43 entry in the database with the location
- 00:00:45 of that stored image
- 00:00:47 so that we can send it back with a get
- 00:00:49 request so
- 00:00:50 like when we get all um products
- 00:00:53 so that we have that data on the client
- 00:00:57 available too
- 00:00:58 so the first step is to accept images
- 00:01:02 here in this post route and now there
- 00:01:04 are two different approaches you could
- 00:01:06 take here
- 00:01:07 the first approach is that you build an
- 00:01:10 additional endpoint
- 00:01:11 where you accept binary data only so
- 00:01:14 where you don't
- 00:01:15 try to get request body because request
- 00:01:18 body as we use it here
- 00:01:20 won't be available because the binary
- 00:01:23 body will not be parsed by
- 00:01:25 our body parser package since that only
- 00:01:28 parses
- 00:01:28 url encoded or json bodies
- 00:01:31 instead we could read out the raw
- 00:01:33 request body
- 00:01:35 and you will find a link in the video
- 00:01:36 description where you can see this
- 00:01:38 approach in action and how you would
- 00:01:40 parse that incoming data
- 00:01:43 one problem we have with that approach
- 00:01:45 is that we
- 00:01:46 also somehow need to find out to which
- 00:01:49 product this file belongs and certainly
- 00:01:53 there are ways to pass that information
- 00:01:55 along with the
- 00:01:56 file itself but an easier approach is
- 00:01:59 to change the way this post route here
- 00:02:01 works right now
- 00:02:03 we're getting request body here which is
- 00:02:05 coming from our body parser plugin
- 00:02:08 which parses the incoming json body
- 00:02:11 now if we accept a different kind of
- 00:02:13 body
- 00:02:14 namely forum data to be precise
- 00:02:17 multi-part
- 00:02:18 foreign data then this changes
- 00:02:21 then we can use this forum data object
- 00:02:23 javascript offers us or
- 00:02:25 you automatically get when you submit a
- 00:02:27 forum to submit both
- 00:02:30 fields where we have data like names or
- 00:02:33 email addresses
- 00:02:35 and files where we have well the file
- 00:02:38 and this is the approach i want to use
- 00:02:40 here now to be able to
- 00:02:42 read such incoming requests which hold
- 00:02:44 form data data which is just
- 00:02:47 a different content type i need a
- 00:02:49 separate package
- 00:02:50 so i will install it with npm install
- 00:02:53 dash dash save
- 00:02:54 and the name of the package is malter
- 00:02:56 now malter
- 00:02:58 is simply a package that like body
- 00:03:00 parser
- 00:03:01 is able to parse incoming bodies but
- 00:03:04 here we're talking about form data
- 00:03:06 bodies
- 00:03:06 so it's essentially an alternative to
- 00:03:08 body parser for
- 00:03:10 well bodies that can't be handled by
- 00:03:12 body parsers which is the case for
- 00:03:14 foreign data
- 00:03:15 thereafter i'll restart my server and
- 00:03:18 now we can start
- 00:03:19 implementing malter you can find
- 00:03:21 detailed instructions about how to use
- 00:03:22 malta in the video description
- 00:03:24 it's actually not that difficult though
- 00:03:26 in
- 00:03:27 the products.js file to where i want to
- 00:03:29 get a file
- 00:03:31 i will create a new constant which i'll
- 00:03:33 name malter
- 00:03:34 where i require well malter so the
- 00:03:37 package we just installed
- 00:03:39 thereafter i'll create a new constant
- 00:03:42 upload
- 00:03:43 where i execute malter like this
- 00:03:46 this will basically initialize it now we
- 00:03:49 can pass a configuration to malta here
- 00:03:51 and i will do so i will pass a
- 00:03:53 configuration
- 00:03:55 which is destination
- 00:03:58 uploads for example now
- 00:04:01 this specifies a folder where malter
- 00:04:05 will try to store
- 00:04:06 all incoming files this folder of course
- 00:04:10 isn't publicly accessible by default so
- 00:04:13 you will probably have to turn it
- 00:04:15 to a static folder so what we can do is
- 00:04:19 we can go to our app.js where we
- 00:04:21 initialize
- 00:04:22 our whole app our express app
- 00:04:26 and set this folder up to be statically
- 00:04:28 accessible
- 00:04:29 we'll do this in a second step though
- 00:04:31 for now let's stick to implementing the
- 00:04:33 upload functionality and see if that
- 00:04:35 works as it should
- 00:04:37 so we initialized multi and we're saying
- 00:04:40 hey
- 00:04:40 store all files in this place now we
- 00:04:43 want to use it
- 00:04:44 on this post route here how do we do
- 00:04:47 that
- 00:04:49 you actually can pass an argument prior
- 00:04:51 to the function where we handle
- 00:04:53 the incoming request you can pass as
- 00:04:56 many handlers as you want here
- 00:04:58 and each handler is just a middleware
- 00:05:00 that is executed before the next one
- 00:05:02 runs
- 00:05:03 and turns out that this upload object we
- 00:05:06 created by initializing multer here
- 00:05:09 gives us a couple of middlewares we can
- 00:05:12 put in front
- 00:05:13 of our main handler whoops we are
- 00:05:17 here so here we can execute
- 00:05:20 upload and then there are a couple of
- 00:05:22 methods for us single is interesting
- 00:05:25 single means that here we will get
- 00:05:28 one file only so it will only try to
- 00:05:30 parse one file
- 00:05:32 and by just passing upload single here
- 00:05:34 in front of this handler
- 00:05:36 it will parse that file you here also
- 00:05:39 need to specify the name of the field
- 00:05:41 that will hold the file
- 00:05:42 i'll name it product image here like
- 00:05:45 this
- 00:05:46 now with that we should be able to use
- 00:05:48 that file in here and let's start simply
- 00:05:50 by simply outputting it so console.log
- 00:05:53 and now you can access request file this
- 00:05:55 is a new object that will be available
- 00:05:57 to you
- 00:05:58 due to this middleware being executed
- 00:06:00 first
- 00:06:01 now let's save this and i'm getting an
- 00:06:04 error
- 00:06:07 we get a permission problem here with
- 00:06:08 the uploads folder
- 00:06:10 the reason for this is a typo here where
- 00:06:13 i set up the destination uploads
- 00:06:15 the leading slash turns this into an
- 00:06:17 absolute path
- 00:06:18 so now it would try to create this in my
- 00:06:19 root node modules folder
- 00:06:21 which i of course don't want so let's
- 00:06:23 now restart npm start
- 00:06:25 and now it's working so turn this into a
- 00:06:28 relative path by not specifying a
- 00:06:30 leading slash here with that let's
- 00:06:33 simply try it out
- 00:06:34 let's post a new
- 00:06:38 product here let's go to body
- 00:06:41 and now this will fail i can already
- 00:06:43 prepare you for that because
- 00:06:45 we'll now also still try to parse our
- 00:06:48 json data which won't be available but
- 00:06:51 still
- 00:06:51 let's see if we at least get that image
- 00:06:54 up there so i will
- 00:06:55 switch that to form data here the body
- 00:06:58 of the post request
- 00:07:00 and now we basically ignore the body
- 00:07:02 down there the json body
- 00:07:04 we just add key value pairs here so
- 00:07:07 what we can try is that we will also
- 00:07:10 pass name and price
- 00:07:12 maybe request body still works because
- 00:07:15 malter handles
- 00:07:16 this let's see so we will have name
- 00:07:19 and we will have a price key here the
- 00:07:22 price could be 12.99
- 00:07:24 and the name could be harry potter 5
- 00:07:29 and then we have an additional key which
- 00:07:31 is of type
- 00:07:32 file here which should be named product
- 00:07:35 image because that's what we're trying
- 00:07:37 to extract here
- 00:07:39 and now i can choose a file
- 00:07:42 now i prepared a fitting file here for
- 00:07:44 christmas let's open that
- 00:07:46 and now let's try sending this and now i
- 00:07:49 get an unexpected token in json position
- 00:07:52 0.
- 00:07:53 now it makes sense because in the
- 00:07:54 headers i set the content type to
- 00:07:56 application
- 00:07:57 json so let's remove that here and let's
- 00:07:59 try sending this again
- 00:08:02 now we get message not found the reason
- 00:08:04 for this is because it should be slash
- 00:08:06 products here little mistake on my site
- 00:08:08 so let's try again
- 00:08:10 and now i created a product successfully
- 00:08:13 so no failure
- 00:08:15 and we also get an uploads folder with a
- 00:08:17 file in there
- 00:08:19 with some cryptic file name
- 00:08:22 which if you were to open it in your
- 00:08:24 explorer or finder
- 00:08:25 will actually be the image you store it
- 00:08:27 the file name and file extension is
- 00:08:29 missing but we'll take care about this
- 00:08:30 later
- 00:08:31 but the file is getting stored and
- 00:08:34 that's pretty huge
- 00:08:35 you also saw that unlike expected
- 00:08:39 it didn't crash because that's the
- 00:08:41 second thing malta does
- 00:08:43 it doesn't just give you request file it
- 00:08:45 also
- 00:08:46 gives you a request body and since we
- 00:08:49 passed name and price
- 00:08:50 with the request this works and if
- 00:08:53 you're wondering why we didn't have to
- 00:08:54 set the header
- 00:08:55 well this is set automatically here when
- 00:08:58 we choose
- 00:08:59 form data as a body then the header sent
- 00:09:02 was actually the right one
- 00:09:04 to be parsed by malter you also see
- 00:09:07 that we locked information about the
- 00:09:09 file here on the backend due to this
- 00:09:11 line here
- 00:09:12 and there you see it got the field name
- 00:09:15 it also found the file name
- 00:09:17 the encoding the mime type and where it
- 00:09:19 stored it
- 00:09:20 the file name it created here and
- 00:09:22 therefore the path where it uploaded it
- 00:09:24 here
- 00:09:25 and that of course is all pretty great
- 00:09:28 now
- 00:09:28 let's use that knowledge here that we
- 00:09:31 have all the information in the file
- 00:09:33 to change the way it is stored right now
- 00:09:36 we're storing this cryptic name here in
- 00:09:37 uploads
- 00:09:38 we can also change the configuration up
- 00:09:41 here when we initialize
- 00:09:42 multer though we can be more detailed
- 00:09:45 here
- 00:09:46 and define how we want to store that
- 00:09:48 file we can also make sure that we only
- 00:09:50 store
- 00:09:51 certain types of files so let's
- 00:09:53 implement both
- 00:09:54 let me first implement a storage
- 00:09:56 strategy for that
- 00:09:57 i'll add a new constant which i'll name
- 00:09:59 storage
- 00:10:01 and there i will use the multiple
- 00:10:02 package and they're the disk
- 00:10:04 storage function you can pass a
- 00:10:07 javascript object to it to configure it
- 00:10:09 and this basically allows you to adjust
- 00:10:11 how files gets stored
- 00:10:13 it's a therefore more detailed way of
- 00:10:15 storing files than just setting up desk
- 00:10:19 there you have to provide two properties
- 00:10:23 a destination property which is
- 00:10:26 a function that defines where the
- 00:10:29 incoming file should be stored
- 00:10:30 and you get access to the full request
- 00:10:32 in this function to the file into a
- 00:10:34 callback
- 00:10:35 so all of that is passed into the
- 00:10:36 function automatically by malter
- 00:10:39 and we also have a file name function
- 00:10:42 here
- 00:10:42 which defines how the file well should
- 00:10:45 be named you get a request the file and
- 00:10:47 a callback here too
- 00:10:49 malter will execute these functions
- 00:10:50 whenever a new file is received
- 00:10:53 in the destination you execute the
- 00:10:56 callback in the end
- 00:10:57 and to it you pass a potential
- 00:11:01 error here now because we've gotten on
- 00:11:04 and then the path where you want to
- 00:11:05 store the file
- 00:11:06 and this could be a relative path where
- 00:11:08 we just again use
- 00:11:10 uploads so the file we already specified
- 00:11:12 before
- 00:11:14 now in the file name function you define
- 00:11:16 how the file should be named
- 00:11:18 and there you could choose file dot file
- 00:11:21 name
- 00:11:22 we saw in the log that this property is
- 00:11:24 available
- 00:11:26 or you choose the original name if you
- 00:11:28 want to if you want to keep this
- 00:11:30 you could also do something like get
- 00:11:34 new date to iso string to get the
- 00:11:38 current date in the iso string form
- 00:11:40 and concatenate this uh with the
- 00:11:43 original name something like that
- 00:11:45 original
- 00:11:47 name whoops all lowercase
- 00:11:52 well let's see if that works as expected
- 00:11:55 we now have to pass this to malta here
- 00:11:58 so now in this object we pass to malta
- 00:12:00 we set storage to our storage constant
- 00:12:03 which holds our own strategy
- 00:12:05 then we save this and then let's send
- 00:12:08 the same request
- 00:12:09 again maybe with harry potter 6 so that
- 00:12:11 we can see a difference
- 00:12:13 if we do send this let's have a look at
- 00:12:16 the uploads folder
- 00:12:18 now we see we got the file named with
- 00:12:21 the date
- 00:12:22 and thereafter also with the original
- 00:12:24 file name and we can see the image here
- 00:12:26 because it has the
- 00:12:27 right extension now too let me delete
- 00:12:29 the other file therefore
- 00:12:31 so this is how we can fine-tune multi
- 00:12:33 when it comes to this
- 00:12:35 now you might also be interested in
- 00:12:36 filtering out certain files
- 00:12:38 for one you can set some limits to for
- 00:12:40 example not
- 00:12:41 accept files that are bigger than a
- 00:12:43 certain size
- 00:12:45 you do this by passing the limits
- 00:12:47 property to that configuration object
- 00:12:49 you pass to multur
- 00:12:51 and there you can set file size whoops
- 00:12:54 that
- 00:12:54 first of all takes another object and
- 00:12:56 then in that object you can set
- 00:12:58 file size to a number in bytes
- 00:13:01 so you could choose 1024x
- 00:13:06 1024 so now you would have one megabyte
- 00:13:10 here
- 00:13:10 maybe times five to only accept files up
- 00:13:13 to five megabytes
- 00:13:15 now that is one additional check you can
- 00:13:17 implement
- 00:13:18 but maybe you need a more precise filter
- 00:13:20 to accept
- 00:13:21 only certain mime types for that
- 00:13:24 you can also set up your own filter so
- 00:13:27 i'll create a new constant which i'll
- 00:13:28 name
- 00:13:28 file filter now that is a function where
- 00:13:32 you get a request
- 00:13:32 the file and a callback here i'll write
- 00:13:36 it as an arrow function you could use it
- 00:13:37 as a normal function with the function
- 00:13:39 keyword too
- 00:13:41 and in there you can either reject or
- 00:13:45 accept an incoming file
- 00:13:47 you reject a file by passing callback
- 00:13:50 null false this will simply ignore the
- 00:13:54 file will not store it
- 00:13:56 you accept it by executing callback null
- 00:14:00 true this will store the file
- 00:14:03 or you can also throw an error so if you
- 00:14:06 don't set
- 00:14:07 null it will return with an error
- 00:14:09 ignoring it or not
- 00:14:10 saving it with false does not throw an
- 00:14:12 error it just doesn't save the file
- 00:14:14 now what you can do is you can access
- 00:14:17 some file information here
- 00:14:18 to filter out certain files you don't
- 00:14:20 want
- 00:14:22 for example we can add a if check here
- 00:14:24 where we say if
- 00:14:26 file and then there is a mime type
- 00:14:29 property automatically populated by
- 00:14:31 malta you could see this in the log
- 00:14:32 earlier
- 00:14:35 here mime type so if filemind type
- 00:14:38 is image slash jpg
- 00:14:42 or if the file mime type is
- 00:14:46 image slash png
- 00:14:50 then let's say you want to store it so
- 00:14:52 then you'll take that
- 00:14:53 callback where you pass true as the
- 00:14:55 second argument this will store the file
- 00:14:58 else so for all other incoming mime
- 00:15:00 types like zip files
- 00:15:02 but also gives and so on you could
- 00:15:04 execute callback false to
- 00:15:06 not save it now with that let's see that
- 00:15:09 in action and to see it
- 00:15:11 we have to pass it to our
- 00:15:14 mulcher options so here where we have
- 00:15:16 storage and limits
- 00:15:17 i'll now add an additional property so
- 00:15:20 we have storage
- 00:15:22 we have limits here
- 00:15:27 let's now add one additional property
- 00:15:29 which is file
- 00:15:31 filter and i'll set this equal to file
- 00:15:34 filter to my constant
- 00:15:36 now if i save this and i send this
- 00:15:38 request again i get a success message
- 00:15:41 and it does store the file
- 00:15:42 because i accept jpeg files and that's
- 00:15:45 what i'm sending
- 00:15:46 if i temporarily remove that though and
- 00:15:48 save everything
- 00:15:50 now if you're safe to say send the same
- 00:15:51 request it succeeds here but the file
- 00:15:54 isn't stored
- 00:15:55 it's right that it succeeds because i
- 00:15:57 don't throw an error
- 00:15:59 i could do this here i could return a
- 00:16:01 new
- 00:16:02 error here with some message so i could
- 00:16:05 do that and not here but in the second
- 00:16:08 second callback so
- 00:16:10 down there but i actually don't want to
- 00:16:14 do that
- 00:16:14 i just want to not store the file let's
- 00:16:16 say and of course the exact behavior is
- 00:16:18 up to you
- 00:16:19 now i'll re-add image here image jpeg as
- 00:16:22 a valid
- 00:16:23 mime type and now sending this again
- 00:16:25 will also store it again
- 00:16:27 so this is now storing the files
- 00:16:30 correctly now what about getting the
- 00:16:32 files
- 00:16:33 now we store the files
- 00:16:36 on our backend here that's nice but we
- 00:16:39 also want to store an entry in the
- 00:16:42 database
- 00:16:43 so that when we get a list of all the
- 00:16:44 files we actually
- 00:16:46 can can see them there so we can access
- 00:16:49 them
- 00:16:50 for that let's work on our model
- 00:16:53 on the product model besides name and
- 00:16:56 price
- 00:16:57 we also need a product image
- 00:17:00 field or something like that the type
- 00:17:02 will just be a string because it's just
- 00:17:04 a url
- 00:17:06 and you might also set required to true
- 00:17:09 that's up to you if you want to make an
- 00:17:11 image required or not
- 00:17:13 so if i save this and i go back to
- 00:17:15 products
- 00:17:16 when we store a product in the post
- 00:17:18 route we now also need to set up this
- 00:17:21 product image property now
- 00:17:25 so this one which should hold a url to
- 00:17:27 the image
- 00:17:29 now how do we get that image url
- 00:17:32 well we can get it from malter
- 00:17:36 if we have a look at our console and we
- 00:17:38 scroll up to the point of time when we
- 00:17:40 logged everything
- 00:17:41 you see we had a path property here and
- 00:17:44 that is what we're interested in
- 00:17:46 so what i will send here or what i what
- 00:17:50 i
- 00:17:50 store here is actually
- 00:17:54 my pop so file path
- 00:17:58 and with that oh this should be request
- 00:18:01 file here
- 00:18:02 so request file will be default of so
- 00:18:04 with that we're
- 00:18:05 getting that we're storing that
- 00:18:08 information
- 00:18:09 now when we retrieve files here for
- 00:18:11 example when we get all files
- 00:18:12 we just retrieve the name the price and
- 00:18:15 the id
- 00:18:16 now we should also add product image
- 00:18:18 here to make sure we get this too
- 00:18:20 and the same for the route where we get
- 00:18:22 a single product
- 00:18:23 here we also should retrieve that from
- 00:18:25 the database
- 00:18:26 now we're not done yet though just
- 00:18:28 adding this here doesn't do the trick
- 00:18:30 here for a single product we do output
- 00:18:32 this here
- 00:18:33 so that is okay but for all the products
- 00:18:36 selecting all the fields is one step
- 00:18:39 but then here we return our own object
- 00:18:41 anyways so there we should also return a
- 00:18:43 product
- 00:18:44 image which should be dock product
- 00:18:48 image now if we save this
- 00:18:52 and we send another post request to
- 00:18:54 products with our file
- 00:18:56 the file gets stored if we now get
- 00:18:59 all products we will see that if we
- 00:19:02 scroll through the list of products here
- 00:19:05 we get some with a product image because
- 00:19:07 i tested this between cuts a little bit
- 00:19:09 now if i pick for example the last one
- 00:19:11 here and i copy that product image
- 00:19:14 and i go to the browser
- 00:19:18 and add that path after localhost 3000
- 00:19:22 i'm getting an error a not found error
- 00:19:25 so this
- 00:19:25 isn't found this route isn't found and
- 00:19:28 it makes sense because there is no such
- 00:19:30 route
- 00:19:31 in our project we got routes for posting
- 00:19:34 and getting getting our products
- 00:19:36 but we have no route to handle requests
- 00:19:39 at a slash
- 00:19:40 uploads url and by default that folder
- 00:19:43 isn't publicly accessible either
- 00:19:45 so this all doesn't work now there are
- 00:19:47 two possible approaches you can take
- 00:19:49 here
- 00:19:50 you can either implement a route for
- 00:19:52 example here in the products routing
- 00:19:54 file
- 00:19:55 where you parse all requests coming to
- 00:19:57 slash uploads
- 00:19:58 and where you then look up the image
- 00:20:01 file name and see if you got such an
- 00:20:02 image and return it manually
- 00:20:04 or you make the uploads folder publicly
- 00:20:07 available
- 00:20:07 that's what i'll do and i'll do this in
- 00:20:09 app.js for this i'll add a new
- 00:20:11 middleware here right at the start
- 00:20:14 and it's a middleware shipping with
- 00:20:15 express the static function here is the
- 00:20:17 one i'm looking for
- 00:20:18 it makes a folder statically so publicly
- 00:20:21 available
- 00:20:22 here i can pass uploads and this will
- 00:20:25 make the uploads folder
- 00:20:26 available to everyone if we now
- 00:20:29 save this and therefore the server
- 00:20:31 restarts and i refresh this page
- 00:20:34 still doesn't work the reason for this
- 00:20:37 is that if i make it available publicly
- 00:20:39 i actually have to remove the route so
- 00:20:41 the folder name and just
- 00:20:43 access the file name and then it does
- 00:20:44 work as you can see
- 00:20:46 now would be better if we also had the
- 00:20:48 folder name still
- 00:20:50 that is why i will use the different
- 00:20:51 syntax of app use
- 00:20:53 i'll pass slash uploads at the beginning
- 00:20:56 to parse only requests at targeted at
- 00:21:00 slash uploads and then apply this
- 00:21:02 middleware and this will then ignore
- 00:21:04 this part we parsed
- 00:21:06 so now with that change i can reload
- 00:21:08 this page and also see the image
- 00:21:10 and the url's dishes still is uploads
- 00:21:13 and then the file name
- 00:21:14 so this is how we can now also get our
- 00:21:17 files
- 00:21:18 a huge advancement of course because now
- 00:21:20 we can store files
- 00:21:21 and get the information where they are
- 00:21:24 stored and