Croc
Croc

Reputation: 838

Best way to store images in MERN stack web application

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.

Upvotes: 8

Views: 12431

Answers (4)

Croc
Croc

Reputation: 838

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.

Upvotes: 3

Shift Mora
Shift Mora

Reputation: 86

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
);

Upvotes: 3

hangindev.com
hangindev.com

Reputation: 4873

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
}

Upvotes: 5

Frank van Puffelen
Frank van Puffelen

Reputation: 599011

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.

Upvotes: 1

Related Questions