Search code examples
ajaxfile-uploadmultipartform-datahapi.jscontent-length

uploading profile pic in hapijs 17.0


I am using hapijs version 17.0.1. I am trying to upload an image using ajax request on a hapijs route. Here is my AJAX code to upload profile pic:

var image_file_input = document.getElementById("user_profile_upload");

image_file_input.onchange = function () {
    if(this.files != undefined)
    {
        if(this.files[0] != undefined)
        {
            var formData = tests.formdata ? new FormData() : null;
            if (tests.formdata)
            {
                //alert(file)
                formData.append('image_file', this.files[0]);
                formData.append('userId', user_id);
                formData.append('memberId', member_id);
            }
            $.ajax({
                url: "/v1/User/uploadUserPic",
                data: formData,
                type: "POST",
                dataType: "json",
                contentType: false,
                processData: false,
                contentType: "multipart/form-data",
                success: function(data){
                    console.log(data);
                    var errMsg = null;
                    var resData = null;
                    if(data.statusCode == 200)
                    {
                        resData = data.result;
                    }
                    else
                    {
                        alert(data.message)
                    }
                },
                error: function(error){
                    alert(error);
                }
            });
        }
    }
}

And here is my Hapijs route Code:

    var uploadUserPic = {
        method: 'POST',
        path: '/v1/Module/uploadUserPic',
        config: {
            description: 'Update Image For User',
            tags: ['api', 'User'],
            auth: 'session',
            payload: {
                output: 'stream',
                parse: true,
                allow: 'multipart/form-data'
            },
            validate: {
                payload: {
                    userId : Joi.string().regex(/^[a-f\d]{24}$/i).required(),
                    memberId: Joi.string().required(),
                    image_file: Joi.object().required(),
                },
                failAction: FailCallBack
            }
        },
        handler: function (request, reply) {
            var resultData = null;
            var error = null;
            return new Promise(function (resolve) {
                var multiparty = require('multiparty');
                var fs = require('fs');
                var form = new multiparty.Form();
                form.parse(request.payload, function (err, fields, files) {
                    if(err)
                    {
                        error = err;
                        resolve();
                    }
                    else
                    {
                        var mkdirp = require('mkdirp');
                        var img_dir = "./files/users/";
                        mkdirp(img_dir, function (err) {
                            if (err)
                            {
                                error = err;
                                console.error(err);
                                resolve();
                            }
                            else
                            {
                                var oldpath = files.image_file.path;
                                var newpath = "./files/users/"+requestPayload.userId+".png";
                                fs.rename(oldpath, newpath, function (err) {
                                    if(err)
                                    {
                                        error = err;
                                    }
                                    resolve();
                                });
                            }
                        });
                    }
                });
            }).then(function (err, result) {
                if(err) return sendError(err);
                if(error) return sendError(error)
                return {
                    "statusCode": 200,
                    "success": true
                };
            });
        }
    }

The above code gives me following error cannot read property 'content-length' of undefined on line form.parse(request.payload, function (err, fields, files) {});

Please let me know If I am doing something wrong. If I replace the url in ajax request with anohter url that I have written in php then it works perfectly. which means that something is wrong with my hapijs/nodejs code.


Solution

  • There's a good post on how to handle file uploads in Hapi.js (written in version 16) https://scotch.io/bar-talk/handling-file-uploads-with-hapi-js

    Since you are using payload.parse = true, I am not seeing a particular reason why you have to use multiparty. I have the following working code that would save files (of any type) uploaded from client into uploads directory on the server (Please do not use directly on production as no sanitation is done)

            {
                path: '/upload',
                method: 'POST',
                config: {
                    payload: {
                        output: 'stream',
                        parse: true,
                        allow: 'multipart/form-data'
                    },
                    validate: {
                        payload: {
                            files: Joi.array().single()
                        }
                    }
                },
                handler: function(request) {
                    const p = request.payload, files = p.files
                    if(files) {
                        console.log(`${files.length} files`)
                        files.forEach(async file => {
                            const filename= file.hapi.filename
                            console.log(`Saving ${filename} to ./uploads`)
                            const out = fs.createWriteStream(`./uploads/${filename}`)
                            await file.pipe(out)
                        })
                    }
                    return {result: 'ok'}
                }
            }
    

    You can use the following curl command to test

    curl http://localhost:8080/upload -F 'files=@/path/to/a/note.txt' -F 'files=@/path/to/test.png' -vvv
    

    There are a few issues with your code. First in your $.ajax call, you have specified contentType twice, although it's not a syntax error but it's careless to code like that. Second the function's signature inside your .then() block is incorrect. You are mixing the idea of Promise and callback. I don't think the following line will be triggered

            if(err) return sendError(err);
    

    One last trivial thing, you said you are using Hapi 17 but based on the handler function's signature

    handler: function (request, reply) {
    ...
    

    Seems you are not totally onboard with Hapi17 as the new signature is

    handler: function (request, h) {
    

    And it's not just the rename of reply to h.