DEV Community

Cover image for Building a Photo Uploader: A Practical Introduction to the JAMSTACK with Vue.js
Jekayin-Oluwa Olabemiwo for Hackmamba

Posted on

Building a Photo Uploader: A Practical Introduction to the JAMSTACK with Vue.js

Introduction

JAMSTACK is a JavaScript-powered stack that enables you to harness the powers of JavaScript, APIs, and markups. You can build with the JAMSTACK by using the various JavaScript frameworks and libraries and integrate with serverless functions or APIs. It is fast, lean and inexpensive.

In this tutorial, you will learn how to build JAMSTACK apps with a feature to upload images. You will use Cloudinary to power the upload functionality and Auth0 for authentication. This can come in handy when building photo albums, e-commerce applications, and business websites.

For a brief introduction to Vue.js, you can check out this article, Getting Started with Vue JS: The Progressive JavaScript Framework.

Prerequisites

  1. A machine with Node.js and npm) installed.

  2. Basic familiarity with JavaScript, and ES6 and Vue.js).

  3. A text editor. E.g. Sublime Text, Visual Studio Code, Atom, etc.

  4. A Cloudinary account. You can sign up for a Cloudinary account for free.

  5. An Auth0 account. You may create a free Auth0 account here.

Install the Project Dependencies

You need the Node.js runtime and the npm to use Vue.js. The Node package manager, npm will enable you to use Node.js on the command-line interface, CLI. If you do not have Node.js or npm installed, you can follow the instructions here to install them on your machine.

Another tool that you need to follow in this tutorial is the Vue CLI package. The vue library will provide the capabilities of the Vue.js framework while vue-cli will enable you to use certain terminal commands to interact with Vue.js and your project.

You can go ahead to install Vue and the vue-cli tool if you have Node.js and npm on your machine already. Use the following terminal commands to install the two packages respectively.

npm install vue
npm install -g @vue/cli
Enter fullscreen mode Exit fullscreen mode

In the next section, you will set up your Vue.js project.

Create Vue Project

Use the following vue-cli command to set up a Vue.js project on your terminal. You may name the project vue-photo-uploader.

vue create vue-photo-uploader
Enter fullscreen mode Exit fullscreen mode

Select Vue.js 2 version in the options prompted and allow Vue to scaffold the project folders and files for you. You should get a message that looks like the following in your terminal.


📄 Generating README.md...

🎉 Successfully created project vue-photo-uploader.
👉 Get started with the following commands:

Enter fullscreen mode Exit fullscreen mode

Now, you need to set up the new project to handle file uploads. This is the purpose of the next section of the tutorial.

Configue the Vue.js Project for File Uploads

To handle file uploads, you will need to create an interface for your users to be able to upload files. To do that, modify the App.vue file of your project as follows:

<template>
  <div id="app">
    <div>
      <img alt="Vue logo" src="./assets/logo.png">
      <HelloWorld msg="Learn how to upload photos with Cloudinary"/>
    </div>

    <div>
        <div>
            <button @click="openUploadModel">Add Photo</button>
        </div>
    </div>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

