Skip to content

Serverless Image Storage & Manipulation with Netlify

Serverless frameworks enable frontent developers to build full stack applications without having to operate and manage the entire application infrastructure.

By abstracting unnecessary complexities from the development process, developers don’t need to manually implement backend tasks like scaling server, provisioning capacity, and resources. Instead, the coding logic embedded in serverless frameworks such as Netlify Functions, automates much of the back-end legwork.

In this article, we’ll explore how you can leverage Netlify’s serverless framework to connect Cloudinary and Airtable in order to perform image manipulation, transformation, and storage.

The complete source code used in this tutorial is available on Codesandbox.

While this is a beginner-friendly tutorial, you’ll want to have some experience writing functional JavaScript, downloading libraries, etc.

You will need Node.js and Netlify CLI installed on your machine. They are the core dev dependencies of this project.

Cloudinary, Airtable and Netlify are the platforms we’’ll be leveraging to deploy, store, and manipulate images.

If you don’t have a Cloudinary account yet, you can sign up for a free one.

After verifying your account, you can login to access the required credentials.

From the dashboard, write down your cloud_name, api_key & api_secret. We’ll be using them in the application.

The next step is to create a folder where all the application images will be stored. Click on the Media library tab, and create a folder. Name it aircloud .

A great way to manipulate images as we upload them to Cloudinary is by using upload presets. This will enable one to pass one or more upload parameters defined in the cloudinary documentation. In this application, we will create an upload preset to resize images. To do this;

  • Click on the settings icon on the navigation bar of your Cloudinary dashboard,

  • Scroll down to the upload presets section, and click on the Add upload preset link.

  • Give your preset a name, and make the signing mode Signed, since we want the parameters declared with the requests we make to be considered first.

  • On the Folder form, please input the name of the folder you would want your application images to be stored. In our case, we named the folder aircloud.

  • Click on the save button and the setup will be complete.

Airtable is an easy-to-use online platform for creating and sharing relational databases. This is where we will store the deployed images, IDs, and Urls, in order to access them on our application. After creating an account on the platform, you’ll need to have a base (database), and a table/sheet set up before you can start to programmatically interact with the database.

You shall need the apiKey, base id and table name in order to access the base (database) from your application. Upon creating the table and getting the credentials your table structure should look like :

A detailed step by step introduction to Airtable can be found Here.

With all the platforms above setup we can now start to build the application.

To get started with Create React App:

Navigate to the project directory of your choice and run :

npx create-react-app aircloud

After the installation is done, you will have a React application placed in the folder aircloud.

Install the required dependencies the application will use by running the command below:

npm i dotenv cloudinary-react cloudinary

When it comes to structuring the appearance of the application, we’re going to be leveraging on bootstrap a Css library. Add the following CDN to your index.html file found inside the public directory :

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
Code language: HTML, XML (xml)

After you setup the different platforms, and acquire the credentials, create an .env file on the root of your folder, and add the respective values :

CLOUDINARY_CLOUD_NAME = xxxxx;
CLOUDINARY_API_KEY = xxxxxxx;
CLOUDINARY_API_SECRET = xxxxxxx;
CLOUDINARY_UPLOAD_PRESET = xxxxxxx;
AIRTABLE_API_KEY = xxxxxxxx;
AIRTABLE_BASE_ID = xxxxxxxxx;
AIRTABLE_TABLE_NAME = xxxxxxxx;

To use the Netlify functions to manage the application, create a netlify.toml file on the root project folder, and paste the code below:

[build]
    functions="functions"
Code language: JavaScript (javascript)

This file will define how Netlify will build and deploy your site. All the serverless functions shall be stored in the functions folder.

Now, create a functions folder in the root project directory as defined in the netlify.toml file; this will be the home to all our severless functions.

In order to interact with Airtable within our application without having to repeat the same code everytime in different files, create a utils folder inside the functions folder, and add an airtable.js file.

Paste the following in it:

require('dotenv').config();
const Airtable = require('airtable');

Airtable.configure({
  apiKey: process.env.AIRTABLE_API_KEY,
});

const base = Airtable.base(process.env.AIRTABLE_BASE_ID);
const table = base(process.env.AIRTABLE_TABLE_NAME);

module.exports = {
  base,
  table,
};
Code language: JavaScript (javascript)

This will enable us to achieve the coding principle of don't repeat yourself by reusing the file.

From the code above, I imported the dotenv package in order to access the environment variables stored in the .env file. I then initialized and configured the Airtable library, which enabled me to create and export variables that enable one to access the base and table globally within the application.

With all of the above setup and configurations, its time to spin up the server. Since we will be leveraging on cloud functions, replace the default React start command npm start and use Netlify CLI to perform this operation. Run the following command on your terminal:

netlify dev

The application will have two major components that will be used to upload and display the images. To enable this, create a components folder inside the src directory of your react application, and add an Upload.js & ImageGallery.js file.

Now, navigate back to the App.js component, and paste the following code :


import './App.css';
import Upload from './components/Upload';
import ImageGallery from './components/ImageGallery';
//  import Title from './components/Title';

function App() {
  return (
    <div className='container'>
      <Upload />
      <ImageGallery />
    </div>
  );
}

export default App;
Code language: JavaScript (javascript)

We can now build the Upload.js component that will allow the upload and storage of images.

import React, { useState } from 'react';

