Zac Fukuda
019

Drop & Upload File Tutorial - Step 2: Ajax with Express.js

This is a second step of the three tutorials on building a Drop & Upload application with React. The each step covers:

  • Step 1: Drag and Drop with FileReader in native Javascript
    Before we dive into building a React application, first let us understand how the basic Drag & Drop API works.
  • Step 2: Save files on the server with Ajax & Express.js
    Using Express, we make the app able to save the dropped images on the server. We will send the data of images to the server through XML HttpRequest.
  • Step 3: React-ify with Babel & webpack
    To make it more adaptable to the modern web development, we will turn the app from Step 2 into a React app.

In the previous step, I presented a sample code of simple Drag & Drop API. As simple as it is, the code was a HTML file and nothing comprehensive. Since it is a static file, it doesn’t upload the file. And that’s what we’re going to do in this step: To make it able to upload files to the server by Drag & Drop. In order to do that, we use Express to run the server and Formidable, a Node.js module for parsing form data, to upload files.

There is an another handful tutorial on how to build a file-upload application with Express and Formidable, but this is not using the Drop API.

If you just want to see or test the code, please jump to From Github section.

Prerequisite

Although the prior experience in the backend Javascript is preferable, the code being used here is very simple. So probably this tutorial can be also a good place to start with Node.js, not just the Drag & Drop API or Ajax.

File Structure

The file structure of the code used in this step is as follows:

.
└ uploads
└ server.js
└ index.html
└ dropUploadFile.js
└ style.css
└ package.json

Let’s create those files.

$ mkdir drop-upload-app && cd drop-upload-app
$ mkdir uploads
$ touch server.js index.html dropUploadFile.js style.css

Install npm Packages & Run Server

To build and test our simple Drop & Upload app, we need only three npm packages.

  1. Express: web application framework
  2. Formidable: form data parser
  3. Nodemon: Change monitor & server restarter

I’m used to nodemon when I test a small piece of codes, however, of course you can use your favorite module to do the same job. I’ve used multer, another great module, to handle the file form data, but I decided to give a shot at Formidable this time.

Now initialize a npm project and install the packages required.

$ yarn init
$ yarn add express formidable
$ yarn add nodemon --dev

After installing packages, add a npm script inside package.json to start the server.

"scripts": {
  "start": "nodemon server.js --watch server.js"
}

Write the code below inside server.js.

server.js
var express = require('express');
var formidable = require('formidable');

var app = express();
app.use(express.static(__dirname));

// GET: display index.html
app.get('/', function (req, res) {
	res.sendFile(__dirname + '/index.html');
});

// POST: handle form data sent from client
app.post('/upload', function(req, res) {
	var form = new formidable.IncomingForm();
	var index, filename;

	form.parse(req);

	form.on('field', function(name, value) {
		if (name == 'index') index = value;
	});

	form.on('fileBegin', function(name, file) {
		file.path = __dirname + '/uploads/' + file.name;
	});
	
	form.on('file', function(name, file) {
		filename = file.name;
	});

	form.on('end', function() {
		res.json({
			index: index,
			filename: filename
		});
	});

	form.on('error', function () {
		res.end('Something went wrong on ther server side. Your file may not have yet uploaded.');
	});
});

// Run server
app.listen(3000, function () {
	console.log("Server listening at http://localhost:3000.");
});

The crucial part of the code above is app.post(). Inside it, the whole logic to handle the form data sent from client are written.

The whole process is asynchronous, which means that if you try to upload a huge size file and a small size file at the same time, the latter file would be processed faster, then return the notice to the client before the former, although it is uploaded later by the client. As a result of this, it causes the sequential disorder between uploading and saving, and clients don’t always get the notion which file is saved in the same order as they drop them. This is why I added an index variable as an ID to determine which file is saved.

Write Hello world or something inside index.html and the run the command:

$ yarn start

