'Uploading image to amazon s3 using multer-s3 nodejs

I am trying to upload an image to amazon s3 using multer-s3, but I am getting this error:

TypeError: Expected opts.s3 to be object node_modules/multer-s3/index.js:69:20

This is my server code:

var upload = multer({
    storage: s3({
        dirname: '/',
        bucket: 'bucket',
        secretAccessKey: 'key',
        accessKeyId: 'key',
        region: 'us-west-2',
        filename: function (req, file, cb) {
            cb(null, file.originalname); 
        }
    })
});

app.post('/upload', upload.array('file'), function (req, res, next) {
    res.send("Uploaded!");
});

Why I am getting this error?



Solution 1:[1]

[Update Mar 2022] It works perfectly fine till-date and now also shows the uploaded file public URL as well.

Complete and working Node Cheat | Upload to s3 using multer-s3 available.

Code:

var express = require('express'),
    aws = require('aws-sdk'),
    bodyParser = require('body-parser'),
    multer = require('multer'),
    multerS3 = require('multer-s3');

aws.config.update({
    secretAccessKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
    accessKeyId: 'XXXXXXXXXXXXXXX',
    region: 'us-east-1'
});

var app = express(),
    s3 = new aws.S3();

app.use(bodyParser.json());

var upload = multer({
    storage: multerS3({
        s3: s3,
        acl: 'public-read',
        bucket: 'bucket-name',
        key: function (req, file, cb) {
            console.log(file);
            cb(null, file.originalname); //use Date.now() for unique file keys
        }
    })
});

//open in browser to see upload form
app.get('/', function (req, res) {
    res.sendFile(__dirname + '/index.html');//index.html is inside node-cheat
});

//use by upload form
app.post('/upload', upload.array('upl', 25), function (req, res, next) {
    res.send({
        message: "Uploaded!",
        urls: req.files.map(function(file) {
            return {url: file.location, name: file.key, type: file.mimetype, size: file.size};
        })
    });
});
  
app.listen(3000, function () {
    console.log('Example app listening on port 3000!');
});

For complete repo:

Clone node-cheat express_multer_s3, run node app followed by npm install express body-parser aws-sdk multer multer-s3.

Happy Helping!

Solution 2:[2]

@V31 has answered very well still I want to add my 2 cents.

I believe in keeping one responsibility into one file, for better code organization and debugging purpose.

I have created a file for uploading upload.js.

require('dotenv').config();
const AWS = require('aws-sdk');
const multer = require('multer');
const multerS3 = require('multer-s3');

const s3Config = new AWS.S3({
    accessKeyId: process.env.AWS_IAM_USER_KEY,
    secretAccessKey: process.env.AWS_IAM_USER_SECRET,
    Bucket: process.env.AWS_BUCKET_NAME
  });

const fileFilter = (req, file, cb) => {
    if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
        cb(null, true)
    } else {
        cb(null, false)
    }
}

// this is just to test locally if multer is working fine.
const storage = multer.diskStorage({
    destination: (req, res, cb) => {
        cb(null, 'src/api/media/profiles')
    },
    filename: (req, file, cb) => {
        cb(null, new Date().toISOString() + '-' + file.originalname)
    }
})

const multerS3Config = multerS3({
    s3: s3Config,
    bucket: process.env.AWS_BUCKET_NAME,
    metadata: function (req, file, cb) {
        cb(null, { fieldName: file.fieldname });
    },
    key: function (req, file, cb) {
        console.log(file)
        cb(null, new Date().toISOString() + '-' + file.originalname)
    }
});

const upload = multer({
    storage: multerS3Config,
    fileFilter: fileFilter,
    limits: {
        fileSize: 1024 * 1024 * 5 // we are allowing only 5 MB files
    }
})

exports.profileImage = upload; 

Which is imported inside my routes routes.js

const express = require('express');

