7

I am building a web application using the MERN stack (MongoDB, Express Server, ReactJS front end and NodeJS back end) and was wondering some good ways to store images from the back end.

In the past, I have used Firebase for authentication and storage directly from the front end. As I am handling my own user authentication model in MongoDB, is it possible to still use firebase storage and if so would it be from the front end or back end. If it was from the front end, how would I secure it without having firebase authentication?

Other options I have read into are storing images into MongoDB using GridFS or storing on the server using Multer.

Once I have a solution in mind, I will be able to read the docs and figure out how to get it done.

Any advice is appreciated.

Croc
  • 789
  • 7
  • 13

4 Answers4

5

An option is to upload the image to Cloudinary in the client-side and save the returned URL to MongoDB with your own API. Cloudinary does more than hosting your images but also handles image manipulation and optimization and more.

Basically what you will have to do is:

  1. Sign up for a Cloudinary account
  2. Go to Settings -> Upload
  3. Add an "upload preset" with 'Unsigned mode' to enable unsigned uploading to Cloudinary

Then your upload function can be something like this:

async function uploadImage(file) { // file from <input type="file"> 
  const data = new FormData();
  data.append("file", file);
  data.append("upload_preset", NAME_OF_UPLOAD_PRESET);

  const res = await fetch(
    `https://api.cloudinary.com/v1_1/${YOUR_ID}/image/upload`,
    {
      method: "POST",
      body: data,
    }
  );
  const img = await res.json();
  // Post `img.secure_url` to your server and save to MongoDB
}
hangindev.com
  • 4,573
  • 12
  • 28
  • Hey @Hangindev, this is exactly what I want to do but I'm still super new MERN and Cloudinary. Do you mind helping me out with this? I'm just not 100% sure where everything's suppose to go https://jsfiddle.net/qfme5h8k/ – finners Jul 25 '20 at 01:47
  • 2
    Hi @FrazerFindlater, you may check out this [codesandbox](https://codesandbox.io/s/react-cloudinary-demo-dn2ho-sre1u) I put together. – hangindev.com Jul 25 '20 at 05:24
3

I think using multer is the very convenient way.

You can upload the images into a folder using multer and store the reference URL in MongoDB. It is also important if you are willing to host your MERN application. You don't need any third party help like firebase or Cloudinary uploads and authentications (you have done this already).

So you can host your own app using your own functionalities. No external cost (just for the domain :D)

This may help you to get a brief idea.

const InstrumentImageStore = multer.diskStorage({
  destination: function (req, file, callback) {
    const userId = req.userId;
    const dir = `instrumentImgs/${userId}`;
    fs.exists(dir, (exist) => {
      if (!exist) {
        return fs.mkdir(dir, (error) => callback(error, dir));
      }
      return callback(null, dir);
    });

  },
  filename: function (req, file, callback) {
    callback(null, Date.now() + "-" + file.originalname);
  },
});

router.post(
  "/add/instrument",
  [isauth, multer({ storage: InstrumentImageStore }).array("imageArr", 5)],
//isauth is another middleware that restricts requests using JWT
  instrumentController.addInstrument
);
Shift Mora
  • 86
  • 3
3

I ended up implementing Firebase Storage from the Firebase Admin SDK and using Multer to store images in memory until I load them to Firebase.

https://firebase.google.com/docs/storage/admin/start

const uploader = multer({
  storage: multer.memoryStorage(),
  limits: {
    fileSize: 5 * 1024 * 1024,
  },
});


// @route   POST api/users/upload/avatar
// @desc    Upload a new avatar and save to storage
// @access  Private
router.post('/upload/avatar', [auth, uploader.single('image')], async (req, res, next) => {
  if (!req.file) {
    res.status(400).json({ msg: 'No file submitted.' });
    return;
  }

  try {
    const blob = firebase.bucket.file(req.file.originalname);
    const blobStream = blob.createWriteStream({
      gzip: true,
      resumable: false,
      metadata: {
        contentType: req.file.mimetype,
      },
    });

    blobStream.on('error', (err) => next(err));

    blobStream.on('finish', () => {
      publicUrl = `https://firebasestorage.googleapis.com/v0/b/${
        firebase.bucket.name
      }/o/${encodeURI(blob.name)}?alt=media`;

      res.status(200).json({
        photoURL: publicUrl,
      });

      User.findById(req.user.id).then((user) => {
        user.photoURL = publicUrl;
        user.save();
      });
    });

    blobStream.end(req.file.buffer);
  } catch (error) {
    console.error(error.message);
    res.status(500).send({ msg: 'A Server error occurred' });
  }
});

Thought this might be helpful if someone stumbles upon this post in the future.

Croc
  • 789
  • 7
  • 13
1

You can wire up any external authentication system to Firebase Authentication by [implementing a custom provider](https://firebase.google.com/docs/auth/web/custom-auth for the latter.

This requires that you run code in a trusted environment, such as a server you control or Cloud Functions, where you take the authentication results of the user and convert them into a Firebase Authentication token.

The client-side then signs into Firebase with that token, and from that moment on Storage (and other services) knows about the user just as before.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807