Serverless Image Storage & Manipulation with Netlify

Eugene Musebe

BACKGROUND

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.

Prerequisites

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.

Cloudinary setup

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 setup

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.

REACT SETUP

To get started with Create React App:

Step 1: Create React App

Navigate to the project directory of your choice and run :

1npx create-react-app aircloud

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

Step 2: Install project dependencies

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

1npm 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 :

1<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">

Step 3: Configure environment variables

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 :

1CLOUDINARY_CLOUD_NAME = xxxxx;
2CLOUDINARY_API_KEY = xxxxxxx;
3CLOUDINARY_API_SECRET = xxxxxxx;
4CLOUDINARY_UPLOAD_PRESET = xxxxxxx;
5AIRTABLE_API_KEY = xxxxxxxx;
6AIRTABLE_BASE_ID = xxxxxxxxx;
7AIRTABLE_TABLE_NAME = xxxxxxxx;

Step 4: Set up Netlify functions

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

1[build]
2 functions="functions"

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:

1require('dotenv').config();
2const Airtable = require('airtable');
3
4Airtable.configure({
5 apiKey: process.env.AIRTABLE_API_KEY,
6});
7
8const base = Airtable.base(process.env.AIRTABLE_BASE_ID);
9const table = base(process.env.AIRTABLE_TABLE_NAME);
10
11module.exports = {
12 base,
13 table,
14};

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:

1netlify dev

Step 5: Create the upload and image display components

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 :

1import './App.css';
2import Upload from './components/Upload';
3import ImageGallery from './components/ImageGallery';
4// import Title from './components/Title';
5
6function App() {
7 return (
8 <div className='container'>
9 <Upload />
10 <ImageGallery />
11 </div>
12 );
13}
14
15export default App;

Upload Files Component

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

1import React, { useState } from 'react';
2
3const Upload = () => {
4 const [imageDataUrl, setImageDataUrl] = useState('');
5
6 const handleChange = (e) => {
7 const file = e.target.files[0];
8 const reader = new FileReader();
9 reader.readAsDataURL(file);
10 reader.onloadend = () => {
11 setImageDataUrl(reader.result);
12 };
13 reader.onerror = () => {
14 console.log('error');
15 };
16 };
17
18 return (
19 <div>
20 <form onSubmit={submitHandler}>
21 <label>
22 <input type='file' onChange={handleChange} />
23 <span>+</span>
24 </label>
25
26 <button type='submit' className='button' disabled={!imageDataUrl}>
27 {' '}
28 Upload Image
29 </button>
30 </form>
31 {imageDataUrl && (
32 <img className="height-50 w-50" src={imageDataUrl} alt="aircloud_gallery" />
33 )}
34 </div>
35 </div>
36 );
37};
38
39export default Upload;

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

1const submitHandler = async (e) => {
2 e.preventDefault();
3 console.log('submitting');
4 try {
5 const res = await fetch(
6 deployed_function,
7 {
8 method: 'POST',
9 body: imageDataUrl,
10 }
11 );
12
13 const data = res.json();
14 // console.log(data);
15 setImageDataUrl('');
16 } catch (err) {
17 console.error(err);
18 }

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.

File UPLOAD SEVERLESS FUNCTION

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

1require('dotenv').config();
2const cloudinary = require('cloudinary').v2;
3
4cloudinary.config({
5 cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
6 api_key: process.env.CLOUDINARY_API_KEY,
7 api_secret: process.env.CLOUDINARY_API_SECRET,
8});
9
10const { table } = require('./utils/airtable');
11
12exports.handler = async (event) => {
13// Capture the file from the event body
14 const file = event.body;
15
16 try {
17 // Upload the file captured to cloudinary
18 const { public_id, secure_url } = await cloudinary.uploader.upload(file, {
19 upload_preset: process.env.CLOUDINARY_UPLOAD_PRESET,
20 });
21
22 console.log(public_id, secure_url);
23// Save the secure_url and public id to Airtable
24 const record = await table.create({
25 imgId: public_id,
26 url: secure_url,
27 username: 'Musebecodes',
28 });
29 return {
30 statusCode: 200,
31 body: JSON.stringify(record),
32 };
33 } catch (err) {
34 console.error(err);
35 return {
36 statusCode: 500,
37 body: JSON.stringify({ err: 'Failed to upload image' }),
38 };
39 }
40};

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.

Image Display

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 :

1const { table } = require('./utils/airtable');
2
3exports.handler = async (event) => {
4 try {
5 const records = await table.select({}).firstPage();
6 const formattedRecords = records
7 .map((record) => ({
8 id: record.id,
9 ...record.fields,
10 }))
11 .filter((record) => !!record.imgId);
12 return {
13 statusCode: 200,
14 body: JSON.stringify(formattedRecords),
15 };
16 } catch (err) {
17 return {
18 statusCode: 500,
19 body: JSON.stringify({ err: 'Failed to upload image' }),
20 };
21 }
22};

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 :

1import React, { useEffect, useState } from 'react';
2import { Image, Transformation } from 'cloudinary-react';
3
4const ImageGallery = () => {
5 const [images, setImages] = useState([]);
6 // const local_function = 'http://localhost:58665/api/getImages';
7 const deployed_function = 'https://aircloud.netlify.app/.netlify/functions/getImages';
8
9 useEffect(() => {
10 const loadImages = async () => {
11 try {
12 const res = await fetch(
13 deployed_function
14 );
15 const data = await res.json();
16 setImages(data);
17 console.log(data);
18 } catch (error) {
19 console.log(error);
20 }
21 };
22 loadImages();
23 }, []);
24
25 return (
26 <div className='image-gallery'>
27 {images.length > 0 &&
28 images.map((image) => (
29 <div className='gallery-img' key={image.id}>
30 <Image cloudName='hackit-africa' publicId={image.imgId}>
31 <Transformation width='280' height='280' crop='fit' />
32 </Image>
33 </div>
34 ))}
35 </div>
36 );
37};
38
39export default ImageGallery;

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.

Conclusion

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:

Eugene Musebe

Software Developer

I’m a full-stack software developer, content creator, and tech community builder based in Nairobi, Kenya. I am addicted to learning new technologies and loves working with like-minded people.