Simple file upload in Node.js

Originally Posted on Jan 20, 2018

Categorised under #HowTo

I have been using Koa.js for a while now and it turns out to be great. I’m having a lot of fun playing around with it since its simple and configurable. And I was searching for a ground up way to execute file uploads so that I can tweak its functionality however I want to. And it turns out, It’s not as difficult as I thought. Before going forward, I’m assuming you have a basic knowledge of how to setup basic Koa.js server with routers since it’s being covered in so many articles. So I’ll be skipping the server setup with routers. Click here if you want to go through an article that covers the same. So, Lets get coding.

Server Page : app.js

// app.js
const Koa = require("koa");
const koaBody = require("koa-body");
const logger = require("koa-logger");

const router = require("./router");

const app = new Koa();

app.use(koaBody(
    { multipart: true }
));

app.use(logger());

app.use(router.routes()).use(router.allowedMethods());

app.listen(3000, () => {
    console.log("Listening at 3000");
});

The above app.js file starts up the server and starts listening in port 3000 for requests. Simple stuff, nothing fancy. But things to be noted are the koa-body module and { multipart : true } option. This part is really important, since koa-body parses the request body and populates Koa ctx object based on the form data being sent. When a file is being sent from the front-end, koa-body parses the request and the uploaded file attributes will be available under ctx.request.files which we can then access across Koa server to implement file upload functionality

Router and Upload Logic : Router.js

Well, Its not a good practice to add the server logic under router.js file since router is only used for routing the requests. You will have a controller file to take care of the logic when the request is being routed from router. But for the course of this tutorial, I have written the logic in the router itself.

In this part, you will need a package called promisepipe which I’ll explain in a second. For now, install the package by running the following command. And create a folder named uploads in the root of the project which will hold uploaded images.

npm install promisepipe
// router.js
const Koa = require("koa");
const Router = require("koa-router");
const promisePipe = require("promisepipe");
const fs = require("fs");
const path = require("path");

const router = new Router();

router.get("/", (context, next) => {
    context.body = "Hey";
});

router.post("/upload", async (context, next) {
    try {
        const uploadfile = context.request.files.file;
        const savefile = `${Date.now()}#${uploadfile.name}`;
        const readStream = fs.createReadStream(uploadfile.path);
        const writeStream = fs.createWriteStream(path.join("uploads", savefile));
        await promisePipe(
            readStream.on("error", () => {
                throw new Error({
                    errors: "File Read Error"
                });
            }),
            writeStream.on("error", () => {
                throw new Error({
                    errors: "Write Error"
                });
            })
        );
        context.body = {
            message: "File Uploaded"
        };
    } catch (err) {
        console.log(err);
        context.body = {
            message: "There was an error",
            errors: err
        };
    }
});

module.exports = router;

The Upload Logic

Anything that is written in the bold letters in router.js is important and I’ll be explaining the mode of execution one by one.

By the end of execution, file will be saved in uploads folder and its name will be stored in savefile variable which can be added to the database for tracking down the files when they are requested. And if there is an error while reading or writing, it will be caught by the catch block and valid response to the front-end will be sent.

Summing it all up

Koa.js is an amazing lightweight configurable framework for Node.js. And arguably, There could be tons of better ways to achieve the same results but this turned out to be the best one for me where i can configure the functionality from the ground up. So if you know of any way to make this code better with added functionalities, I’d look forward to hear it from you. Happy coding. Have a nice day.