How to upload images to Cloudinary using Express and Template Engines

terieyenike

oteri

Posted on March 21, 2023

How to upload images to Cloudinary using Express and Template Engines

Uploading media images to a central location found in forms and creating your media profile on social media and other applications on the web is necessary to capture user input. Cloudinary as a tool provides such a feature making it possible to store all your media library in the cloud.

In this tutorial, you will learn how to use technologies like Express as the server with the post method to send this image to Cloudinary. The template engine (HBS) would generate HTML and display the image on the client-side (frontend) of the application.

Demo and source code

To follow through, you can find the source code in this repo and try the live demo.

The final app will look something like the following:

final app preview

Prerequisites

The following requirements are needed:

  • Cloudinary account. Sign up for a free account
  • Knowledge of JavaScript and Node.js
  • Have Node installed on your local machine, which comes with the node package manager (npm) for installing package dependencies

What is Cloudinary?

Cloudinary is a cloud-based image and video management service enabling users to upload, store, manage, manipulate, and deliver engaging media experiences at scale with its image and video APIs.

What are template engines?

Template engines work with server-side applications like Node.js runtime and enable you to use static template files in your application. There are many existing template engines to choose from, and they render data as variables into the HTML template.

Getting started

To get started, create a new directory for the Node project and call it upload-image.

mkdir upload-image
Enter fullscreen mode Exit fullscreen mode

Next, navigate to the new directory and run the following command to initialize the project:

npm init -y
Enter fullscreen mode Exit fullscreen mode

The -y flag accepts all the initial defaults in the package.json file

With the creation of the package.json file, install these dependencies:

npm install cloudinary dotenv express express-fileupload hbs
Enter fullscreen mode Exit fullscreen mode
  • cloudinary: will allow the use of the Node.js software development kit (SDK) and the classes in your code
  • dotenv: load environment and store secret variables from the .env file when working locally
  • express: is the Node.js web framework for creating the server
  • express-fileupload: an express middleware for uploading files
  • hbs: Express.js view engine for handlebars

Building the user interface with hbs

In the root directory, create these two folders, views and public. The views folder will contain the following files, index.hbs, layout.hbs, and media.hbs, while the public folder will have all the styles for the project. Create a folder named css and include the style.css file all within the public folder.

  • The views folder is the template that will have the HTML structure
  • The public folder is the container recognized by hbs for all the static files used in the project, like the styles
> public
      > css
          style.css
Enter fullscreen mode Exit fullscreen mode
 > views
        index.hbs
        layout.hbs
        media.hbs
Enter fullscreen mode Exit fullscreen mode

Update each of the files in the views folder with the following:

    // views/index.hbs

    <div class='section'>
      <h1>Uploading images to Cloudinary Console</h1>
      <form
        action='/upload'
        method='post'
        encType='multipart/form-data'
        ref='uploadImage'
        id='uploadImage'
      >
        <div class='form__section'>
          <input type='file' name='uploadFile' />
          <label>
            Please select the image to be uploaded
          </label>
        </div>
        <input type='submit' value='Upload!' class='btn' />
      </form>
    </div>
Enter fullscreen mode Exit fullscreen mode

The action attribute, /upload in the <form> element, represents where the data gets sent.

    // layout.hbs

    <html lang='en'>
      <head>
        <meta charset='UTF-8' />
        <meta http-equiv='X-UA-Compatible' content='IE=edge' />
        <meta name='viewport' content='width=device-width, initial-scale=1.0' />
        <meta
          name='description'
          content='Upload images using Cloudinary and Node.js'
        />
        <title>Take your images to the cloud</title>
        <link rel='preconnect' href='https://fonts.googleapis.com' />
        <link rel='preconnect' href='https://fonts.gstatic.com' crossorigin />
        <link
          href='https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap'
          rel='stylesheet'
        />
        <link rel='stylesheet' href='css/style.css' />
      </head>
      <body>
        {{{body}}}
      </body>
    </html>
