avatar
Filip Jerga
August 3, 2020

Image upload with Cloudinary - Part 3 (Next/React & Node JS)

Let's implement simple image gallery

TODO: provide alt

Welcome in the third and last part of "how to upload an image" guide. We will start with some useful links.

Part 2: https://www.eincode.com/blogs/image-upload-with-cloudinary-part-2

Video guide: https://youtu.be/6czwam9chvM

Lesson code: https://github.com/Jerga99/next-youtube-course/commit/3ce18ff1383f165e4be7b080f20a175328a0cdc8

This part will be the shortest one. We will start with refactoring of our code. In your server folder create services folder with three files -> multers.js, data-uri.js, cloudinary.js

Let's each of this file separately

const multer = require('multer');

const ALLOWED_FORMATS = ['image/jpeg', 'image/png', 'image/jpg'];

const storage = multer.memoryStorage();
exports.upload = multer({
  storage,
  fileFilter: function(req, file, cb) {
    if (ALLOWED_FORMATS.includes(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error('Not supported file type!'), false);
    }
  }
})
multer


const path = require('path');
const DatauriParser = require('datauri/parser');
const parser = new DatauriParser();

exports.formatBufferTo64 = file =>
  parser.format(path.extname(file.originalname).toString(), file.buffer)
data-uri

const cloudinary = require('cloudinary').v2;

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET
})

exports.cloudinaryUpload = file => cloudinary.uploader.upload(file);
cloudinary

Remove all of this code from index.js and move it to the files specified above.

Then, in server.js import functions.

const { formatBufferTo64 } = require('./services/data-uri');
const { upload } = require('./services/multer');
const { cloudinaryUpload } = require('./services/cloudinary');
index

Now we need to create a solution to store files in DB. In this project I am using very simple local database. If you want to use mongoose or SQL it's very easy to integrate it into this project.

First register the model which will expose functions and JSON file to store your image data.

Create image.js file in db/image.js with following content:

const fileDb = require('./model');

module.exports = fileDb.register('images');
image

This will register the model. Now we can import the model into index.js and call function "save" to store image data into JSON file. Let's modify the endpoint.

const CImage = require('./db/image');

app.post('/api/image-upload', singleUploadCtrl, async (req, res) => {
  try {
    if (!req.file) { throw new Error('Image is not presented!'); }
    const file64 = formatBufferTo64(req.file);
    const uploadResult = await cloudinaryUpload(file64.content);

    // You can use here your own DB Solution
    const cImage = new CImage({cloudinaryId: uploadResult.public_id, url: uploadResult.secure_url});
    await cImage.save();
    // ----------

    return res.json(cImage);
  } catch(e) {
    return res.status(422).send({message: e.message})
  }
})
index

We have imported the model, created the instance of it with results from the cloudinary upload(id, url). Then we are calling save function which will store these data into db/data/images.js

Here is a very good spot to replace my model with yours Database specific solution.

After image is saved we return the image in the response. Response signature didn't change from the last time. We are still sending the same data.

Now we are going to implement endpoint to fetch images from images.json. That should be simple.

app.get('/api/images', async (req, res) => {
  const images = await CImage.getAll();
  return res.json(images);
})
index

That's it! Just call function getAll which will fetch all images from json file. We are done with the server.

Let's move to the client application where we will create new page -> images.js


import { useGet } from 'restful-react';
import { PageTitle } from 'components/shared';

const Images = () => {
  const { data: images, loading } = useGet({
    path: 'images'
  });

  const displayImages = () =>
    images.map(image =>
      <div key={image.cloudinaryId} className="col-md-3">
        <a
          className="d-block mb-4 h-100"
          target="_blank"
          href={image.url} >
          <img className="img-fluid img-thumbnail" src={image.url}/>
        </a>
      </div>
    )

  if (loading) {
    return 'Loading images...'
  }

  return (
    <>
      <PageTitle text="Image gallery"/>
      <div className="row text-center text-lg-left">
        { images ? displayImages() : 'There are no images :('}
      </div>
    </>
  )
}

export default Images;
images

First we will use useGet hook to make a GET request to our previously created endpoint. This will fetch the images.

Then we just simply iterate over them images and display them.

Guys that should be it. Last part done!

For more courses and blogs see: https://academy.eincode.com

Cheers.

Filip