Nodejs Book: Chapter 7

In the previous chapter we added onto our basic ajax example and added the option
to upload a JSON formatted string and parse it on the server. So how do we handle
other functionality, like uploading a file? In this example we will make another
simple example, where we make a form to upload a jpg image and save it on the server.
Once the image is saved on the server we will display it on the client.

- api.js
+ public/
| - upload.html
| - + js/
| --- upload.js
| - + img/

Our directory layout for this example should look like above. We’ll start with
the client code as it hasn’t changed too much from the last chapter.

File: upload.html

<!DOCTYPE HTML>
<html>

    <head>

        <meta charset="utf-8"/>
        <title>Ajax Request</title>

    </head>

    <body>

        <input type="file" id="fileUpload"/>
        <button id="clickBtn">Click Me!</button><br>
        <img id="responseImage"></img>

        <script type="text/javascript" src="js/upload.js"></script>

    </body>

</html>

For the html side we have a file select input and an image with no src, which
we will replace once our image has uploaded.

File: js/upload.js

"use strict";

var fileUpload = document.getElementById("fileUpload");
var clickBtn = document.getElementById("clickBtn");
var responseImage = document.getElementById("responseImage");

clickBtn.addEventListener("click", function (event) {

    if(!fileUpload.files.length){
        alert("Please select a file!");
        return;
    }

    var file = fileUpload.files[0];
    var reader = new FileReader();
    reader.readAsArrayBuffer(file);

    reader.addEventListener("load", function(event){

        var xml = new XMLHttpRequest();
        xml.open("POST", "/api/upload", true);
        xml.send(event.target.result);

        xml.onload = function() {

            var image = document.createElement("img");
            image.setAttribute("src", xml.responseText);

            image.onload = function() {

                responseImage.parentNode.replaceChild(image, responseImage);
                responseImage = image;

            }

        }

    });

});

Our client side Javascript file has a few more lines added to it. First we check
to see if a file has been selected. If not file has been selected, then we don’t
attempt to upload anything.

If a file is selected, then we read it as an ArrayBuffer, and initiate and Ajax
request. When we send the request, we send the ArrayBuffer we read from the file
input. The concept of an array buffer is a little hard to grasp at first, but ultimately
it’s not very difficult. When information is stored on your computer, it’s stored
using a binary sequence of 1’s and 0’s. In the case of images this generally means
storing the red, green and blue values for each pixel in the image.

When we read and ArrayBuffer, we are simply reading the raw information from the
hard drive and keeping it in memory in our client Javascript. When we run
“`xml.send()“` with the ArrayBuffer paramter included, we are sending the exact
information stored on the harddrive on the client computer to the server.

File: callback.js

"use strict";

const fs = require("fs");
const http = require("http");
const uniqid= require("uniqid");
const handleFile = require("handle-file");

const server = http.createServer();
server.on("request", handleRequest);
server.listen(8080, handleListen);

function handleRequest(req, res) {

        console.log(req.method);

        if(req.method === "GET") {

                handleFile(req, res);

        } else if(req.method === "POST") {

                switch(req.url) {
                        case "/api/hello":

                                api_say_hello(req, res);

                        break;
                        case "/api/upload":

                                api_upload_file( req, res );

                        break;
                        default:

                                res.writeHead( 204, { "Content-Type" : "text/plain" });
                                res.end( "Empty Response" );

                        break;
                }

        } else {

                res.writeHead( 204, { "Content-Type" : "text/plain" });
                res.end( "Empty Response" );

        }

}

function handleListen( ) {

        console.log("Server is listening on port 8080");

}

function api_upload_file( req, res ) {

        let str = "/img/" + uniqid() + ".jpg";
        let writeStream = fs.createWriteStream("./public" + str);
        req.pipe(writeStream);

        req.on("end", function() {

                res.writeHead(200, { "Content-Type" : "text/plain" });
                res.end(str);

        });

}

There’s not too many changes. In this case, when we send an ArrayBuffer from the
client to the server, we skip sending the filename. So in this case we use the
uniqid library to create a random name for us. The uniqid library can be
installed with

 $ npm install uniqid

Also since we don’t have the file name, we assume a JPG type file. It’s a little
primitive, but we’re still working on a proof of concept. So this amount of functionality
is okay for now. As for the file actually being saved to the server, similar to
serving files, writing them is largely the same. We pipe the input from the
request directly to the hard drive. Once we’re done we tell the client where
the image can be loaded from to display on the client.