{"id":28227,"date":"2021-04-07T20:21:52","date_gmt":"2021-04-07T20:21:52","guid":{"rendered":"http:\/\/Serverless-Image-Storage-and-Manipulation-Using-React-Airtable-and-Netlify-functions"},"modified":"2021-04-07T20:21:52","modified_gmt":"2021-04-07T20:21:52","slug":"serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/","title":{"rendered":"Serverless Image Storage &amp; Manipulation with Netlify"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><h2><strong>BACKGROUND<\/strong><\/h2>\n<p>Serverless frameworks enable frontent developers to build full stack applications without having to operate and manage the entire application infrastructure.<\/p>\n<p>By abstracting unnecessary complexities from the development process, developers don\u2019t 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.<\/p>\n<p>In this article, we\u2019ll explore how you can leverage Netlify\u2019s serverless framework to connect Cloudinary and Airtable in order to perform image manipulation, transformation, and storage.<\/p>\n<p>The complete source code used in this tutorial is available on Codesandbox.<\/p>\n<iframe src=\"https:\/\/codesandbox.io\/embed\/aircloud-el3wv?fontsize=14&#038;hidenavigation=1&#038;theme=dark\"style=\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\" title=\"aircloud\"allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"><\/iframe>\n<h2><strong>Prerequisites<\/strong><\/h2>\n<p>While this is a beginner-friendly tutorial, you\u2019ll want to have some experience writing functional JavaScript, downloading libraries, etc.<\/p>\n<p>You will need <a href=\"https:\/\/nodejs.org\/en\/\">Node.js<\/a> and <a href=\"https:\/\/docs.netlify.com\/cli\/get-started\/\">Netlify CLI<\/a> installed on your machine. They are the core dev dependencies of this project.<\/p>\n<p><a href=\"https:\/\/cloudinary.com\/\">Cloudinary<\/a>, <a href=\"https:\/\/airtable.com\/\">Airtable<\/a> and <a href=\"https:\/\/www.netlify.com\">Netlify<\/a> are the platforms we\u2019\u2019ll be leveraging to deploy, store, and manipulate images.<\/p>\n<h2><strong>Cloudinary setup<\/strong><\/h2>\n<p>If you don\u2019t have a Cloudinary account yet, you can <a href=\"https:\/\/cloudinary.com\/signup\">sign up<\/a> for a free one.<\/p>\n<p>After verifying your account, you can login to access the required credentials.<\/p>\n<p>From the dashboard, write down your <code>cloud_name, api_key &amp; api_secret<\/code>. We\u2019ll be using them in the application.<\/p>\n<p>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 <code>aircloud<\/code> .<\/p>\n<p>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;<\/p>\n<ul>\n<li>\n<p>Click on the settings icon on the navigation bar of your Cloudinary dashboard,<\/p>\n<\/li>\n<li>\n<p>Scroll down to the upload presets section, and click on the <code>Add upload preset<\/code> link.<\/p>\n<\/li>\n<li>\n<p>Give your preset a <strong><code>name<\/code><\/strong>, and make the signing mode <strong><code>Signed<\/code><\/strong>, since we want the parameters declared with the requests we make to be considered first.<\/p>\n<\/li>\n<li>\n<p>On the <strong><code>Folder<\/code><\/strong> form, please input the name of the folder you would want your application images to be stored. In our case, we named the folder <code>aircloud<\/code>.<\/p>\n<\/li>\n<li>\n<p>Click on the save button and the setup will be complete.<\/p>\n<\/li>\n<\/ul>\n<h2><strong>Airtable setup<\/strong><\/h2>\n<p>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\u2019ll need to have a base (database), and a table\/sheet set up before you can start to programmatically interact with the database.<\/p>\n<p>You shall need the <code>apiKey<\/code>, <code>base id<\/code> and <code>table name<\/code> in order to access the base (database) from your application.\nUpon creating the table and getting the credentials your table structure should look like :<\/p>\n <iframe loading=\"lazy\" class=\"airtable-embed\" src=\"https:\/\/airtable.com\/embed\/shr3pOyZrmmHZPwEy?backgroundColor=purple&#038;viewControls=on\" frameborder=\"0\" onmousewheel=\"\" width=\"100%\" height=\"533\" style=\"background: transparent; border: 1px solid #ccc;\"><\/iframe>\n<p>A detailed step by step introduction to Airtable can be found <a href=\"https:\/\/musebecodes.dev\/airtable\">Here.<\/a><\/p>\n<p>With all the platforms above setup we can now start to build the application.<\/p>\n<h2><strong>REACT SETUP<\/strong><\/h2>\n<p>To get started with Create React App:<\/p>\n<h4><strong>Step 1: Create React App<\/strong><\/h4>\n<p>Navigate to the project directory of your choice and run :<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npx create-react-app aircloud\n<\/code><\/span><\/pre>\n<p>After the installation is done,  you will have a React application placed in the folder aircloud.<\/p>\n<h4><strong>Step 2: Install project dependencies<\/strong><\/h4>\n<p>Install the required dependencies the application will use by running the command below:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm i dotenv cloudinary-react cloudinary\n<\/code><\/span><\/pre>\n<p>When it comes to structuring the appearance of the application, we\u2019re going to be leveraging on <code>bootstrap<\/code> a Css library.\nAdd the following CDN to your <code>index.html<\/code> file found inside the <code>public<\/code> directory :<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span> <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"stylesheet\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@4.6.0\/dist\/css\/bootstrap.min.css\"<\/span> <span class=\"hljs-attr\">integrity<\/span>=<span class=\"hljs-string\">\"sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L\/Z6nronJ3oUOFUFpCjEUQouq2+l\"<\/span> <span class=\"hljs-attr\">crossorigin<\/span>=<span class=\"hljs-string\">\"anonymous\"<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h4><strong>Step 3: Configure environment variables<\/strong><\/h4>\n<p>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 :<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">CLOUDINARY_CLOUD_NAME = xxxxx;\nCLOUDINARY_API_KEY = xxxxxxx;\nCLOUDINARY_API_SECRET = xxxxxxx;\nCLOUDINARY_UPLOAD_PRESET = xxxxxxx;\nAIRTABLE_API_KEY = xxxxxxxx;\nAIRTABLE_BASE_ID = xxxxxxxxx;\nAIRTABLE_TABLE_NAME = xxxxxxxx;\n<\/code><\/span><\/pre>\n<h4><strong>Step 4: Set up Netlify functions<\/strong><\/h4>\n<p>To use the Netlify functions to manage the application, create a <code>netlify.toml<\/code> file on the root project folder, and paste the code below:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">&#91;build]\n    functions=<span class=\"hljs-string\">\"functions\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This file will define how Netlify will build and deploy your site. All the serverless functions shall be stored in the functions folder.<\/p>\n<p>Now, create a functions folder in the root project directory as defined in the <code>netlify.toml<\/code> file; this will be the home to all our severless functions.<\/p>\n<p>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 <code>airtable.js<\/code> file.<\/p>\n<p>Paste the following in it:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'dotenv'<\/span>).config();\n<span class=\"hljs-keyword\">const<\/span> Airtable = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'airtable'<\/span>);\n\nAirtable.configure({\n  <span class=\"hljs-attr\">apiKey<\/span>: process.env.AIRTABLE_API_KEY,\n});\n\n<span class=\"hljs-keyword\">const<\/span> base = Airtable.base(process.env.AIRTABLE_BASE_ID);\n<span class=\"hljs-keyword\">const<\/span> table = base(process.env.AIRTABLE_TABLE_NAME);\n\n<span class=\"hljs-built_in\">module<\/span>.exports = {\n  base,\n  table,\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This will enable us to achieve the coding principle of <code>don't repeat yourself<\/code> by reusing the file.<\/p>\n<p>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.<\/p>\n<p>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 <code>npm start<\/code> and use Netlify CLI to perform this operation.  Run the following command on your terminal:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">netlify dev\n<\/code><\/span><\/pre>\n<h4><strong>Step 5: Create the upload and image display components<\/strong><\/h4>\n<p>The application will have two major components that will be used to upload and display the images. To enable this, create a <code>components<\/code> folder inside the <code>src<\/code> directory of your react application, and add an <code>Upload.js<\/code> &amp; <code>ImageGallery.js<\/code> file.<\/p>\n<p>Now, navigate back to the <code>App.js<\/code> component, and paste the following code :<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'.\/App.css'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Upload <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/components\/Upload'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> ImageGallery <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/components\/ImageGallery'<\/span>;\n<span class=\"hljs-comment\">\/\/  import Title from '.\/components\/Title';<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">App<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">'container'<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Upload<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ImageGallery<\/span> \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> App;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3><strong>Upload Files Component<\/strong><\/h3>\n<p>We can now build the <code>Upload.js<\/code> component that will allow the upload and storage of images.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">import React, { useState } from 'react';\n\nconst Upload = () =&gt; {\n  const &#91;imageDataUrl, setImageDataUrl] = useState('');\n\n  const handleChange = (e) =&gt; {\n    const file = e.target.files&#91;0];\n    const reader = new FileReader();\n    reader.readAsDataURL(file);\n    reader.onloadend = () =&gt; {\n      setImageDataUrl(reader.result);\n    };\n    reader.onerror = () =&gt; {\n      console.log('error');\n    };\n  };\n\n  return (\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{submitHandler}<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">'file'<\/span> <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{handleChange}<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span>&gt;<\/span>+<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">'submit'<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">'button'<\/span> <span class=\"hljs-attr\">disabled<\/span>=<span class=\"hljs-string\">{!imageDataUrl}<\/span>&gt;<\/span>\n          {' '}\n          Upload Image\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n      {imageDataUrl &amp;&amp; (\n       <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"height-50 w-50\"<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{imageDataUrl}<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"aircloud_gallery\"<\/span> \/&gt;<\/span>\n      )}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n  );\n};\n\nexport default Upload;\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Using the useState hook in the component will enable you track changes when uploading images.\nThe handleChange function capture the first file selected, converts the file into a string and sets the application state to have that Url by using <code>setImageDataUrl<\/code>\nWe then create a form inside the return statement that will utilize the <code>handleChange<\/code> function when triggered.<\/p>\n<p>The last piece to make the component complete is to create a <code>submitHandler<\/code> which will be responsible for uploading files<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"> <span class=\"hljs-keyword\">const<\/span> submitHandler = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n    e.preventDefault();\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'submitting'<\/span>);\n    <span class=\"hljs-keyword\">try<\/span> {\n      <span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> fetch(\n        deployed_function,\n        {\n          <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">'POST'<\/span>,\n          <span class=\"hljs-attr\">body<\/span>: imageDataUrl,\n        }\n      );\n\n      <span class=\"hljs-keyword\">const<\/span> data = res.json();\n      <span class=\"hljs-comment\">\/\/ console.log(data);<\/span>\n      setImageDataUrl(<span class=\"hljs-string\">''<\/span>);\n    } <span class=\"hljs-keyword\">catch<\/span> (err) {\n      <span class=\"hljs-built_in\">console<\/span>.error(err);\n    }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>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.<\/p>\n<h2><strong>File UPLOAD SEVERLESS FUNCTION<\/strong><\/h2>\n<p>Inside the functions folder, create an <code>upload.js<\/code> file that will hold all the upload logic, and add the following:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">\n<span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'dotenv'<\/span>).config();\n<span class=\"hljs-keyword\">const<\/span> cloudinary = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'cloudinary'<\/span>).v2;\n\ncloudinary.config({\n  <span class=\"hljs-attr\">cloud_name<\/span>: process.env.CLOUDINARY_CLOUD_NAME,\n  <span class=\"hljs-attr\">api_key<\/span>: process.env.CLOUDINARY_API_KEY,\n  <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_API_SECRET,\n});\n\n<span class=\"hljs-keyword\">const<\/span> { table } = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'.\/utils\/airtable'<\/span>);\n\nexports.handler = <span class=\"hljs-keyword\">async<\/span> (event) =&gt; {\n<span class=\"hljs-comment\">\/\/ Capture the file from the event body<\/span>\n  <span class=\"hljs-keyword\">const<\/span> file = event.body;\n\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-comment\">\/\/ Upload the file captured to cloudinary<\/span>\n    <span class=\"hljs-keyword\">const<\/span> { public_id, secure_url } = <span class=\"hljs-keyword\">await<\/span> cloudinary.uploader.upload(file, {\n      <span class=\"hljs-attr\">upload_preset<\/span>: process.env.CLOUDINARY_UPLOAD_PRESET,\n    });\n\n    <span class=\"hljs-built_in\">console<\/span>.log(public_id, secure_url);\n<span class=\"hljs-comment\">\/\/ Save the secure_url and public id to Airtable<\/span>\n    <span class=\"hljs-keyword\">const<\/span> record = <span class=\"hljs-keyword\">await<\/span> table.create({\n      <span class=\"hljs-attr\">imgId<\/span>: public_id,\n      <span class=\"hljs-attr\">url<\/span>: secure_url,\n      <span class=\"hljs-attr\">username<\/span>: <span class=\"hljs-string\">'Musebecodes'<\/span>,\n    });\n    <span class=\"hljs-keyword\">return<\/span> {\n      <span class=\"hljs-attr\">statusCode<\/span>: <span class=\"hljs-number\">200<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify(record),\n    };\n  } <span class=\"hljs-keyword\">catch<\/span> (err) {\n    <span class=\"hljs-built_in\">console<\/span>.error(err);\n    <span class=\"hljs-keyword\">return<\/span> {\n      <span class=\"hljs-attr\">statusCode<\/span>: <span class=\"hljs-number\">500<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ <span class=\"hljs-attr\">err<\/span>: <span class=\"hljs-string\">'Failed to upload image'<\/span> }),\n    };\n  }\n};\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>As a result of the preceding, we bring in environment variables by using the <code>dotenv<\/code> module, which is used to configure access to Cloudinary. We also bring Airtable into the file for storage of the image parameters.<\/p>\n<p>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 <code>aircloud<\/code> folder we earlier created. This is made possible by utilizing the upload preset.<\/p>\n<p>After the upload to Cloudinary, We store the <code>public_id<\/code> and <code>secure_url<\/code> given to us into Airtable by using the <code>table.create<\/code> method.<\/p>\n<h2><strong>Image Display<\/strong><\/h2>\n<p>To display the stored images, we first need to create a severless function that will <code>GET <\/code>all the data stored on the Airtable database. This can be achieved by creating a creating a <code>getImages.js<\/code> file inside the functions folder and adding the following :<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> { table } = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'.\/utils\/airtable'<\/span>);\n\nexports.handler = <span class=\"hljs-keyword\">async<\/span> (event) =&gt; {\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> records = <span class=\"hljs-keyword\">await<\/span> table.select({}).firstPage();\n    <span class=\"hljs-keyword\">const<\/span> formattedRecords = records\n      .map(<span class=\"hljs-function\">(<span class=\"hljs-params\">record<\/span>) =&gt;<\/span> ({\n        <span class=\"hljs-attr\">id<\/span>: record.id,\n        ...record.fields,\n      }))\n      .filter(<span class=\"hljs-function\">(<span class=\"hljs-params\">record<\/span>) =&gt;<\/span> !!record.imgId);\n    <span class=\"hljs-keyword\">return<\/span> {\n      <span class=\"hljs-attr\">statusCode<\/span>: <span class=\"hljs-number\">200<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify(formattedRecords),\n    };\n  } <span class=\"hljs-keyword\">catch<\/span> (err) {\n    <span class=\"hljs-keyword\">return<\/span> {\n      <span class=\"hljs-attr\">statusCode<\/span>: <span class=\"hljs-number\">500<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ <span class=\"hljs-attr\">err<\/span>: <span class=\"hljs-string\">'Failed to upload image'<\/span> }),\n    };\n  }\n};\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>From the above code, we first import <code>table<\/code> from the utils folder in order to access airtable, fetched, and formatted the json data returned.<\/p>\n<p>With all the above done, it\u2019s time to display the uploaded images. Create an <code>imageGallery.js<\/code> component, and include the following :<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> React, { useEffect, useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Image, Transformation } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'cloudinary-react'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> ImageGallery = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> &#91;images, setImages] = useState(&#91;]);\n  <span class=\"hljs-comment\">\/\/ const local_function = 'http:\/\/localhost:58665\/api\/getImages';<\/span>\n  <span class=\"hljs-keyword\">const<\/span> deployed_function = <span class=\"hljs-string\">'https:\/\/aircloud.netlify.app\/.netlify\/functions\/getImages'<\/span>;\n\n  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> loadImages = <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n      <span class=\"hljs-keyword\">try<\/span> {\n        <span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> fetch(\n          deployed_function\n        );\n        <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> res.json();\n        setImages(data);\n        <span class=\"hljs-built_in\">console<\/span>.log(data);\n      } <span class=\"hljs-keyword\">catch<\/span> (error) {\n        <span class=\"hljs-built_in\">console<\/span>.log(error);\n      }\n    };\n    loadImages();\n  }, &#91;]);\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">'image-gallery'<\/span>&gt;<\/span>\n      {images.length &gt; 0 &amp;&amp;\n        images.map((image) =&gt; (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">'gallery-img'<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{image.id}<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span> <span class=\"hljs-attr\">cloudName<\/span>=<span class=\"hljs-string\">'hackit-africa'<\/span> <span class=\"hljs-attr\">publicId<\/span>=<span class=\"hljs-string\">{image.imgId}<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Transformation<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">'280'<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">'280'<\/span> <span class=\"hljs-attr\">crop<\/span>=<span class=\"hljs-string\">'fit'<\/span> \/&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Image<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        ))}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> ImageGallery;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>By using <code>useEffect<\/code>, we have the ability to fetch all the stored images and display them to the user by using the <a href=\"https:\/\/www.npmjs.com\/package\/cloudinary-react\">cloudinary-react<\/a> library,.which enables us to perform image resizing and cropping to attain uniformity on all the images uploaded.<\/p>\n<h2><strong>Conclusion<\/strong><\/h2>\n<p>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.<\/p>\n<p>For more information visit:<\/p>\n<ul>\n<li>\n<a href=\"https:\/\/training.cloudinary.com\">Digital Asset Management Training<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/musebecodes.dev\/airtable\">Introduction to Airtable<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/dev.to\/ekafyi\/getting-started-with-netlify-functions-part-1-zero-config-setup-and-writing-our-first-functions-1i5b\">Getting Started with Netlify Functions<\/a>\n<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28228,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,370,175,385,246,377,371],"class_list":["post-28227","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-image","tag-jamstack","tag-netlify","tag-react","tag-resizing","tag-under-review"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Serverless Image Storage &amp; Manipulation with Netlify<\/title>\n<meta name=\"description\" content=\"Serverless Image Storage &amp; Manipulation Using React, Airtable and Netlify functions\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Serverless Image Storage &amp; Manipulation with Netlify\" \/>\n<meta property=\"og:description\" content=\"Serverless Image Storage &amp; Manipulation Using React, Airtable and Netlify functions\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2021-04-07T20:21:52+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925080\/Web_Assets\/blog\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65.png?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"992\" \/>\n\t<meta property=\"og:image:height\" content=\"504\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Serverless Image Storage &amp; Manipulation with Netlify\",\"datePublished\":\"2021-04-07T20:21:52+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/\"},\"wordCount\":7,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925080\/Web_Assets\/blog\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65.png?_i=AA\",\"keywords\":[\"Guest Post\",\"Image\",\"JAMStack\",\"Netlify\",\"React\",\"Resizing\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2021\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/\",\"name\":\"Serverless Image Storage &amp; Manipulation with Netlify\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925080\/Web_Assets\/blog\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65.png?_i=AA\",\"datePublished\":\"2021-04-07T20:21:52+00:00\",\"description\":\"Serverless Image Storage & Manipulation Using React, Airtable and Netlify functions\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925080\/Web_Assets\/blog\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925080\/Web_Assets\/blog\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65.png?_i=AA\",\"width\":992,\"height\":504},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Serverless Image Storage &amp; Manipulation with Netlify\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Serverless Image Storage &amp; Manipulation with Netlify","description":"Serverless Image Storage & Manipulation Using React, Airtable and Netlify functions","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/","og_locale":"en_US","og_type":"article","og_title":"Serverless Image Storage &amp; Manipulation with Netlify","og_description":"Serverless Image Storage & Manipulation Using React, Airtable and Netlify functions","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/","og_site_name":"Cloudinary Blog","article_published_time":"2021-04-07T20:21:52+00:00","og_image":[{"width":992,"height":504,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925080\/Web_Assets\/blog\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65.png?_i=AA","type":"image\/png"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/"},"author":{"name":"","@id":""},"headline":"Serverless Image Storage &amp; Manipulation with Netlify","datePublished":"2021-04-07T20:21:52+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/"},"wordCount":7,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925080\/Web_Assets\/blog\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65.png?_i=AA","keywords":["Guest Post","Image","JAMStack","Netlify","React","Resizing","Under Review"],"inLanguage":"en-US","copyrightYear":"2021","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/","name":"Serverless Image Storage &amp; Manipulation with Netlify","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925080\/Web_Assets\/blog\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65.png?_i=AA","datePublished":"2021-04-07T20:21:52+00:00","description":"Serverless Image Storage & Manipulation Using React, Airtable and Netlify functions","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925080\/Web_Assets\/blog\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925080\/Web_Assets\/blog\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65.png?_i=AA","width":992,"height":504},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/serverless-image-storage-and-manipulation-using-react-airtable-and-netlify-functions\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Serverless Image Storage &amp; Manipulation with Netlify"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925080\/Web_Assets\/blog\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65\/a36f4eebc2a3f3b4a62aaf7d7df129a19a125c13-992x504-1_2822889b65.png?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28227","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28227"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28227\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28228"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28227"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28227"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28227"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}