const Upload = () => {
  const [imageDataUrl, setImageDataUrl] = useState('');

  const handleChange = (e) => {
    const file = e.target.files[0];
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onloadend = () => {
      setImageDataUrl(reader.result);
    };
    reader.onerror = () => {
      console.log('error');
    };
  };

  return (
    <div>
    <form onSubmit={submitHandler}>
        <label>
          <input type='file' onChange={handleChange} />
          <span>+</span>
        </label>

        <button type='submit' className='button' disabled={!imageDataUrl}>
          {' '}
          Upload Image
        </button>
      </form>
      {imageDataUrl && (
       <img className="height-50 w-50" src={imageDataUrl} alt="aircloud_gallery" />
      )}
    </div>
    </div>
  );
};

export default Upload;

Code language: HTML, XML (xml)

Using the useState hook in the component will enable you track changes when uploading images. The handleChange function capture the first file selected, converts the file into a string and sets the application state to have that Url by using setImageDataUrl We then create a form inside the return statement that will utilize the handleChange function when triggered.

The last piece to make the component complete is to create a submitHandler which will be responsible for uploading files

 const submitHandler = async (e) => {
    e.preventDefault();
    console.log('submitting');
    try {
      const res = await fetch(
        deployed_function,
        {
          method: 'POST',
          body: imageDataUrl,
        }
      );

      const data = res.json();
      // console.log(data);
      setImageDataUrl('');
    } catch (err) {
      console.error(err);
    }
Code language: JavaScript (javascript)

The preventDefault() method is used to prevent the upload button from submitting the form, after which we made a post request to the severless api endpoint that we will create to store the image to Cloudinary, and the url to Airtable.

Inside the functions folder, create an upload.js file that will hold all the upload logic, and add the following:


require('dotenv').config();
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,
});

const { table } = require('./utils/airtable');

exports.handler = async (event) => {
// Capture the file from the event body
  const file = event.body;

  try {
    // Upload the file captured to cloudinary
    const { public_id, secure_url } = await cloudinary.uploader.upload(file, {
      upload_preset: process.env.CLOUDINARY_UPLOAD_PRESET,
    });

    console.log(public_id, secure_url);
// Save the secure_url and public id to Airtable
    const record = await table.create({
      imgId: public_id,
      url: secure_url,
      username: 'Musebecodes',
    });
    return {
      statusCode: 200,
      body: JSON.stringify(record),
    };
  } catch (err) {
    console.error(err);
    return {
      statusCode: 500,
      body: JSON.stringify({ err: 'Failed to upload image' }),
    };
  }
};

Code language: JavaScript (javascript)

As a result of the preceding, we bring in environment variables by using the dotenv module, which is used to configure access to Cloudinary. We also bring Airtable into the file for storage of the image parameters.

Then, we created an asynchronous handler function that listens to the event body and captures a file when an upload is initiated. This then takes the upload, and stores it in Cloudinary inside the aircloud folder we earlier created. This is made possible by utilizing the upload preset.

After the upload to Cloudinary, We store the public_id and secure_url given to us into Airtable by using the table.create method.

To display the stored images, we first need to create a severless function that will GET all the data stored on the Airtable database. This can be achieved by creating a creating a getImages.js file inside the functions folder and adding the following :

const { table } = require('./utils/airtable');

exports.handler = async (event) => {
  try {
    const records = await table.select({}).firstPage();
    const formattedRecords = records
      .map((record) => ({
        id: record.id,
        ...record.fields,
      }))
      .filter((record) => !!record.imgId);
    return {
      statusCode: 200,
      body: JSON.stringify(formattedRecords),
    };
  } catch (err) {
    return {
      statusCode: 500,
      body: JSON.stringify({ err: 'Failed to upload image' }),
    };
  }
};

Code language: JavaScript (javascript)

From the above code, we first import table from the utils folder in order to access airtable, fetched, and formatted the json data returned.

With all the above done, it’s time to display the uploaded images. Create an imageGallery.js component, and include the following :

import React, { useEffect, useState } from 'react';
import { Image, Transformation } from 'cloudinary-react';

const ImageGallery = () => {
  const [images, setImages] = useState([]);
  // const local_function = 'http://localhost:58665/api/getImages';
  const deployed_function = 'https://aircloud.netlify.app/.netlify/functions/getImages';

  useEffect(() => {
    const loadImages = async () => {
      try {
        const res = await fetch(
          deployed_function
        );
        const data = await res.json();
        setImages(data);
        console.log(data);
      } catch (error) {
        console.log(error);
      }
    };
    loadImages();
  }, []);

  return (
    <div className='image-gallery'>
      {images.length > 0 &&
        images.map((image) => (
          <div className='gallery-img' key={image.id}>
            <Image cloudName='hackit-africa' publicId={image.imgId}>
              <Transformation width='280' height='280' crop='fit' />
            </Image>
          </div>
        ))}
    </div>
  );
};

export default ImageGallery;
Code language: JavaScript (javascript)

By using useEffect, we have the ability to fetch all the stored images and display them to the user by using the cloudinary-react library,.which enables us to perform image resizing and cropping to attain uniformity on all the images uploaded.

You have successfully built a severless application leveraging on Airtable, Cloudinary and Netlify functions. I hope you have enjoyed this tutorial. Feel free to share your thoughts.

For more information visit:

Back to top

Featured Post