Enter fullscreen mode Exit fullscreen mode

The layout.hbs file represents the typical markup for every website structure. Within the <body>, all the other files from the views folder are placed directly in the {{{body}}} which displays the template of the remaining files.

// media.hbs

    <div class='img-upload'>
      <h2>Title: MacBook Pro</h2>
      <img src="https://images.pexels.com/photos/8020173/pexels-photo-8020173.jpeg?auto=compress&cs=tinysrgb&w=800&lazy=load" alt='Cross pollination by insect' />
      <a href='/' class='btn'>Upload another photo</a>
    </div>
Enter fullscreen mode Exit fullscreen mode

This particular code will display the final image upon successful upload to Cloudinary in the endpoint, /upload, which we will set in the index.js file.

For the styles, copy and paste this code into the public/css/style.css file:

// public/css/style.css

    *,
    *:before,
    *:after {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    body {
      font-family: 'Inter', sans-serif;
    }
    a {
      text-decoration: none;
    }
    h1 {
      font-weight: 700;
    }
    .section {
      padding: 2em 3em;
    }
    .form__section {
      margin-top: 2em;
      margin-bottom: 2em;
    }
    label {
      display: block;
      margin-top: 0.75em;
    }
    .btn {
      border: unset;
      background: royalblue;
      color: #fff;
      padding: 1em 2em;
      cursor: pointer;
    }
    .img-upload {
      display: flex;
      align-items: center;
      flex-direction: column;
    }
    .img-upload h2 {
      margin-top: 1em;
    }
    .img-upload img {
      max-width: 75rem;
      width: 85%;
      margin-inline: auto;
      padding: 2em 0;
    }
    img {
      display: block;
      max-width: 100%;
    }
Enter fullscreen mode Exit fullscreen mode

Setting up a Node/Express app

Create a file named index.js in the root directory. The index.js file will contain all the code for the Node.js/Express server.

Copy and paste the following code into the index.js file:

// index.js

    const express = require('express');
    const app = express();

    const PORT = process.env.PORT || 4000;

    // middleware
    app.use(express.json());

    app.get('/', (req, res) => {
      res.send({ title: "Uploading images to Cloudinary Console" });
    });

    app.listen(PORT, () => {
      console.log(`server listening on port ${PORT}`);
    });
Enter fullscreen mode Exit fullscreen mode

In the code snippet above, the following occurs:

  • Declare the express module with require(‘express’)
  • Run the express function, as this defines the Express instance assigned to a variable app
  • Assign a port number, 4000, which will be accessible in the browser
  • The middleware, app.use() function parses the incoming JSON requests at the path specified
  • The app.get() handles the GET requests with the passed route to a given endpoint which in this case is the home route, /
  • The port environment variable is called in the app.listen() function and displays the console message when the app is running

Before testing and running this app, let’s update the package.json file within the scripts object:

// package.json

    {
      ...
      "scripts": {
        "start": "node index.js",
      },
      ...
    }
Enter fullscreen mode Exit fullscreen mode

Run this command:

node index.js
Enter fullscreen mode Exit fullscreen mode

The development server starts running on http://localhost:4000, which should look something like this:

the / endpoint

Configuring Cloudinary
With the server working, let’s configure the app with the following details from your Cloudinary dashboard.

First, create these files, .env and config.js, in the root directory.

Include this code in the .env file:

 // .env

    CLOUDINARY_CLOUD_NAME=<cloud-name>
    CLOUDINARY_API_KEY=<api-key>
    CLOUDINARY_API_SECRET=<api-secret>
Enter fullscreen mode Exit fullscreen mode

Access your variables, like your cloud name, API key, and API secret, from the Cloudinary dashboard. Replace the placeholders with your values.

cloudinary console

In the config.js file, copy and paste this code:

// config.js

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

    cloudinary.config({
      cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
      api_key: process.env.CLOUDINARY_API_KEY,
      api_secret: process.env.CLOUDINARY_API_SECRET,
      secure: true,
    });

    module.exports = cloudinary;
Enter fullscreen mode Exit fullscreen mode

The configuration of this file includes the following:

  • Importing the Cloudinary dependency
  • Set the configured parameters in the config objects with the appropriate variables from the .env file
  • Export the cloudinary classes with module.exports

Let’s update the index.js file with the following code:

// index.js

    ...
    const path = require("path");
    const fileUpload = require("express-fileupload");
    const cloudinary = require("./config");

    // dotenv
    require("dotenv").config();

    ...

    app.set("view engine", "hbs");
    app.set("views", path.join(__dirname, "views"));

    // middleware
    app.use("/", express.static(path.join(__dirname, "public")));
    app.use(
      fileUpload({
        useTempFiles: true,
        limits: { fileSize: 50 * 1024 * 1024 },
      })
    );

    app.get("/", (req, res) => {
      res.render("index", {
        title: "Uploading images to Cloudinary Console",
      });
    });

    app.post("/upload", async (req, res) => {
      if (!req.files || Object.keys(req.files).length === 0) {
        res.status(400).json({
          msg: "No files were uploaded. Try uploading an image",
        });
        return;
      }
      const uploadFile = req.files.uploadFile;
      const result = await cloudinary.uploader.upload(uploadFile.tempFilePath, {
        public_id: uploadFile.name,
        resource_type: "auto",
        folder: "uploaded",
        use_filename: true,
        unique_filename: false,
      });
      if (result.url) {
        res.render("media", {
          img: result.url,
          name: uploadFile.name.replace(/.jpeg|.jpg|.png|.webp/gi, ""),
        });
      } else {
        res.render("/upload");
      }
    });

    ...
Enter fullscreen mode Exit fullscreen mode

The code above does the following:

  • Imports Node.js path module for transforming file paths, express-fileupload, and cloudinary
  • Configure dotenv to prevent leaking the environment variables in the .env file
  • The app.set() function with the set “view engine” will render the files .hbs in the views folder
  • The other app.set() reads the file path of the directory views
  • The first app.use() middleware serves static files in the public folder
  • Managing the upload process is handled by the fileUpload options, and the fileSize indicates the maximum file size in bytes
  • The get request with the res.render() method accepts the template index and a title string data in the option object.
  • The post request with the endpoint /upload that route to the successful page with the final image. Also, within the callback of this HTTP method, it shows a 400 status error code and displays a message to the user of a failed image upload on clicking the button Upload.
  • Using the Cloudinary upload method, passing in parameters with the name of the folder to store and maintaining the file name of the image
  • Lastly, using regex, you strictly remove the extension from the uploaded image and return only the image name

Displaying the rendered data

With the server complete, let’s return to the index.js file and pass the rendered data property.

Let’s update the files in the views folder once again.

// views/index.hbs

    <div class='section'>
      <h1>{{title}}</h1>
      ...
    </div>
Enter fullscreen mode Exit fullscreen mode

Replace the content within the

with the expression {{variable name followed by a }}. When the template is executed, the expression is replaced with the actual input object from the res.render() method.

Now do the same for the media.hbs file and update its contents.

// views/media.hbs

    <div class='img-upload'>
      <h2>Title: {{name}}</h2>
      <img src={{img}} alt='Uploaded to server' />
      ...
    </div>
Enter fullscreen mode Exit fullscreen mode

Run the app with this command to start the server once again:

node index.js
Enter fullscreen mode Exit fullscreen mode

If you upload any image from your app, your Cloudinary media library should display the images in an uploaded folder like the screenshot below.

cloudinary console

Conclusion

This article showed you the process of using a Node/Express server, connecting it with a template engine hbs to display an uploaded image and storing these images to Cloudinary.

Further reading

💖 💪 🙅 🚩
terieyenike
oteri

Posted on March 21, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related