Search code examples
node.jsexpressamazon-s3routermulter

Express Router - uploading to S3 and MongoDb


I'm trying to do the following in Node.js using express router, Multer-S3, Multer, AWS and Mongodb.

I want to: 1: Check if filetype is image, price is number etc (some kind of quality check) 2: If above true, upload image to S3 to get Image url 3: If Image Url was generated, upload to Mongodb, including the generated image url..

Trying with below code but can only get one of these to work at same time..

const express = require("express");
const router = express.Router();

const shopController = require("../controllers/shop");

router.post(
  "/shop/create/:shopId",
  shopController.creatingShop,
  shopController.createShopItem
);
const ShopItem = require("../models/shopitem"); //Mongoose Schema

const multer = require("multer");
const fileview = multer().single("file1"); //Trying to use this to view file before uploading to S3

const uploader = require("../services/file-upload");
const singleUpload = uploader.single("file1"); //Using this to upload to S3

exports.createShopItem = (req, res, next) => {
  fileview(req, res, function (err) {
    const file = req.file;
    const title = req.body.title;
    const price = req.body.price;
    const description = req.body.description;
    const location = req.body.location;
    const user = "OrreSnorre";

    if (
      file.mimetype != "image/jpeg" &&
      file.mimetype != "image/jpg" &&
      file.mimetype != "image/png"
    ) {
      return next(new Error("invalid file type"));
    }

    if (file.size > 2500000) {
      return next(new Error("Your image is to big. Maximum 2.5mb"));
    }
    next();
    console.log(
      "Here I want to add upload text to mongoDb... including URL from S3 after it is generated"
    );
   
  });

exports.creatingShop = (req, res, next) => {
  singleUpload(req, res, function (err) {
    console.log(req.file);
    // res.json({ "image-url": req.file.location });
  });
  next();
};

Anyone got ideas? Or examples that work?

Best regards, Oscar


Solution

  • There are 2 ways to do this, either you can use only multer or multer-s3.

    For simplicity, I will show you the way using only multer.

    Flow of processing as follow:

    1. Multer process and save to local
    2. You read from local, and upload to s3 using s3 SDK (You should explore how to remove the file after upload as well, but I wont clutter you with this logic here)
    3. If upload is successful, you retrieve the URL and pass it to your MongoDB.
        // Make "temp" directory as multer.diskStorage wont create folder
        fs.mkdir('./temp', { recursive: true }, (err) => {
          if (err) throw err;
        });
        const PORT = parseInt(process.argv[2]) || parseInt(process.env.PORT) || 3000;
        // Multer
        const storage = multer.diskStorage({
          destination: function (req, file, cb) {
            cb(null, './temp');
          },
          filename: function (req, file, cb) {
            let extArray = file.mimetype.split('/');
            let extension = extArray[extArray.length - 1];
            cb(null, new Date().getTime() + '.' + extension);
          },
        });
        
        const upload = multer({ storage: storage });
        
        const endpoint = new AWS.Endpoint(AWS_S3_HOSTNAME);
        const s3 = new AWS.S3({
          endpoint,
          accessKeyId: AWS_S3_ACCESSKEY_ID,
          secretAccessKey: AWS_S3_SECRET_ACCESSKEY,
        });
        
        // Get the uploaded file in local here
        const readFile = (path) =>
          new Promise((resolve, reject) =>
            fs.readFile(path, (err, buff) => {
              if (null != err) reject(err);
              else resolve(buff);
            })
        
        // Upload to AWS S3 here
        const putObject = (file, buff, s3) =>
          new Promise((resolve, reject) => {
            const params = {
              Bucket: AWS_S3_BUCKET_NAME,
              Key: file.filename,
              Body: buff,
              ACL: 'public-read',
              ContentType: file.mimetype,
              ContentLength: file.size,
            };
            s3.putObject(params, (err, result) => {
              if (null != err) reject(err);
              else resolve(file.filename);
            });
          });
          );
        
        const mongoClient = new MongoClient(MONGO_URL, {
          useNewUrlParser: true,
          useUnifiedTopology: true,
        });
        app.post('/api/post', upload.single('imageFile'), async (req, res) => {
        readFile(req.file.path)
                .then((buff) =>
                  // Insert Image to S3 upon succesful read
                  putObject(req.file, buff, s3)
                )
                .then((results) => {
                  // build url of the resource upon successful insertion
                  const resourceURL = `https://${AWS_S3_BUCKET_NAME}.${AWS_S3_HOSTNAME}/${results}`;
                  const doc = {
                    comments,
                    title,
                    ts: new Date(),
                    image: resourceURL, // Your URL reference to image here
                  };
                  // Insert to your mongoDB
                  mongoClient
                    .db(MONGO_DB)
                    .collection(MONGO_COLLECTION)
                    .insertOne(doc)
                    .then((results) => {
                      // delete the temp file when no error from MONGO & AWS S3
                      fs.unlink(req.file.path, () => {});
                      // return the inserted object
                      res.status(200).json(results.ops[0]);
                    })
                    .catch((error) => {
                      console.error('Mongo insert error: ', error);
                      res.status(500);
                      res.json({ error });
                    });
                })
                .catch((error) => {
                  console.error('insert error: ', error);
                  res.status(500);
                  res.json({ error });
                });
    }