How to upload images to Cloudinary using Express and Template Engines
Tags: programming, tutorial, javascript, node
Published: 2023-03-21 21:01:26

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](https://github.com/Terieyenike/cloudinary-upload) and try the [live demo](https://uploader-qo96.onrender.com/).

The final app will look something like the following:


![final app preview](https://paper-attachments.dropboxusercontent.com/s_DF51205B1B1DD26A29AB9205A4C79FCD89DC298D73EA9DF015261BE234BD069C_1679392164869_uploader.gif)



## Prerequisites

The following requirements are needed:


- Cloudinary account. Sign up for a [free](https://cloudinary.com/users/register_free) account
- Knowledge of JavaScript and Node.js
- Have Node [installed](https://nodejs.org/en) 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](https://expressjs.com/en/resources/template-engines.html) 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

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


    npm init -y

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


- `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
```

```
 > views
        index.hbs
        layout.hbs
        media.hbs
```

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

```hbs
    // 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>
```

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

```hbs
    // 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>
```

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.

```hbs
// 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>
```

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:

```css
// 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%;
    }
```

## 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:

```js
// 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}`);
    });
```

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:

```bash
// package.json
    
    {
      ...
      "scripts": {
        "start": "node index.js",
      },
      ...
    }
```

Run this command:


    node index.js

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


![the / endpoint](https://paper-attachments.dropboxusercontent.com/s_DF51205B1B1DD26A29AB9205A4C79FCD89DC298D73EA9DF015261BE234BD069C_1679406264367_image.png)


**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:

```bash
 // .env
    
    CLOUDINARY_CLOUD_NAME=<cloud-name>
    CLOUDINARY_API_KEY=<api-key>
    CLOUDINARY_API_SECRET=<api-secret>
```

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


![cloudinary console](https://paper-attachments.dropboxusercontent.com/s_DF51205B1B1DD26A29AB9205A4C79FCD89DC298D73EA9DF015261BE234BD069C_1679408071405_Screenshot+2023-03-21+at+15.12.23.jpg)

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

```js
// 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;
```

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:

```js
// 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");
      }
    });
    
    ...
```

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.

```hbs
// views/index.hbs
    
    <div class='section'>
      <h1>{{title}}</h1>
      ...
    </div>
```

Replace the content within the **<h1>** 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.

```hbs
// views/media.hbs
    
    <div class='img-upload'>
      <h2>Title: {{name}}</h2>
      <img src={{img}} alt='Uploaded to server' />
      ...
    </div>
```

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


    node index.js

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](https://paper-attachments.dropboxusercontent.com/s_DF51205B1B1DD26A29AB9205A4C79FCD89DC298D73EA9DF015261BE234BD069C_1679357753414_image.png)

## 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
- [Template engines](https://expressjs.com/en/resources/template-engines.html)
- [Cloudinary documentation](https://cloudinary.com/documentation)
- [Cloudinary Node.js SDK](https://cloudinary.com/documentation/node_integration)