This will execute nodemon and run server at http://localhost:3000. If you see the message you wrote, the server is running properly. The nodemon will watch the changes of server.js and restart the server.

HTML & CSS

For the both files, please refer to the index.html and style.css from Github. The only difference from the Step 1 is the name of function assigned to the aborting button element, from abortRead() to abortUpload().

Javascript

The all codes in this section belong to dropUploadFile.js. Since this is a series of tutorials, the file is primarily same as the Step 1. It’s just done a modification for Ajax. If you haven’t followed the Step 1, please first grab the code, and then continue.

Add a new global variable xhr.

…
var reader, files, xhr;
var dropZone = document.getElementById('dropZone'),
…

Modify the main function of this app.

function handleFileSelect(e) {
	
	…
	
	for (var i=0, f; f=files[i]; i++) {

		// Only process image files.
		if ( !f.type.match('image.*') ) continue;
		
		// Create form data containing a file to be uploaded.
		var formData = new FormData();
		formData.append("index", i);
		formData.append("image", f);

		// Ajax event: Upload files to the server.
		xhr = new XMLHttpRequest();
		xhr.onreadystatechange = ajaxHandler;
		xhr.onprogress = progressHandler;
		xhr.open('POST', '/upload', true);
		xhr.send(formData);

	} // END for

}

Although the app are now to be Ajax-ready, we can share the same code of progressHandler from the Step 1. For more information about XMLHttpRequest, you may want to check out MDN or w3schools websites.

The code above is designed to send the file data one by one, not files data at once. It is, however, possible to create a formData associating the all files to it, and then pass to the server. I adopted the former strategy just because I want a response for each file. If you want to send the files data as an one FormData object, you need modifications on both client and server side.

Now let’s create a new function ajaxHandler.

// Handle the ajax response from the server.
function ajaxHandler() {
	if (this.readyState == 4 && this.status == 200) {
		var response = JSON.parse(this.response);
		console.log(response.filename + ' uploaded');
		appendThumbnail( files[response.index] );
	} else {
		// Uncomment if you want to display the states other than above.
		// console.log('State: ' + this.readyState + ', ' + this.statusText); 
	}
}

Every time the xhr gets the ok status, the ajaxHandler displays the name of the uploaded file on the console, then pass the file data according to the value of index returned by the server to appendThumbnail function, which too is not changed from the Step 1.

We need one more function to abort the process of Ajax.

function abortUpload(e) {
		e.stopPropagation();
		e.preventDefault();
		xhr.abort();
	}

That’s it. Please check the complete version of dropUploadFile.js on Github.

With the server running, visit http://localhost:3000, drop image files and see whether your app is working properly. The files will be saved in uploads folder.

From Github

The source code for this tutorials are available on Github.

$ git clone https://github.com/zacfukuda/react-drop-upload-app.git

The code used in the Step 2 is located in step2-ajax folder. To test, please follow the commands below.

$ cd react-drop-upload-app/step2-ajax
$ mkdir uploads
$ yarn
$ yarn start

The application will be run at http://localhost:3000.

What’s Not Good About This App

Despite the fact that file datas are sent to the server asynchronously, they share one progress bar. Hence this surely causes the trouble and what it displays is not adequate and precise. The first solution coming across my mind to walk around this problem was to process the XMLHttpRequest synchronously, but I got a warning from Chrome that the synchronous request will be deprecated in future.

Next solution I came up was to use Socket. But this complexes our application and make it harder to understand. The end purpose of this tutorials is to get you familiar with React. So I gave up. Probably I will write an article regarding the socket technology, how to display an adequate progress bar communication with the server, in future.

Next Step: React-ify

Last and finally, the next step is to make our app a React one. Until this point, the primary focus was on how to build a drop & file application. Now it’s time to move on to our main purpose. In order to build a React app it requires you to understand how to set up a developing environment as well—in other words how to set up webpack. And I’m going to cover that too.

Go to the Step 3: React-ify with Babel & webpack.