const ProfileController = require('../profile/controller');
const { profileImage } = require('../utils/upload.js'); 

 const routes = (app) => {
    const apiRoutes = express.Router();

    apiRoutes.use('/profile', profileRoutes);
    profileRoutes.post('/',profileImage.single('profileImage'), ProfileController.saveProfile);

    app.use('/api', apiRoutes);

 }

module.exports = routes

Postman screen shot for post body

enter image description here

Solution 3:[3]

I just want to add my cents,

There are many comments in all answers like how to get public URL after uploading and S3 response object and lets see implementation and cases,

// INITIALIZE NPMS
var AWS = require('aws-sdk'),
multer = require('multer'),
multerS3 = require('multer-s3'),
path = require('path');

// CONFIGURATION OF S3
AWS.config.update({
    secretAccessKey: '***********************************',
    accessKeyId: '****************',
    region: 'us-east-1'
});

// CREATE OBJECT FOR S3
const S3 = new AWS.S3();
// CREATE MULTER FUNCTION FOR UPLOAD
var upload = multer({
    // CREATE MULTER-S3 FUNCTION FOR STORAGE
    storage: multerS3({
        s3: S3,
        acl: 'public-read',
        // bucket - WE CAN PASS SUB FOLDER NAME ALSO LIKE 'bucket-name/sub-folder1'
        bucket: 'bucket-name',
        // META DATA FOR PUTTING FIELD NAME
        metadata: function (req, file, cb) {
            cb(null, { fieldName: file.fieldname });
        },
        // SET / MODIFY ORIGINAL FILE NAME
        key: function (req, file, cb) {
            cb(null, file.originalname); //set unique file name if you wise using Date.toISOString()
            // EXAMPLE 1
            // cb(null, Date.now() + '-' + file.originalname);
            // EXAMPLE 2
            // cb(null, new Date().toISOString() + '-' + file.originalname);

        }
    }),
    // SET DEFAULT FILE SIZE UPLOAD LIMIT
    limits: { fileSize: 1024 * 1024 * 50 }, // 50MB
    // FILTER OPTIONS LIKE VALIDATING FILE EXTENSION
    fileFilter: function(req, file, cb) {
        const filetypes = /jpeg|jpg|png/;
        const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
        const mimetype = filetypes.test(file.mimetype);
        if (mimetype && extname) {
            return cb(null, true);
        } else {
            cb("Error: Allow images only of extensions jpeg|jpg|png !");
        }
    }
});

There are three cases, if we want to retrieve files res object from S3 after upload:

Case 1: When we are using .single(fieldname) method it will return file object in req.file

app.post('/upload', upload.single('file'), function (req, res, next) {
    console.log('Uploaded!');
    res.send(req.file);
});

Case 2: When we are using .array(fieldname[, maxCount]) method it will return file object in req.files

app.post('/upload', upload.array('file', 1), function (req, res, next) {
    console.log('Uploaded!');
    res.send(req.files);
});

Case 3: When we are using .fields(fields) method it will return file object in req.files

app.post('/upload', upload.fields([
  { name: 'avatar', maxCount: 1 },
  { name: 'gallery', maxCount: 8 }
]), function (req, res, next) {
    console.log('Uploaded!');
    res.send(req.files);
});

Solution 4:[4]

s3 needs to be an object to be passed. According to the docs, the object needs to be like this:

var upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'some-bucket',
    metadata: function (req, file, cb) {
      cb(null, {fieldName: file.fieldname});
    },
    key: function (req, file, cb) {
      cb(null, Date.now().toString())
    }
  })
})

MulterS3 Docs

Solution 5:[5]

/*** Using Multer To Upload Image image is uploading */

const fileStorage = multer.diskStorage({
  destination: function(req, file, cb) {
    cb(null, "./public/uploads");
  },
  filename: function(req, file, cb) {
    cb(null, file.originalname);
  }
});

/** AWS catalog */

aws.config.update({
  secretAccessKey: process.env.SECRET_KEY,
  accessKeyId: process.env.ACCESS_KEY,
  region: "us-east-1"
});