In the above code, you edited the message to be displayed by the HelloWorld component. You changed it to learn how to upload photos with Cloudinary. In addition, you created a button that calls the openUploadModel method. The method will open the Cloudinary upload modal when the button is clicked. Now, add the method inside the part of the <code>App.vue</code> file as follows:<br> </p> <div class="highlight"><pre class="highlight javascript"><code><span class="o">&lt;</span><span class="nx">script</span><span class="o">&gt;</span> <span class="k">import</span> <span class="nx">HelloWorld</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/HelloWorld.vue</span><span class="dl">'</span> <span class="k">export</span> <span class="k">default</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">App</span><span class="dl">'</span><span class="p">,</span> <span class="na">components</span><span class="p">:</span> <span class="p">{</span> <span class="nx">HelloWorld</span><span class="p">,</span> <span class="p">},</span> <span class="na">methods</span><span class="p">:</span> <span class="p">{</span> <span class="nf">openUploadModel</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span> <span class="p">}</span> <span class="o">&lt;</span><span class="sr">/script</span><span class="err">&gt; </span></code></pre></div> <p></p> <p>The full code of the <code>App.vue</code> file becomes:<br> </p> <div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;template&gt;</span> <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"app"</span><span class="nt">&gt;</span> <span class="nt">&lt;div&gt;</span> <span class="nt">&lt;img</span> <span class="na">alt=</span><span class="s">"Vue logo"</span> <span class="na">src=</span><span class="s">"./assets/logo.png"</span><span class="nt">&gt;</span> <span class="nt">&lt;HelloWorld</span> <span class="na">msg=</span><span class="s">"Learn how to upload photos with Cloudinary"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;div&gt;</span> <span class="nt">&lt;div&gt;</span> <span class="nt">&lt;button</span> <span class="err">@</span><span class="na">click=</span><span class="s">"openUploadModel"</span><span class="nt">&gt;</span>Add Photo<span class="nt">&lt;/button&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/template&gt;</span> <span class="nt">&lt;script&gt;</span> <span class="k">import</span> <span class="nx">HelloWorld</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/HelloWorld.vue</span><span class="dl">'</span> <span class="k">export</span> <span class="k">default</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">App</span><span class="dl">'</span><span class="p">,</span> <span class="na">components</span><span class="p">:</span> <span class="p">{</span> <span class="nx">HelloWorld</span><span class="p">,</span> <span class="p">},</span> <span class="na">methods</span><span class="p">:</span> <span class="p">{</span> <span class="nf">openUploadModal</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span> <span class="p">}</span> <span class="nt">&lt;/script&gt;</span> </code></pre></div> <p></p> <h2> <a name="configure-cloudinary-upload-widget" href="#configure-cloudinary-upload-widget" class="anchor"> </a> Configure Cloudinary Upload Widget </h2> <p><strong>Login and obtain parameters</strong></p> <p>Login to your Cloudinary account and go to your <a href="https://cloudinary.com/console">console dashboard</a> and get your cloud name. Also, get your upload preset from your Cloudinary <a href="https://cloudinary.com/console/settings/upload">acount settings</a>. Copy the two values somewhere because you will need them in your Vue.js project.</p> <p><strong>Configure Upload Preset settings</strong></p> <p>In your account settings page, scroll down the <a href="https://cloudinary.com/console/settings/upload">Upload Settings tab</a> to the Upload Preset section as shown below.</p> <p><img src="https://paper-attachments.dropbox.com/s_AFF2FF088F20EBD06640F4218CCE5DBD4255763E00DF0ED2933F2E3D5F60B001_1632957465326_4.+Scroll+down+to+Upload+Preset.png" alt="Upload preset settings on Cloudinary dashboard"></p> <p>Activate the unsigned uploading feature by clicking the <strong>Enable unsigned uploading</strong> link. The link should change to <strong>Signed</strong> as shown below.</p> <p><img src="https://paper-attachments.dropbox.com/s_AFF2FF088F20EBD06640F4218CCE5DBD4255763E00DF0ED2933F2E3D5F60B001_1632957494073_4b.+Enable+unSigned+uploading.png" alt="Unsigned uploading feature enabled"></p> <p>If you don&#39;t enable unsigned upload preset, you might get the following error.<br> </p> <div class="highlight"><pre class="highlight shell"><code>Upload preset must be whitelisted <span class="k">for </span>unsigned uploads </code></pre></div> <p></p> <p>Next, navigate to the <code>index.html</code> page in the <code>public</code> folder of your project. Then, include the Cloudinary widget script inside the <body> tag.<br> </p> <div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;body&gt;</span> <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://widget.cloudinary.com/v2.0/global/all.js"</span> <span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">&gt;&lt;/script&gt;</span> ... <span class="nt">&lt;/body&gt;</span> </code></pre></div> <p></p> <p>Next, modify the <code>openUploadModal</code> of the <code>App.vue</code> file as follows:<br> </p> <div class="highlight"><pre class="highlight javascript"><code> <span class="p">...</span> <span class="nf">openUploadModal</span><span class="p">()</span> <span class="p">{</span> <span class="nb">window</span><span class="p">.</span><span class="nx">cloudinary</span><span class="p">.</span><span class="nf">openUploadWidget</span><span class="p">(</span> <span class="p">{</span> <span class="na">cloud_name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">&lt;CLOUD_NAME&gt;</span><span class="dl">'</span><span class="p">,</span> <span class="na">upload_preset</span><span class="p">:</span> <span class="dl">'</span><span class="s1">&lt;UPLOAD_PRESET&gt;</span><span class="dl">'</span> <span class="p">},</span> <span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">result</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">error</span> <span class="o">&amp;&amp;</span> <span class="nx">result</span> <span class="o">&amp;&amp;</span> <span class="nx">result</span><span class="p">.</span><span class="nx">event</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">success</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Image uploaded..: </span><span class="dl">'</span><span class="p">,</span> <span class="nx">result</span><span class="p">.</span><span class="nx">info</span><span class="p">);</span> <span class="p">}</span> <span class="p">}).</span><span class="nf">open</span><span class="p">();</span> <span class="p">}</span> </code></pre></div> <p></p> <p>In the above code, you added the logic of the <code>@click</code> event of the button by which calls the <code>openUploadWidget</code> method that activates the Cloudinary file upload modal.</p> <p>Replace <code>CLOUD_NAME</code> and <code>UPLOAD_PRESET</code> in the above code with the values you got from your Cloudinary account.</p> <p>Next, run the project on your terminal:<br> </p> <div class="highlight"><pre class="highlight shell"><code>npm run serve </code></pre></div> <p></p> <p>Then, navigate to <code>127.0.0.1:8000</code> on your browser to add images to the application. You should get a page that looks like the following image.</p> <p><img src="https://paper-attachments.dropbox.com/s_AFF2FF088F20EBD06640F4218CCE5DBD4255763E00DF0ED2933F2E3D5F60B001_1632957517736_1.+Ready-to-add-photo.png" alt="Photo album prototype live!"></p> <p>Click the <code>Add Photo</code> button and the upload modal will be displayed like the following.</p> <p><img src="https://paper-attachments.dropbox.com/s_AFF2FF088F20EBD06640F4218CCE5DBD4255763E00DF0ED2933F2E3D5F60B001_1632957529258_2.+Upload+Modal+Widget.png" alt="Cloudinary upload widget"></p> <p>Now, you can upload your images.</p> <h2> <a name="deliver-and-view-images-with-cloudinary" href="#deliver-and-view-images-with-cloudinary" class="anchor"> </a> Deliver and View Images with Cloudinary </h2> <p>Cloudinary enables the delivery of images to the browser via its Vue.js SDK, <code>cloudinary-vue</code>. Install the library to get started:<br> </p> <div class="highlight"><pre class="highlight shell"><code>npm <span class="nb">install </span>cloudinary-vue </code></pre></div> <p></p> <p>You need the following Cloudinary Vue.js components to achieve the goal of this section of the article.</p> <p><code>CldContext</code> - this tag helps you to set the parameters that all the child components share.<br> <code>CldImage</code> - the Cloudinary Image tag. It sources images from Cloudinary and makes them available on the browser.<br> <code>CldTransformation</code> - the tag that allows you to describe the transformations that are applied to the images delivered.</p> <p>Next, import the components in the <code>&lt;script&gt;</code> section of the <code>App.vue</code> file. In addition, add the image <code>url</code> and <code>public_id</code> to the Data property of Vue.js. These two variables will be defined later in the article.<br> </p> <div class="highlight"><pre class="highlight javascript"><code><span class="o">&lt;</span><span class="nx">script</span><span class="o">&gt;</span> <span class="p">...</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">CldContext</span><span class="p">,</span> <span class="nx">CldImage</span><span class="p">,</span> <span class="nx">CldTransformation</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">cloudinary-vue</span><span class="dl">'</span> <span class="k">export</span> <span class="k">default</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">App</span><span class="dl">'</span><span class="p">,</span> <span class="na">components</span><span class="p">:</span> <span class="p">{</span> <span class="nx">HelloWorld</span><span class="p">,</span> <span class="nx">CldContext</span><span class="p">,</span> <span class="nx">CldImage</span><span class="p">,</span> <span class="nx">CldTransformation</span> <span class="p">},</span> <span class="nf">data</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">{</span> <span class="na">url</span><span class="p">:</span> <span class="dl">''</span><span class="p">,</span> <span class="na">publicId</span><span class="p">:</span> <span class="dl">''</span> <span class="p">}</span> <span class="p">},</span> <span class="p">...</span> <span class="o">&lt;</span><span class="sr">/script</span><span class="err">&gt; </span></code></pre></div> <p></p> <p>Then, apply the imported components in the template code.<br> </p> <div class="highlight"><pre class="highlight vue"><code><span class="nt">&lt;</span><span class="k">template</span><span class="nt">&gt;</span> <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"app"</span><span class="nt">&gt;</span> ... <span class="nt">&lt;div&gt;</span> <span class="nt">&lt;cld-context</span> <span class="na">cloudName=</span><span class="s">"CLOUD_NAME"</span><span class="nt">&gt;</span> <span class="nt">&lt;div</span> <span class="na">style=</span><span class="s">"display: flex; justify-content: center;"</span><span class="nt">&gt;</span> <span class="nt">&lt;cld-image</span> <span class="na">:publicId=</span><span class="s">"publicId"</span> <span class="na">width=</span><span class="s">"250"</span><span class="nt">&gt;</span> <span class="nt">&lt;cld-transformation</span> <span class="na">width=</span><span class="s">"200"</span> <span class="na">height=</span><span class="s">"200"</span> <span class="na">gravity=</span><span class="s">"face"</span> <span class="na">crop=</span><span class="s">"thumb"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/cld-image&gt;</span> <span class="nt">&lt;cld-image</span> <span class="na">:publicId=</span><span class="s">"publicId"</span> <span class="na">width=</span><span class="s">"250"</span><span class="nt">&gt;</span> <span class="nt">&lt;cld-transformation</span> <span class="na">width=</span><span class="s">"200"</span> <span class="na">height=</span><span class="s">"200"</span> <span class="na">gravity=</span><span class="s">"face"</span> <span class="na">crop=</span><span class="s">"thumb"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/cld-image&gt;</span> <span class="nt">&lt;cld-image</span> <span class="na">:publicId=</span><span class="s">"publicId"</span> <span class="na">width=</span><span class="s">"250"</span><span class="nt">&gt;</span> <span class="nt">&lt;cld-transformation</span> <span class="na">width=</span><span class="s">"200"</span> <span class="na">height=</span><span class="s">"200"</span> <span class="na">gravity=</span><span class="s">"face"</span> <span class="na">crop=</span><span class="s">"thumb"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/cld-image&gt;</span> <span class="nt">&lt;cld-image</span> <span class="na">:publicId=</span><span class="s">"publicId"</span> <span class="na">width=</span><span class="s">"250"</span><span class="nt">&gt;</span> <span class="nt">&lt;cld-transformation</span> <span class="na">width=</span><span class="s">"200"</span> <span class="na">height=</span><span class="s">"200"</span> <span class="na">gravity=</span><span class="s">"face"</span> <span class="na">crop=</span><span class="s">"thumb"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/cld-image&gt;</span> <span class="nt">&lt;cld-image</span> <span class="na">:publicId=</span><span class="s">"publicId"</span> <span class="na">width=</span><span class="s">"250"</span><span class="nt">&gt;</span> <span class="nt">&lt;cld-transformation</span> <span class="na">width=</span><span class="s">"200"</span> <span class="na">height=</span><span class="s">"200"</span> <span class="na">gravity=</span><span class="s">"face"</span> <span class="na">crop=</span><span class="s">"thumb"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/cld-image&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/cld-context&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/</span><span class="k">template</span><span class="nt">&gt;</span> </code></pre></div> <p></p> <p>Don&#39;t forget to replace <code>CLOUD_NAME</code> in the code above with the real value gotten from your dashboard.</p> <p>Whenever you upload an image, Cloudinary assigns a <code>public_id</code> to an image whenever you are uploading one.<br> Therefore, you can use the <code>public_id</code> returned in Cloudinary&#39;s JSON response when an image is being uploaded. </p> <p>So, modify the <code>openUploadModal()</code> method to capture the <code>public_id</code> of an uploaded image.<br> </p> <div class="highlight"><pre class="highlight javascript"><code> <span class="nf">openUploadModal</span><span class="p">()</span> <span class="p">{</span> <span class="nb">window</span><span class="p">.</span><span class="nx">cloudinary</span><span class="p">.</span><span class="nf">openUploadWidget</span><span class="p">(</span> <span class="p">{</span> <span class="na">cloud_name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">CLOUD_NAME</span><span class="dl">'</span><span class="p">,</span> <span class="na">upload_preset</span><span class="p">:</span> <span class="dl">'</span><span class="s1">UPLOAD_PRESET</span><span class="dl">'</span> <span class="p">},</span> <span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">result</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">error</span> <span class="o">&amp;&amp;</span> <span class="nx">result</span> <span class="o">&amp;&amp;</span> <span class="nx">result</span><span class="p">.</span><span class="nx">event</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">success</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Image uploaded..: </span><span class="dl">'</span><span class="p">,</span> <span class="nx">result</span><span class="p">.</span><span class="nx">info</span><span class="p">);</span> <span class="c1">//add the next 2 lines </span> <span class="k">this</span><span class="p">.</span><span class="nx">url</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">info</span><span class="p">.</span><span class="nx">url</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">publicId</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">info</span><span class="p">.</span><span class="nx">public_id</span><span class="p">;</span> <span class="p">}</span> <span class="p">}).</span><span class="nf">open</span><span class="p">();</span> <span class="p">}</span> </code></pre></div> <p></p> <p>The full code of the <code>App.vue</code> file is the following:<br> </p> <div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;template&gt;</span> <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"app"</span><span class="nt">&gt;</span> <span class="nt">&lt;img</span> <span class="na">alt=</span><span class="s">"Vue logo"</span> <span class="na">src=</span><span class="s">"./assets/logo.png"</span><span class="nt">&gt;</span> <span class="nt">&lt;HelloWorld</span> <span class="na">msg=</span><span class="s">"Learn how to upload photos with Cloudinary"</span><span class="nt">/&gt;</span> <span class="nt">&lt;div&gt;</span> <span class="nt">&lt;button</span> <span class="err">@</span><span class="na">click=</span><span class="s">"openUploadModel"</span><span class="nt">&gt;</span>Add Photo<span class="nt">&lt;/button&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;div&gt;</span> <span class="nt">&lt;cld-context</span> <span class="na">cloudName=</span><span class="s">"diiayntjq"</span><span class="nt">&gt;</span> <span class="nt">&lt;div</span> <span class="na">style=</span><span class="s">"display: flex; justify-content: center;"</span><span class="nt">&gt;</span> <span class="nt">&lt;cld-image</span> <span class="na">:publicId=</span><span class="s">"publicId"</span> <span class="na">width=</span><span class="s">"250"</span><span class="nt">&gt;</span> <span class="nt">&lt;cld-transformation</span> <span class="na">width=</span><span class="s">"600"</span> <span class="na">height=</span><span class="s">"600"</span> <span class="na">gravity=</span><span class="s">"face"</span> <span class="na">crop=</span><span class="s">"thumb"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/cld-image&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/cld-context&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/template&gt;</span> <span class="nt">&lt;script&gt;</span> <span class="k">import</span> <span class="nx">HelloWorld</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/HelloWorld.vue</span><span class="dl">'</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">CldContext</span><span class="p">,</span> <span class="nx">CldImage</span><span class="p">,</span> <span class="nx">CldTransformation</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">cloudinary-vue</span><span class="dl">'</span> <span class="k">export</span> <span class="k">default</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">App</span><span class="dl">'</span><span class="p">,</span> <span class="na">components</span><span class="p">:</span> <span class="p">{</span> <span class="nx">HelloWorld</span><span class="p">,</span> <span class="nx">CldContext</span><span class="p">,</span> <span class="nx">CldImage</span><span class="p">,</span> <span class="nx">CldTransformation</span> <span class="p">},</span> <span class="nf">data</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">{</span> <span class="na">url</span><span class="p">:</span> <span class="dl">''</span><span class="p">,</span> <span class="na">publicId</span><span class="p">:</span> <span class="dl">''</span> <span class="p">}</span> <span class="p">},</span> <span class="na">methods</span><span class="p">:</span> <span class="p">{</span> <span class="nf">openUploadModel</span><span class="p">()</span> <span class="p">{</span> <span class="nb">window</span><span class="p">.</span><span class="nx">cloudinary</span><span class="p">.</span><span class="nf">openUploadWidget</span><span class="p">(</span> <span class="p">{</span> <span class="na">cloud_name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">diiayntjq</span><span class="dl">'</span><span class="p">,</span> <span class="na">upload_preset</span><span class="p">:</span> <span class="dl">'</span><span class="s1">bi7uln2q</span><span class="dl">'</span> <span class="p">},</span> <span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">result</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">error</span> <span class="o">&amp;&amp;</span> <span class="nx">result</span> <span class="o">&amp;&amp;</span> <span class="nx">result</span><span class="p">.</span><span class="nx">event</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">success</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Image uploaded..: </span><span class="dl">'</span><span class="p">,</span> <span class="nx">result</span><span class="p">.</span><span class="nx">info</span><span class="p">);</span> <span class="k">this</span><span class="p">.</span><span class="nx">url</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">info</span><span class="p">.</span><span class="nx">url</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">publicId</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">info</span><span class="p">.</span><span class="nx">public_id</span><span class="p">;</span> <span class="p">}</span> <span class="p">}).</span><span class="nf">open</span><span class="p">();</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="nt">&lt;/script&gt;</span> </code></pre></div> <p></p> <h2> <a name="authenticating-with-auth0" href="#authenticating-with-auth0" class="anchor"> </a> Authenticating with Auth0 </h2> <p>Auth0 allows you to <a href="https://en.wikipedia.org/wiki/Authentication">authenticate</a> and <a href="https://en.wikipedia.org/wiki/Authorization">authorize</a> users. It integrates with several languages and frameworks for building applications. Furthermore, Auth0 proves a <a href="https://auth0.com/docs/login/universal-login">Universal Login</a> page to help with authenticating users. The login page redirects users back to your application after they log in.</p> <p><strong>Create an Auth0 Application</strong></p> <p>To start the authentication setup, log in to your <a href="https://manage.auth0.com/dashboard/">Auth0 dashboard</a> and follow the following steps to create an Auth0 Application.</p> <ol> <li>Click <strong>&quot;Applications&quot;</strong> in the left sidebar menu. Supply these parameters: <ul> <li><strong>Name</strong>: Vue.js Image Uploader</li> <li> <strong>Application type</strong> as Single Page Applications</li> </ul></li> <li>Click <strong>&quot;Create&quot;</strong> button to complete the application creation</li> <li>Click on the <strong>&quot;Settings&quot;</strong> tab of the newly created Application page. Set the following parameters: <ul> <li><strong>Allowed Callback URLs</strong>: <code>http://localhost:8080</code></li> <li><strong>Allowed Logout URLs</strong>: <code>http://localhost:8080</code></li> <li><strong>Allowed Web Origins</strong>: <code>http://localhost:8080</code></li> </ul></li> <li>Scroll down the page and click on the <strong>&quot;Save Changes&quot;</strong> button to save the settings.</li> </ol> <p><strong>Install Auth0</strong> <br> Install the Client SDK for Auth0 Single-Page Applications:<br> </p> <div class="highlight"><pre class="highlight shell"><code>npm <span class="nb">install</span> @auth0/auth0-spa-js </code></pre></div> <p></p> <p>Since you need to move from one page or link to another in the Vue.js application, you will need the <a href="https://router.vuejs.org/">Vue router</a>. Add it with the following command and choose <strong>&#39;yes&#39;</strong> when asked if you&#39;d like to use it in history mode.<br> </p> <div class="highlight"><pre class="highlight shell"><code>vue add router </code></pre></div> <p></p> <p>Create a new folder inside the <code>src</code> folder and name it <code>auth</code>. Then, create a new file inside the <code>auth</code> folder and name it <code>index.js</code>. Add the following code inside the file.<br> </p> <div class="highlight"><pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">Vue</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vue</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">createAuth0Client</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@auth0/auth0-spa-js</span><span class="dl">"</span><span class="p">;</span> <span class="cm">/** Define a default action to perform after authentication */</span> <span class="kd">const</span> <span class="nx">DEFAULT_REDIRECT_CALLBACK</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nb">window</span><span class="p">.</span><span class="nx">history</span><span class="p">.</span><span class="nf">replaceState</span><span class="p">({},</span> <span class="nb">document</span><span class="p">.</span><span class="nx">title</span><span class="p">,</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span><span class="p">);</span> <span class="kd">let</span> <span class="nx">instance</span><span class="p">;</span> <span class="cm">/** Returns the current instance of the SDK */</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">getInstance</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">instance</span><span class="p">;</span> <span class="cm">/** Creates an instance of the Auth0 SDK. If one has already been created, it returns that instance */</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">useAuth0</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">onRedirectCallback</span> <span class="o">=</span> <span class="nx">DEFAULT_REDIRECT_CALLBACK</span><span class="p">,</span> <span class="nx">redirectUri</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">origin</span><span class="p">,</span> <span class="p">...</span><span class="nx">options</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="nx">instance</span><span class="p">)</span> <span class="k">return</span> <span class="nx">instance</span><span class="p">;</span> <span class="c1">// The 'instance' is simply a Vue object</span> <span class="nx">instance</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Vue</span><span class="p">({</span> <span class="nf">data</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">{</span> <span class="na">loading</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">isAuthenticated</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">user</span><span class="p">:</span> <span class="p">{},</span> <span class="na">auth0Client</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="na">popupOpen</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">error</span><span class="p">:</span> <span class="kc">null</span> <span class="p">};</span> <span class="p">},</span> <span class="na">methods</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/** Authenticates the user using a popup window */</span> <span class="k">async</span> <span class="nf">loginWithPopup</span><span class="p">(</span><span class="nx">options</span><span class="p">,</span> <span class="nx">config</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">popupOpen</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="k">try</span> <span class="p">{</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">loginWithPopup</span><span class="p">(</span><span class="nx">options</span><span class="p">,</span> <span class="nx">config</span><span class="p">);</span> <span class="k">this</span><span class="p">.</span><span class="nx">user</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">getUser</span><span class="p">();</span> <span class="k">this</span><span class="p">.</span><span class="nx">isAuthenticated</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">isAuthenticated</span><span class="p">();</span> <span class="k">this</span><span class="p">.</span><span class="nx">error</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">error</span> <span class="o">=</span> <span class="nx">e</span><span class="p">;</span> <span class="c1">// eslint-disable-next-line</span> <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">e</span><span class="p">);</span> <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">popupOpen</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="p">}</span> <span class="k">this</span><span class="p">.</span><span class="nx">user</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">getUser</span><span class="p">();</span> <span class="k">this</span><span class="p">.</span><span class="nx">isAuthenticated</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="p">},</span> <span class="cm">/** Handles the callback when logging in using a redirect */</span> <span class="k">async</span> <span class="nf">handleRedirectCallback</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">loading</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="k">try</span> <span class="p">{</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">handleRedirectCallback</span><span class="p">();</span> <span class="k">this</span><span class="p">.</span><span class="nx">user</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">getUser</span><span class="p">();</span> <span class="k">this</span><span class="p">.</span><span class="nx">isAuthenticated</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">error</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">error</span> <span class="o">=</span> <span class="nx">e</span><span class="p">;</span> <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">loading</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="p">}</span> <span class="p">},</span> <span class="cm">/** Authenticates the user using the redirect method */</span> <span class="nf">loginWithRedirect</span><span class="p">(</span><span class="nx">o</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">loginWithRedirect</span><span class="p">(</span><span class="nx">o</span><span class="p">);</span> <span class="p">},</span> <span class="cm">/** Returns all the claims present in the ID token */</span> <span class="nf">getIdTokenClaims</span><span class="p">(</span><span class="nx">o</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">getIdTokenClaims</span><span class="p">(</span><span class="nx">o</span><span class="p">);</span> <span class="p">},</span> <span class="cm">/** Returns the access token. If the token is invalid or missing, a new one is retrieved */</span> <span class="nf">getTokenSilently</span><span class="p">(</span><span class="nx">o</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">getTokenSilently</span><span class="p">(</span><span class="nx">o</span><span class="p">);</span> <span class="p">},</span> <span class="cm">/** Gets the access token using a popup window */</span> <span class="nf">getTokenWithPopup</span><span class="p">(</span><span class="nx">o</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">getTokenWithPopup</span><span class="p">(</span><span class="nx">o</span><span class="p">);</span> <span class="p">},</span> <span class="cm">/** Logs the user out and removes their session on the authorization server */</span> <span class="nf">logout</span><span class="p">(</span><span class="nx">o</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">logout</span><span class="p">(</span><span class="nx">o</span><span class="p">);</span> <span class="p">}</span> <span class="p">},</span> <span class="cm">/** Use this lifecycle method to instantiate the SDK client */</span> <span class="k">async</span> <span class="nf">created</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// Create a new instance of the SDK client using members of the given options object</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">createAuth0Client</span><span class="p">({</span> <span class="p">...</span><span class="nx">options</span><span class="p">,</span> <span class="na">client_id</span><span class="p">:</span> <span class="nx">options</span><span class="p">.</span><span class="nx">clientId</span><span class="p">,</span> <span class="na">redirect_uri</span><span class="p">:</span> <span class="nx">redirectUri</span> <span class="p">});</span> <span class="k">try</span> <span class="p">{</span> <span class="c1">// If the user is returning to the app after authentication..</span> <span class="k">if </span><span class="p">(</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="dl">"</span><span class="s2">code=</span><span class="dl">"</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="dl">"</span><span class="s2">state=</span><span class="dl">"</span><span class="p">)</span> <span class="p">)</span> <span class="p">{</span> <span class="c1">// handle the redirect and retrieve tokens</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">appState</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">handleRedirectCallback</span><span class="p">();</span> <span class="k">this</span><span class="p">.</span><span class="nx">error</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="c1">// Notify subscribers that the redirect callback has happened, passing the appState</span> <span class="c1">// (useful for retrieving any pre-authentication state)</span> <span class="nf">onRedirectCallback</span><span class="p">(</span><span class="nx">appState</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">error</span> <span class="o">=</span> <span class="nx">e</span><span class="p">;</span> <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span> <span class="c1">// Initialize our internal authentication state</span> <span class="k">this</span><span class="p">.</span><span class="nx">isAuthenticated</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">isAuthenticated</span><span class="p">();</span> <span class="k">this</span><span class="p">.</span><span class="nx">user</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">auth0Client</span><span class="p">.</span><span class="nf">getUser</span><span class="p">();</span> <span class="k">this</span><span class="p">.</span><span class="nx">loading</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">});</span> <span class="k">return</span> <span class="nx">instance</span><span class="p">;</span> <span class="p">};</span> <span class="c1">// Create a simple Vue plugin to expose the wrapper object throughout the application</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">Auth0Plugin</span> <span class="o">=</span> <span class="p">{</span> <span class="nf">install</span><span class="p">(</span><span class="nx">Vue</span><span class="p">,</span> <span class="nx">options</span><span class="p">)</span> <span class="p">{</span> <span class="nx">Vue</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">$auth</span> <span class="o">=</span> <span class="nf">useAuth0</span><span class="p">(</span><span class="nx">options</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> </code></pre></div> <p></p> <p>The options object in the above code is used to pass in the <code>domain</code> and <code>clientId</code> values. Hence, you have to create a JSON file called <code>auth_config.json</code> in the root folder of the Vue.js application and add the following values to it. Obtain the domain and clientId values from the <strong>&quot;Settings&quot;</strong> tab of the Auth0 Application page and put them in the JSON file like the following.<br> </p> <div class="highlight"><pre class="highlight shell"><code><span class="o">{</span> <span class="s2">"domain"</span>: <span class="s2">"YOUR_DOMAIN"</span>, <span class="s2">"clientId"</span>: <span class="s2">"YOUR_CLIENT_ID"</span> <span class="o">}</span> </code></pre></div> <p></p> <p>Next, import the Auth0 plugin and Vue router in the <code>main.js</code> file as shown below.<br> </p> <div class="highlight"><pre class="highlight vue"><code>... import router from './router' // newly added line // Import the Auth0 configuration import { domain, clientId } from "../auth_config.json"; // Import the plugin here import { Auth0Plugin } from "./auth"; // Install the authentication plugin here Vue.use(Auth0Plugin, { domain, clientId, onRedirectCallback: appState =&gt; { router.push( appState <span class="err">&amp;&amp;</span> appState.targetUrl ? appState.targetUrl : window.location.pathname ); } }); Vue.config.productionTip = false; new Vue({ router, // newly added line render: h =&gt; h(App) }).$mount("#app"); </code></pre></div> <p></p> <p><strong>Configure Log in and Log out follows</strong></p> <p>Edit the App.vue file by adding two buttons for login and log out after the HelloWorld tag in the template code.<br> </p> <div class="highlight"><pre class="highlight vue"><code><span class="nt">&lt;</span><span class="k">template</span><span class="nt">&gt;</span> <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"app"</span><span class="nt">&gt;</span> ... <span class="nt">&lt;div</span> <span class="na">v-if=</span><span class="s">"!$auth.loading"</span><span class="nt">&gt;</span> <span class="c">&lt;!-- show login when not authenticated --&gt;</span> <span class="nt">&lt;button</span> <span class="na">v-if=</span><span class="s">"!$auth.isAuthenticated"</span> <span class="err">@</span><span class="na">click=</span><span class="s">"login"</span><span class="nt">&gt;</span>Log in<span class="nt">&lt;/button&gt;</span> <span class="c">&lt;!-- show logout when authenticated --&gt;</span> <span class="nt">&lt;button</span> <span class="na">v-if=</span><span class="s">"$auth.isAuthenticated"</span> <span class="err">@</span><span class="na">click=</span><span class="s">"logout"</span><span class="nt">&gt;</span>Log out<span class="nt">&lt;/button&gt;</span> <span class="nt">&lt;/div&gt;</span> ... <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/</span><span class="k">template</span><span class="nt">&gt;</span> </code></pre></div> <p></p> <p>Next, add the login and log out methods in the list of methods inside the <code>&lt;script&gt;</code> section<br> </p> <div class="highlight"><pre class="highlight javascript"><code><span class="p">...</span> <span class="nx">methods</span><span class="p">:</span> <span class="p">{</span> <span class="c1">// Log the user in</span> <span class="nf">login</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">$auth</span><span class="p">.</span><span class="nf">loginWithRedirect</span><span class="p">();</span> <span class="p">},</span> <span class="c1">// Log the user out</span> <span class="nf">logout</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">$auth</span><span class="p">.</span><span class="nf">logout</span><span class="p">({</span> <span class="na">returnTo</span><span class="p">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">origin</span> <span class="p">});</span> <span class="p">},</span> <span class="nf">openUploadModel</span><span class="p">()</span> <span class="p">{</span> <span class="p">...</span> </code></pre></div> <p></p> <p>Now, you have added the login and log-out buttons and can proceed to test the authentication flow. Go to 127.0.0.1:8000 on your web browser. You will see the login button present now as shown below.</p> <p><img src="https://paper-attachments.dropbox.com/s_AFF2FF088F20EBD06640F4218CCE5DBD4255763E00DF0ED2933F2E3D5F60B001_1632957574898_Screenshot+2021-09-29+at+23.45.16.png" alt="Login button is visible on the page now"></p> <p>When you click the login button, you will be directed to the Auth0 Universal login Page where your users can sign up or log in after which they will be redirected to your application.</p> <p><img src="https://paper-attachments.dropbox.com/s_AFF2FF088F20EBD06640F4218CCE5DBD4255763E00DF0ED2933F2E3D5F60B001_1632957589856_Screenshot+2021-09-29+at+23.45.43.png" alt="Auth0 universal login page activated"></p> <p>You can study the <a href="https://auth0.com/blog/complete-guide-to-vue-user-authentication/">The Complete Guide to Vue.js User Authentication with Auth0</a> for a detailed explanation of the Auth0 implementation in Vue.js.</p> <h2> <a name="conclusion" href="#conclusion" class="anchor"> </a> Conclusion </h2> <p>With the knowledge gained, you can try implementing the <a href="https://cloudinary.com/documentation/image_video_and_file_upload">image and video upload</a> in your next project. You may also develop a full-fledged user profile with Auth0. Much more, you can learn about the <a href="https://jamstack.org/">JAMSTACK</a> and its ecosystem as you build more JAMSTACK projects.</p> <p>Thanks for your attention.</p> <p>Content created for the <a href="https://content.hackmamba.io/">Hackmamba</a> Jamstack Content Hackathon using <a href="https://auth0.com/">Auth0</a>, <a href="https://cloudinary.com/">Cloudinary</a> and <a href="https://vuejs.org/">Vue.js</a>.</p>

Top comments (1)

Collapse
 
hazelday profile image
HazelDay

that is really amazing and valuable information you have shared . thanks for sharing . Apartments for rent london england