Have you ever thought about how images are actually uploaded to a server, from the UI perspective? There could be many ways. You might have seen Image hosting cloud platforms like Cloudinary, Bynder, etc. Tools like these also provide edge-cached dynamic endpoints for various image qualities etc. But if you take a step back, the first thing all these tools need to do is allow you to upload the image which then gets processed it in a certain way. In this article, we will dive into one simple example of uploading images.
This example also covers the way you can drag-drop the image and preview it before uploading.
Lets dive in.
- Profile picture update.
- Image can be dragged and dropped into the placeholder and it shows a preview.
- Some custom validations to restrict users from uploading bigger size images
- Restrict users from uploading unwanted file-types.
- Showing error message and success message.
- Using Ajax to do all the heavy-lifting and avoid page reload for uploading the image.
- Have a simple back-end using express-js to simulate a real world upload scenario.
- Clear the selected file before upload, no reload needed.
jQuery (not mandatory)
Bootstrap (not mandatory)
Express.js (endpoint to save image)
Node.js (obviously for server)
Bower (frontend deps - not mandatory)
Nodemon (continous development)
You should have NodeJS and Git installed in your machine. You can manage without Git, but I highly recommend you use it.
Go to that folder where you did the
As we installed bower as an npm dependency, we can use the below function to install the bower front-end dependencies.
That is all required to get up and running.
Lets talk about the user interface.
index.html file has the normal html5 boilerplate structure. The Bootstrap 4 related files such as
bootstrap.bundle.min.js (which comes along with PopperJS included) and
jquery.min.js. These are required for our fancy UI to use the Bootstrap 4 magic. Since we are using jQuery for our Ajax functionality, it is good that we included this over here.
There is a form which has a hidden input field with
Notice the label surrounding the input:
The reason for wrapping the input inside the label and hiding it is this:
- We want to stylize the label to look like a circular section where user can drop the image.
- We also want the same circular section to be clicked upon and the native file explorer window should pop-up. The label’s for attribute takes care of this.
- We need to hide the normal file input field, because it looks ugly for our purposes and it looks different in each browser, so for the sake of consistency, we need to hide it.
for attribute points to the input which allows us to action upon the input field by clicking on the label (which is the case for all types of input fields). So, when we click on the circular section, we are actually clicking on the label and that in turn invokes the click action upon the hidden input field.
Notice that there is this attribute
accept=”image/png, image/jpeg” that makes sure that the user can only upload the images of type JPEG or PNG.
Notice, there is no attribute called
multipleso the user can only upload one image file.
There are empty paragraphs to show error and success messages. There is an Upload button and a CTA to clear the selected image (labelled
Now, since we are using Bootstrap 4 framework, there are classes we can use stylize our app. But just for the fun of learning some CSS, lets add custom styles.
Even a superhero needs a haircut.
I’m using flex for centering the form. And the image container (label) is having the bulk of the styling.
To center the form:
The circular thingy:
The error and success messages:
To show the image preview, we use an image tag inside the circular label. But we need to make sure the image centers in that circle and the overflow should be hidden (taken care in the label related style).
Notice, we are using the traditional way of centering things using left and transform translateX stuff there.
We are also giving a special class called
.dragging that gets added to the circular section when the image is being dragged onto it. Just extra border width and low opacity to the contained text.
That is it about the UI. The only place where we are using Bootstrap is when we style the button and to use a container (with a fixed width). And of course the fonts that come with Bootstrap look awesome as always.
Ooohh yes, JS…
We just start with creating variables to all the sections:
All the pointers that we need:
There is a lot going on here.
We have to make sure we:
- allow user to drag and drop into the circular section
- allow user to click on the circular section and select the image
- allow user to clear the selected image
- allow user to submit the selected image
- have to handle drag and drop events to check for file type and size
- have to change style of the circular section when the image is being dragged
This is obviously the most important and intuitive part of this app. But it is equally tricky to make sure you don’t do errors while handling the dragging scenario.
First, you have to prevent the default thing that happens when you drag a file onto a browser which is to load the file as a static file in the window.
For that you have to add a special listener that cancels out such static loading as soon as that event occurs. And this has to be listening to all the drag and drop related events.
Preventing the default action…:
Then comes the hover effect that comes when the user is dragging the file onto the circular section, we have to add the
.dragging class. We do that like this:
dragover events happen when the file is being dragged over to the circular section. The
dragend events happen when the file is being pulled away from the section. The
drop event obviously happens when the file is let go on the section.
UI changes while dragging:
The hover effect:
And then finally when the user drops the file, we need to make sure the file goes through a size-check and file-type check.
There is another listener to
drop event (remember? we can assign multiple listeners to DOM events). The
checkFileProperties function takes care of the file-size and file-type checks. If there is an issue, it will gracefully complain using the
#errorMessage paragraph and end the drop event handler. Also, we are restricting the dragged files to be only one.
Max file size is 500KB:
We are restricting the file size to be less than or equal to 500KB and of type JPEG or PNG. The file has type and size properties that give us this info.
But what if the user manually clicks on the circular section and manually selects the image instead of dragging. For that we will get a change event being triggered on the hidden input field. We need to handle that as well like this:
event.target.files property gives the list of files the user has selected.
The same functionality for both dragging and selecting manually:
Previewing the image:
FileReader reads the file and triggers a
load event when it is done reading. We are setting the image source to be the
This takes care of previewing the image.
When user clicks on the button, the uploaded image gets sent to the backed in a base64 string format.
This is important to note, because there are many ways that we can send files to the back-end but for images, I think base64 formatted string is a quick and easy way.
The Ajax call also sends the name of the file which we are getting from the
handleUploadedFile method. The base64 string is sent in the property named “theFile”.
Once the upload is successful, we show the success message.
If the selected image has to be cleared for some reason, user can click on the Clear link below the button.
The function looks like this:
We remove the manually added image so that the background text shows up again.
The history of backend can be easily marked as “Before NodeJS” and “After NodeJS”. Of course there is still a lot of competition out there from the likes of Python, Java and PHP but in its relatively short life-time, NodeJS has touched every average developer’s life in some way or the other. (Trying not to get emotional here 😜).
We use NodeJS for this example too. We use it for our packaging and server support. Packaging allows us to use many beautiful applications built on NodeJS such as bower, expressJs and the likes.
- NodeJS for sure.
- ExpressJS for routing and body-parser to parse incoming Ajax requests
- Bower for front-end dependency management
- Nodemon — to watch our files and auto-restart Node.
All the imports:
When we talk about the back-end, there is always a good chance that we are trying to build a micro-service or some sort of RESTful API that the front-end is going to use, right!
ExpressJS and its routing features allow us to build seamless API with its middleware functions. We can target various request types and RESTful paths. Also we can target route-params (though we are not covering them here).
There is only one single file that caters all of our needs here, since this is a very simple demo as far as back-end is concerned.
In that file, we
- Build our API route to handle the upload scenario.
- Bring up the server to host the front-end as well as the back-end.
- Handle the uploaded file by saving it in the “images” folder.
We import the express module and build our
app. Then we bring up the server using
The app listens on port 3000 or the PORT variable on the environment.
Then we create the required routing using the app.post method of the ExpressJS API.
A lot is going on here, lets just take a step back and analize.
- The API URL is specified as the first parameter to the
.postmethod. It is
- The second argument is the function that gets called back when this particular route is targeted through a
- When the function gets executed, the first two arguments are request object (
req) and response object (
- The request object as usual contains the body object that contains all the data that is being sent from the front-end.
- In that body object, we can find the
theFileproperty that contains our base64 encoded string of image data.
- We check for that property’s existence and get both the encoded string and the title of the image to save it inside the file that we are going to save it as.
- Then we writ the file to the “images” folder, using the NodeJS’s built-in
- Notice that in this method we are passing the encoding also in the options object. This is to ensure that the file is being written in the proper format.
- At last, we use the response object to send a simple “UPLOADED” string to the front-end to notify that the upload worked fine.
In order to serve the front-end files, we are putting them inside a folder called
public. To make the NodeJS app server serve those files as they are, we use the
express.static method like below.
We are also serving the bower_components in the
We are using
body-parser in order to do some middleware changes.
- We can call the imported
bodyParserfunction with an options object and the main option here is
limitwhich we are setting to
- Also, we are converting all incoming request data to JSON format using
- We are also using the
bodyParser.urlencodedmethod to read the encoded data that sometimes comes through.
I hope you liked this post and gained some knowledge out of it. I have created a YouTube series demonstrating this illustrated example demo.
Please subscribe to my YouTube channel and follow me here as well.
See you soon with more tutorials and tips.
Originally posted in Medium here.