const s3 = new aws.S3();
const awsStorage = multerS3({
  s3: s3,
  bucket: process.env.BUCKET_NAME,
  key: function(req, file, cb) {
    console.log(file);
    cb(null, file.originalname);
  }
});

const upload = multer({
  storage: awsStorage(),
  /** in above line if you are using local storage in ./public/uploads folder than use
   ******* storage: fileStorage,
   * if you are using aws s3 bucket storage than use
   ******* storage: awsStorage(),
   */
  limits: { fileSize: 5000000 },
  fileFilter: function(req, file, cb) {
    checkFileType(file, cb);
  }
});
app.post("/user-profile-image", upload.single("profile"), (req, res, err) => {
  try {
    res.send(req.file);
  } catch (err) {
    res.send(400);
  }
});

const checkFileType = (file, cb) => {
  const filetypes = /jpeg|jpg|png|gif/;
  const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
  const mimetype = filetypes.test(file.mimetype);

  if (mimetype && extname) {
    return cb(null, true);
  } else {
    cb("Error: Images Only!");
  }
};

Solution 6:[6]

    //here is the function for upload the images on aws bucket using multer
    
    const path = require('path');
    const fs = require('fs');
    const aws = require('aws-sdk');
    const multer = require('multer');
    const multerS3 = require('multer-s3');
    
    
    aws.config.update({
        secretAccessKey: '**************************',
        accessKeyId: '********************',
        region: '**********************'
    });
    
    s3 = new aws.S3();
    
    
    const storage = multerS3({
        s3: s3,
        bucket: 'bucket-name',
        key: function(req, file, cb) {
            console.log(file);
            cb(null, file.originalname);
        }
    })
    
    //export the created function
    exports.uploadVideo = multer({ storage: storage }).single('file_name');
    
 //================================================================================   
    
    //import uploadVideo function whenever you need to upload the file on aws s3 bucket
    const { uploadVideo } = require('../../services/upload');
    
    exports.videoUpload = (req, res) => {
        uploadVideo(req, res, function(err) {
            if (err) {
                console.log(err);
                res.json({ status: 401, msg: err.message });
            } else {
                const image = getImagePath(req.file.filename);
                res.json({ status: 200, msg: 'Image uploaded sucess.', data: image });
            }
        });
    }
    //================================================================================
    
    //here is route file
    router.post('/video-upload',uploadController.videoUpload);

Solution 7:[7]

I was passing S3 to mutler in caps, like

S3: {object}

Changing it to small s3 works for me:-

s3: {object}

Solution 8:[8]

WE can Upload IMAGE/ CSV/ EXCEL files to AWS s3 using multer-s3.

Im using .single(fieldname) method for uploading single file.

const aws = require('aws-sdk');
const multer = require('multer');
const multerS3 = require('multer-s3');

const s3 = new aws.S3({
  accessKeyId: process.env.AWS_ACCESS_KEY,
    secretAccessKey: process.env.AWS_SECRET_KEY,
    region: process.env.REGION,
});

 const upload = multer({
   storage: multerS3({
     s3: s3,
     bucket: process.env.AWS_S3_BUCKET,
     metadata: function (req, file, cb) {
       cb(null, {fieldName: 'Meta_Data'});
     },
     key: function (req, file, cb) {
      cb(null, file.originalname);
     },
     limits: {
        fileSize: 1024 * 1024 * 5 //  allowed only 5 MB files
    }
   })
 }).single('file');
 
exports.uploadfile = async(req,res,next)=>{
   try{ 
         upload(req,res, function(err){
             if(err){
                 console.log(err);
             }
             console.log(req.file.location);    
   })
})
}catch (err){
   res.status(400).json({
       status : 'fail', 
       message : err.message
   });
}
}

In Routes file

router.route('/')
      .post(imageController.uploadfile);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4 Zeeshan Hassan Memon
Solution 5
Solution 6 Akshay Mistry
Solution 7 harbrinder_singh
Solution 8