<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Brian Barrow - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Brian Barrow - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 22:23:59 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/the_BrianB/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How Vue Composables Work – Explained with Code Examples ]]>
                </title>
                <description>
                    <![CDATA[ Vue composables are a very helpful tool when developing Vue applications. They give developers an easy way to reuse logic across our applications. In addition to allowing for “stateless” logic (things like formatting or routine calculations), composa... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-vue-composables-work/</link>
                <guid isPermaLink="false">684c58453ee20021234073af</guid>
                
                    <category>
                        <![CDATA[ Vue.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vue ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Brian Barrow ]]>
                </dc:creator>
                <pubDate>Fri, 13 Jun 2025 16:56:37 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749830866913/e732db46-638b-42cd-aabf-ad51a54a3409.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Vue composables are a very helpful tool when developing Vue applications. They give developers an easy way to reuse logic across our applications. In addition to allowing for “stateless” logic (things like formatting or routine calculations), composables also give us the ability to reuse stateful logic throughout the app.</p>
<p>Before diving into the tutorial below, I want to mention that the documentation for Vue is <em>really</em> good. <a target="_blank" href="https://vuejs.org/guide/reusability/composables">The page on composables</a> explains the basics really well and will get you 90 percent of the way there. I am writing this article because I think the examples in the docs could go a little deeper in explaining how things can work inside of a composable. I will be reiterating some of the information from the docs, but I will also provide an example of a more complex composable.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-use-composables">Why Use Composables?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-simple-composable-example">Simple Composable Example</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-complex-composable-example">Complex Composable Example</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-util-function-for-fetch">Util function for fetch</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-useasyncstate-composable">useAsyncState Composable</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-usage-in-a-component">Usage in a component</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-why-use-composables">Why Use Composables?</h2>
<p>Composables let you reuse stateful logic across your apps. Whenever there is logic that is used in more than two places, we typically want to pull that logic into its own function. Most of the time, that logic is considered “stateless”, meaning that it takes an input and returns an output. The docs mention date formatting, but this could also include something like currency calculations or string validation.</p>
<p>In modern web applications, there are often pieces of logic that require managing state over time. Inside a typical component, we have the ability to adapt the application depending on the “state” of different variables within the component. Sometimes that logic, or at least pieces of that logic, are reused throughout the app.</p>
<p>For example, in an e-commerce application, you might have logic to increase and decrease the quantity of a product a person is adding to their cart. This logic could be used both on the product page, and inside the cart itself.</p>
<p>The look and feel of both those places will be different, so re-using a full component wouldn’t make sense – but we still want to centralize the logic to make the code easier to maintain. That is where Composables come in.</p>
<p>(It is worth noting that not everything needs to be a composable. Logic that is only used in a single component shouldn’t be refactored into a composable until necessary.)</p>
<h2 id="heading-simple-composable-example">Simple Composable Example</h2>
<p>Let’s take a look at a simple counter example. Here is some code for a very simple <code>Counter</code> component.</p>
<pre><code class="lang-typescript">&lt;script setup lang=<span class="hljs-string">"ts"</span>&gt;
  <span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>
  <span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Ref } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>

  <span class="hljs-keyword">const</span> count: Ref&lt;<span class="hljs-built_in">number</span>&gt; = ref(<span class="hljs-number">0</span>)
  <span class="hljs-keyword">const</span> increment = <span class="hljs-function">() =&gt;</span> {
    count.value++
  }
  <span class="hljs-keyword">const</span> decrement = <span class="hljs-function">() =&gt;</span> {
    count.value--
  }
&lt;/script&gt;

&lt;template&gt;
  &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"bg-teal-100 border-2 border-gray-800 rounded-xl p-4 w-64"</span>&gt;
    &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"text-center mb-4"</span>&gt;
      &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">"text-lg font-medium text-gray-800"</span>&gt;Count: {{ count }}&lt;/span&gt;
    &lt;/div&gt;

    &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"flex gap-2 justify-center"</span>&gt;
      &lt;button
        <span class="hljs-meta">@click</span>=<span class="hljs-string">"decrement"</span>
        <span class="hljs-keyword">class</span>=<span class="hljs-string">"bg-red-100 border-2 border-gray-800 rounded px-4 py-0 text-gray-800 font-medium hover:bg-red-500 transition-colors"</span>
      &gt;
        -
      &lt;/button&gt;

      &lt;button
        <span class="hljs-meta">@click</span>=<span class="hljs-string">"count = 0"</span>
        <span class="hljs-keyword">class</span>=<span class="hljs-string">"bg-gray-100 border-2 border-gray-800 rounded px-4 py-0 text-gray-800 font-medium hover:bg-gray-300 transition-colors"</span>
      &gt;
        Reset
      &lt;/button&gt;

      &lt;button
        <span class="hljs-meta">@click</span>=<span class="hljs-string">"increment"</span>
        <span class="hljs-keyword">class</span>=<span class="hljs-string">"bg-green-100 border-2 border-gray-800 rounded px-4 py-0 text-gray-800 font-medium hover:bg-green-500 transition-colors"</span>
      &gt;
        +
      &lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>The output of that component would look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748355723167/8c39759c-6fd9-4fcf-abdf-f67e672c172f.gif" alt="8c39759c-6fd9-4fcf-abdf-f67e672c172f" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>This works great, but if we end up needing this same counter logic in another component with a completely different look and feel, then we would end up repeating the logic. We can extract the logic into a composable and access the same stateful logic anywhere we need to.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// counter.ts</span>
<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Ref } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useCounter</span>(<span class="hljs-params"></span>): <span class="hljs-title">Readonly</span>&lt;</span>{
  count: Ref&lt;<span class="hljs-built_in">number</span>&gt;
  increment: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>
  decrement: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>
}&gt; {
  <span class="hljs-keyword">const</span> count: Ref&lt;<span class="hljs-built_in">number</span>&gt; = ref(<span class="hljs-number">0</span>)
  <span class="hljs-keyword">const</span> increment = <span class="hljs-function">() =&gt;</span> {
    count.value++
  }
  <span class="hljs-keyword">const</span> decrement = <span class="hljs-function">() =&gt;</span> {
    count.value--
  }
  <span class="hljs-keyword">return</span> { count, increment, decrement }
}
</code></pre>
<p>Then we update the script tag in the component to use the composable:</p>
<pre><code class="lang-typescript">&lt;script setup&gt;
<span class="hljs-keyword">import</span> { useCounter } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/counter.ts'</span>

<span class="hljs-keyword">const</span> { count, increment, decrement } = useCounter()
&lt;/script&gt;

&lt;template&gt;
  ...
&lt;/template&gt;
</code></pre>
<p>Now we can use this logic in multiple components throughout the app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748355833182/d90d000e-f309-4b22-9530-8e4614b450ec.gif" alt="d90d000e-f309-4b22-9530-8e4614b450ec" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>You will notice that only the logic is copied and each component still has its own copy of the <code>count</code> state. Using a composable does not mean the state is shared across components, only the stateful logic.</p>
<h2 id="heading-complex-composable-example">Complex Composable Example</h2>
<p>In the Vue docs, they give an example of using a composable to handle async data fetching. There are a couple of issues I have with the example they give. The main one is that the error handling is not robust for real world applications. Given that they just want to showcase a straightforward use of composables, this is understandable. But I wanted to showcase a more realistic implementation.</p>
<h3 id="heading-util-function-for-fetch">Util function for <code>fetch</code></h3>
<p>Before getting into the composable, we need to set up a util function for the <code>fetch</code> API. This is because we want to make sure every request throws an error if it fails. The <code>fetch</code> API doesn’t throw an error if the request responds with an error status. We have to check the <code>response.ok</code> in order to verify the status, and then throw an error if necessary.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// utils.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleFetch</span>(<span class="hljs-params">url: <span class="hljs-built_in">string</span>, options: RequestInit = {}</span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">Response</span>&gt; </span>{
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(url, options)
  <span class="hljs-keyword">if</span> (!res.ok) {
    <span class="hljs-keyword">const</span> err = <span class="hljs-keyword">await</span> res.text()
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(err)
  }
  <span class="hljs-keyword">return</span> res
}
</code></pre>
<h3 id="heading-useasyncstate-composable">useAsyncState Composable</h3>
<p>When working with async state, the requests can be in a few different states:</p>
<ul>
<li><p>Pending</p>
</li>
<li><p>Resolved</p>
</li>
<li><p>Rejected</p>
</li>
</ul>
<p>In addition to these states, we want to track the data or the error that comes back from the request.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// useAsyncState.ts</span>
<span class="hljs-keyword">import</span> { shallowRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Ref } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>

<span class="hljs-comment">// Specify a type for the response</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> AsyncState&lt;T&gt; = {
  data: Ref&lt;T | <span class="hljs-literal">null</span>&gt;
  error: Ref&lt;<span class="hljs-built_in">Error</span> | <span class="hljs-literal">null</span>&gt;
  isPending: Ref&lt;<span class="hljs-built_in">boolean</span>&gt;
  isResolved: Ref&lt;<span class="hljs-built_in">boolean</span>&gt;
  isRejected: Ref&lt;<span class="hljs-built_in">boolean</span>&gt;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useAsyncState</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">promise: <span class="hljs-built_in">Promise</span>&lt;T&gt;</span>): <span class="hljs-title">AsyncState</span>&lt;<span class="hljs-title">T</span>&gt; </span>{
  <span class="hljs-comment">// I used shallowRef instead of ref to avoid deep reactivity</span>
  <span class="hljs-comment">// I only care about the top-level properties being reactive</span>
  <span class="hljs-keyword">const</span> data = shallowRef&lt;T | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>)
  <span class="hljs-keyword">const</span> error = shallowRef&lt;<span class="hljs-built_in">Error</span> | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>)
  <span class="hljs-keyword">const</span> isPending = shallowRef(<span class="hljs-literal">false</span>)
  <span class="hljs-keyword">const</span> isResolved = shallowRef(<span class="hljs-literal">false</span>)
  <span class="hljs-keyword">const</span> isRejected = shallowRef(<span class="hljs-literal">false</span>)

  data.value = <span class="hljs-literal">null</span>
  error.value = <span class="hljs-literal">null</span>
  isPending.value = <span class="hljs-literal">true</span>
  isRejected.value = <span class="hljs-literal">false</span>
  isResolved.value = <span class="hljs-literal">false</span>

  promise.then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
    data.value = result
    isPending.value = <span class="hljs-literal">false</span>
    isResolved.value = <span class="hljs-literal">true</span>
  }).catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
    error.value = err
    isPending.value = <span class="hljs-literal">false</span>
    isRejected.value = <span class="hljs-literal">true</span>
  })

  <span class="hljs-keyword">return</span> { data, error, isPending, isResolved, isRejected }
}
</code></pre>
<p>This gives a few more explicit properties for the different states, rather than relying on the values in <code>data</code> and <code>error</code>. You’ll also notice that this composable takes in a promise rather than a URL string like the docs show. Different endpoints will have different response types and I wanted to be able to handle those outside of this composable.</p>
<h3 id="heading-usage-in-a-component">Usage in a component</h3>
<p>I have set up an endpoint that will wait a random number of seconds before responding either successfully or with an error. My component is calling this endpoint using the composable and using the data from the composable to update the template.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748364641116/304b8c08-5277-4243-b621-70a7c19edcfd.gif" alt="304b8c08-5277-4243-b621-70a7c19edcfd" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>With the error state showing like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748364674070/7d0c6923-85b9-4971-8f69-d127ffa6c1f4.png" alt="7d0c6923-85b9-4971-8f69-d127ffa6c1f4" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>You can see a working example at <a target="_blank" href="https://understanding-composables.pages.dev/">https://understanding-composables.pages.dev/</a>.</p>
<p>To make this a bit easier to explain and understand, I am breaking up the <code>&lt;script&gt;</code> tag and the <code>&lt;template&gt;</code> sections of the component.</p>
<h4 id="heading-script">Script</h4>
<pre><code class="lang-typescript">&lt;script lang=<span class="hljs-string">"ts"</span> setup&gt;
<span class="hljs-keyword">import</span> { ref, unref } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Ref } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>
<span class="hljs-keyword">import</span> { useAsyncState } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/composables'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { AsyncState } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/composables'</span>
<span class="hljs-keyword">import</span> { handleFetch } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/utils'</span>

<span class="hljs-keyword">interface</span> RandomResponse {
  msg: <span class="hljs-built_in">string</span>
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRandomResponse</span>(<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">RandomResponse</span>&gt; </span>{
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> handleFetch(<span class="hljs-string">'https://briancbarrow.com/api/random'</span>)
  <span class="hljs-keyword">const</span> text = <span class="hljs-keyword">await</span> response.text()
  <span class="hljs-keyword">return</span> { msg: text }
}

<span class="hljs-keyword">const</span> randomResponseData: Ref&lt;AsyncState&lt;RandomResponse&gt; | <span class="hljs-literal">null</span>&gt; = ref(<span class="hljs-literal">null</span>)

<span class="hljs-keyword">const</span> handleMakeRequest = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> data = getRandomResponse()
    randomResponseData.value = useAsyncState(data)
}
&lt;/script&gt;
</code></pre>
<p>Here we have a method, <code>getRandomResponse</code> that calls an endpoint and returns a promise. That promise is then passed into the <code>useAsyncState</code> when <code>handleMakeRequest</code> is called. That puts the full return value into the <code>randomResponseData</code> ref which we can then use inside the template.</p>
<p>Rather than show the full template, I will just show a few portions of it.</p>
<p>Here you can see two different buttons being used depending on the state. I am using a separate button element to indicate the “loading” state, but in practice you can use the composable properties to set the <code>disabled</code> property of the button and change the text.</p>
<pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
          <span class="hljs-attr">v-if</span>=<span class="hljs-string">"
            !randomResponseData?.isPending &amp;&amp;
            !randomResponseData?.error &amp;&amp;
            !randomResponseData?.data
          "</span>
          <span class="hljs-attr">class</span>=<span class="hljs-string">"px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"</span>
          @<span class="hljs-attr">click</span>=<span class="hljs-string">"handleMakeRequest"</span>
        &gt;</span>
          Make Request
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

        <span class="hljs-comment">&lt;!-- Loading State Button --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
          <span class="hljs-attr">v-if</span>=<span class="hljs-string">"randomResponseData?.isPending"</span>
          <span class="hljs-attr">disabled</span>
          <span class="hljs-attr">class</span>=<span class="hljs-string">"px-6 py-3 bg-blue-600 text-white font-medium rounded-lg opacity-75 cursor-not-allowed flex items-center mx-auto"</span>
        &gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">svg</span>
            <span class="hljs-attr">class</span>=<span class="hljs-string">"animate-spin -ml-1 mr-3 h-5 w-5 text-white"</span>
            <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>
            <span class="hljs-attr">fill</span>=<span class="hljs-string">"none"</span>
            <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span>
          &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">circle</span>
              <span class="hljs-attr">class</span>=<span class="hljs-string">"opacity-25"</span>
              <span class="hljs-attr">cx</span>=<span class="hljs-string">"12"</span>
              <span class="hljs-attr">cy</span>=<span class="hljs-string">"12"</span>
              <span class="hljs-attr">r</span>=<span class="hljs-string">"10"</span>
              <span class="hljs-attr">stroke</span>=<span class="hljs-string">"currentColor"</span>
              <span class="hljs-attr">stroke-width</span>=<span class="hljs-string">"4"</span>
            &gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">circle</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">path</span>
              <span class="hljs-attr">class</span>=<span class="hljs-string">"opacity-75"</span>
              <span class="hljs-attr">fill</span>=<span class="hljs-string">"currentColor"</span>
              <span class="hljs-attr">d</span>=<span class="hljs-string">"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"</span>
            &gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">path</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
          Loading...
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Here are a couple of the rows from the table:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">tr</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"divide-x divide-gray-200"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"py-4 pr-4 pl-4 text-sm font-medium whitespace-nowrap text-gray-900 sm:pl-0"</span>&gt;</span>
    isPending
  <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">td</span>
    <span class="hljs-attr">class</span>=<span class="hljs-string">"p-4 text-sm whitespace-nowrap text-gray-500"</span>
    <span class="hljs-attr">:class</span>=<span class="hljs-string">"randomResponseData?.isPending ? 'bg-blue-500' : 'bg-gray-300'"</span>
  &gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-4 text-sm whitespace-nowrap text-gray-500"</span>&gt;</span>
    {{ randomResponseData?.isPending }}
  <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">tr</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"divide-x divide-gray-200"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"py-4 pr-4 pl-4 text-sm font-medium whitespace-nowrap text-gray-900 sm:pl-0"</span>&gt;</span>
    data
  <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">td</span>
    <span class="hljs-attr">class</span>=<span class="hljs-string">"p-4 text-sm whitespace-nowrap text-gray-500"</span>
    <span class="hljs-attr">:class</span>=<span class="hljs-string">"randomResponseData?.data ? 'bg-green-500' : 'bg-gray-300'"</span>
  &gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-4 text-sm whitespace-nowrap text-gray-500"</span>&gt;</span>
    {{ unref(randomResponseData?.data)?.msg }}
  <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
</code></pre>
<p>In those <code>tr</code> tags, you can see the template rendering different things depending on the state coming from the composable.</p>
<p>For a more complete look at the code, you can visit <a target="_blank" href="https://github.com/briancbarrow/understanding-composables">the GitHub repo</a>. You can also look at how VueUse, a collection of composables, handles similar functionality: <a target="_blank" href="https://vueuse.org/core/useAsyncState/">https://vueuse.org/core/useAsyncState/</a></p>
<p>In a future article, I’ll dive into their implementation.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Composables are an incredibly useful tool in Vue 3. As projects grow in size and scope, knowing how and when to use composables can improve the maintainability of the project over the long term.</p>
<p>The key is identifying when you have stateful logic that needs to be reused across components, then extracting it into a well-structured composable that handles edge cases properly.</p>
<p>For more real world examples you can check out the <a target="_blank" href="https://vueuse.org/">VueUse library</a> and <a target="_blank" href="https://github.com/vueuse/vueuse">repo</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create and Publish a Vue Component Library – Update ]]>
                </title>
                <description>
                    <![CDATA[ Back in 2020, I wrote a post about building a Vue Component library. Since then the package I used has been deprecated, and the recommended way to build a library/package is to use Vite. Getting Started I started off the project by running npm create... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-and-publish-a-vue-component-library-update/</link>
                <guid isPermaLink="false">66bb92e15d242388375d388c</guid>
                
                    <category>
                        <![CDATA[ components ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vite ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vue ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Brian Barrow ]]>
                </dc:creator>
                <pubDate>Tue, 30 May 2023 20:11:28 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/pexels-pixabay-159711.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Back in 2020, I <a target="_blank" href="https://www.freecodecamp.org/news/how-to-create-and-publish-a-vue-component-library/">wrote a post</a> about building a Vue Component library. Since then the package I used has been deprecated, and the recommended way to build a library/package is to use Vite.</p>
<h2 id="heading-getting-started">Getting Started</h2>
<p>I started off the project by running <code>npm create vite@latest</code> and naming my project <code>brian-component-lib</code> to stay consistent with my previous post. I also chose to use TypeScript and Vue when those options came up.</p>
<h2 id="heading-the-component">The Component</h2>
<p>The component I built is a clone of the buttons used on freeCodeCamp.org</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/image-160.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Button component we are building</em></p>
<p>Here is the code for that component. Note that it is using TypeScript and the <code>script setup</code> format available in Vue 3.</p>
<pre><code class="lang-js">&lt;script setup lang=<span class="hljs-string">"ts"</span>&gt;

defineProps&lt;{ <span class="hljs-attr">text</span>: string }&gt;()

&lt;/script&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn-cta"</span>&gt;</span>{{ text }}<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="css">
<span class="hljs-selector-class">.btn-cta</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#d0d0d5</span>;
  <span class="hljs-attribute">border-width</span>: <span class="hljs-number">3px</span>;
  <span class="hljs-attribute">border-color</span>: <span class="hljs-number">#1b1b32</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">border-style</span>: solid;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#1b1b32</span>;
  <span class="hljs-attribute">display</span>: block;
  <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">font-weight</span>: normal;
  <span class="hljs-attribute">text-align</span>: center;
  <span class="hljs-attribute">-ms-touch-action</span>: manipulation;
  <span class="hljs-attribute">touch-action</span>: manipulation;
  <span class="hljs-attribute">cursor</span>: pointer;
  <span class="hljs-attribute">white-space</span>: nowrap;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">6px</span> <span class="hljs-number">12px</span>;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">18px</span>;
  <span class="hljs-attribute">line-height</span>: <span class="hljs-number">1.42857143</span>;
}

<span class="hljs-selector-class">.btn-cta</span><span class="hljs-selector-pseudo">:active</span><span class="hljs-selector-pseudo">:hover</span>,
<span class="hljs-selector-class">.btn-cta</span><span class="hljs-selector-pseudo">:focus</span>,
<span class="hljs-selector-class">.btn-cta</span><span class="hljs-selector-pseudo">:hover</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#1b1b32</span>;
  <span class="hljs-attribute">border-width</span>: <span class="hljs-number">3px</span>;
  <span class="hljs-attribute">border-color</span>: <span class="hljs-number">#000</span>;
  <span class="hljs-attribute">background-image</span>: none;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#f5f6f7</span>;
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p>Then we need to expose this component in the library. We do that by exporting it from an <code>index.ts</code> file.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> FccButton <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/FccButton.vue"</span>;

<span class="hljs-keyword">export</span> { FccButton };
</code></pre>
<h2 id="heading-config">Config</h2>
<p>With the component code ready to go, we need to make sure Vite and the <code>package.json</code> file are configured properly.</p>
<p>Vite has a lot of options when building code. We are interested in the <a target="_blank" href="https://vitejs.dev/guide/build.html#library-mode">"Library Mode"</a>.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"vite"</span>;
<span class="hljs-keyword">import</span> { resolve } <span class="hljs-keyword">from</span> <span class="hljs-string">"path"</span>;
<span class="hljs-keyword">import</span> vue <span class="hljs-keyword">from</span> <span class="hljs-string">"@vitejs/plugin-vue"</span>;

<span class="hljs-comment">// https://vitejs.dev/config/</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">plugins</span>: [vue()],
  <span class="hljs-attr">build</span>: {
    <span class="hljs-attr">lib</span>: {
      <span class="hljs-comment">// src/indext.ts is where we have exported the component(s)</span>
      <span class="hljs-attr">entry</span>: resolve(__dirname, <span class="hljs-string">"src/index.ts"</span>),
      <span class="hljs-attr">name</span>: <span class="hljs-string">"BrianComponentLibrary"</span>,
      <span class="hljs-comment">// the name of the output files when the build is run</span>
      <span class="hljs-attr">fileName</span>: <span class="hljs-string">"brian-component-lib"</span>,
    },
    <span class="hljs-attr">rollupOptions</span>: {
      <span class="hljs-comment">// make sure to externalize deps that shouldn't be bundled</span>
      <span class="hljs-comment">// into your library</span>
      <span class="hljs-attr">external</span>: [<span class="hljs-string">"vue"</span>],
      <span class="hljs-attr">output</span>: {
        <span class="hljs-comment">// Provide global variables to use in the UMD build</span>
        <span class="hljs-comment">// for externalized deps</span>
        <span class="hljs-attr">globals</span>: {
          <span class="hljs-attr">vue</span>: <span class="hljs-string">"Vue"</span>,
        },
      },
    },
  },
});
</code></pre>
<p>Here is the <code>package.json</code> file. We need to make sure we have the properties necessary for pointing to our built files. For more information on what each property does, you can hover over them in VS Code.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"brian-component-lib"</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"module"</span>,
  <span class="hljs-attr">"files"</span>: [<span class="hljs-string">"dist"</span>],
  <span class="hljs-attr">"main"</span>: <span class="hljs-string">"./dist/brian-component-lib.umd.cjs"</span>,
  <span class="hljs-attr">"module"</span>: <span class="hljs-string">"./dist/brian-component-lib.js"</span>,
  <span class="hljs-attr">"exports"</span>: {
    <span class="hljs-attr">"."</span>: {
      <span class="hljs-attr">"import"</span>: <span class="hljs-string">"./dist/brian-component-lib.js"</span>,
      <span class="hljs-attr">"require"</span>: <span class="hljs-string">"./dist/brian-component-lib.umd.cjs"</span>
    },
    <span class="hljs-attr">"./style.css"</span>: <span class="hljs-string">"./dist/style.css"</span>
  },
  <span class="hljs-attr">"types"</span>: <span class="hljs-string">"./dist/index.d.ts"</span>,
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"vite"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"vite build &amp;&amp; vue-tsc --emitDeclarationOnly"</span>,
    <span class="hljs-attr">"types"</span>: <span class="hljs-string">"vue-tsc "</span>,
    <span class="hljs-attr">"preview"</span>: <span class="hljs-string">"vite preview"</span>
  },
  <span class="hljs-attr">"dependencies"</span>: {
    <span class="hljs-attr">"vue"</span>: <span class="hljs-string">"^3.2.47"</span>
  },
  <span class="hljs-attr">"devDependencies"</span>: {
    <span class="hljs-attr">"@types/node"</span>: <span class="hljs-string">"^20.2.5"</span>,
    <span class="hljs-attr">"@vitejs/plugin-vue"</span>: <span class="hljs-string">"^4.2.3"</span>,
    <span class="hljs-attr">"typescript"</span>: <span class="hljs-string">"^5.0.2"</span>,
    <span class="hljs-attr">"vite"</span>: <span class="hljs-string">"^4.3.9"</span>,
    <span class="hljs-attr">"vue-tsc"</span>: <span class="hljs-string">"^1.4.2"</span>
  }
}
</code></pre>
<p>In order for the <code>vue-tsc --emitDeclarationOnly</code> to work when building, we need to add the following properties to the <code>compilerOptions</code> section of the tsconfig.json file:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"outDir"</span>: <span class="hljs-string">"dist"</span>,
<span class="hljs-string">"declaration"</span>: <span class="hljs-literal">true</span>,
</code></pre>
<p>We will also need to remove the <code>noEmit: true</code> property. This will make it so our types are available in the package, so a project using TypeScript with Vue doesn't yell at us for not having declared types.</p>
<p>I also added this line to make sure my <code>App.vue</code> and <code>main.ts</code> file aren't included in the component library built files.</p>
<p><code>"exclude": ["src/App.vue", "src/main.ts"],</code></p>
<h2 id="heading-test-the-library">Test the Library</h2>
<p>We can now run <code>npm run build</code> and then test out our library. To do this, open up a Vue project (you can open a current Vue 3 project you have, or create a blank one).</p>
<p>Inside the package.json file for the project you just opened add the following to the dependencies:</p>
<p><code>"brian-component-lib": "file:../brian-component-library"</code></p>
<p>Make sure the file path you put in points to the correct folder where the component library lives.</p>
<p>Run <code>npm install</code> and you should now have the component library in your <code>node_modules</code>.</p>
<p>Import the component into one of the pages to test that it is working.</p>
<p>Note: You will need to also import the CSS because it gets built to its own file during the build process.</p>
<pre><code class="lang-js">&lt;script setup lang=<span class="hljs-string">"ts"</span>&gt;
<span class="hljs-keyword">import</span> { FccButton } <span class="hljs-keyword">from</span> <span class="hljs-string">'brian-component-lib'</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">"brian-component-lib/style.css"</span>
&lt;/script&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">FccButton</span> <span class="hljs-attr">text</span>=<span class="hljs-string">"Run the Tests"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span>
</code></pre>
<p>You should now see the button when you run the project.</p>
<h2 id="heading-how-to-publish-to-npm">How to Publish to NPM</h2>
<p>If you haven't signed into NPM inside your terminal you can do that by running the <code>npm adduser</code> command.</p>
<p>Then just run the <code>npm publish</code> command and the package should be pushed up and made available on NPM.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Vite makes it pretty easy to get a component library published. Hopefully this helped. <a target="_blank" href="https://twitter.com/the_brianb">Let me know on Twitter</a> if it did or if you'd like to see something else from me in the future.</p>
<p>You can see the repository for this code here: <a target="_blank" href="https://github.com/briancbarrow/vue-component-library-2023">https://github.com/briancbarrow/vue-component-library-2023</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What are Pointers in Go? A Guide for JavaScript Devs ]]>
                </title>
                <description>
                    <![CDATA[ Coming from a language like JavaScript, pointers can be kind of confusing at first. Here I will break it down so that they are easier to understand. What Are Pointers in Go? Just like in JavaScript, Go has variables which are locations in memory that... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-are-pointers-in-go/</link>
                <guid isPermaLink="false">66bb92ea7a6500a14ba5b7a1</guid>
                
                    <category>
                        <![CDATA[ Go Language ]]>
                    </category>
                
                    <category>
                        <![CDATA[ golang ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Brian Barrow ]]>
                </dc:creator>
                <pubDate>Mon, 24 Apr 2023 22:07:19 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/04/kaleb-tapp-J59wWPn09BE-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Coming from a language like JavaScript, pointers can be kind of confusing at first. Here I will break it down so that they are easier to understand.</p>
<h2 id="heading-what-are-pointers-in-go">What Are Pointers in Go?</h2>
<p>Just like in JavaScript, Go has variables which are locations in memory that store a value. </p>
<p>In both languages, that value can be manipulated by assigning it a new value, or by performing operations on it. No matter what you do to that variable, it's still stored in a specific place in memory.</p>
<p>Pointers are another type of variable. Instead of storing a value that you will use, they store the <em>memory address</em> of another variable. It is "pointing to" the location of the variable.</p>
<p><strong>It is not the actual data of that variable</strong>. It is just the address of the variable it points to.</p>
<h3 id="heading-pointer-syntax">Pointer Syntax</h3>
<p>A pointer is defined using the <code>*</code> syntax. So to declare a pointer variable, the code would look like this:</p>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> x *<span class="hljs-keyword">int</span>
</code></pre>
<p>This sets <code>x</code> as a pointer to an <code>int</code> value.</p>
<p>If you want to set a variable as a pointer to another value, you intialize it with the <code>&amp;</code> operator.</p>
<pre><code class="lang-go">normalInt := <span class="hljs-number">5</span>
<span class="hljs-comment">// pointerInt is set as a pointer to normalInt</span>
pointerInt = &amp;normalInt

normalString := <span class="hljs-string">"hello world"</span>
<span class="hljs-comment">// pointerString is set as a pointer to normalString</span>
pointerString = &amp;normalString
</code></pre>
<p>In order to access the underlying value of the pointer, we use the <code>*</code> operator.</p>
<pre><code class="lang-go">fmt.Println(*pointerInt) <span class="hljs-comment">// read pointerInt through the pointer</span>
*pointerInt = <span class="hljs-number">10</span> <span class="hljs-comment">// set pointerInt through the pointer</span>
</code></pre>
<p>Here's a drawing that illustrates the concept of pointers:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Pasted-image-20230421202803.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-watch-for-nil-pointers">Watch for <code>nil</code> Pointers</h3>
<p>When dereferencing a pointer, if it hasn't been pointed to anything then the program will panic. It is good practice to check a pointer to see if it is <code>nil</code> before trying to dereference it.</p>
<h3 id="heading-why-pointers">Why Pointers?</h3>
<p>When a function takes in an argument, the value of the argument is copied into the function. The function then can manipulate the copy of the variable. </p>
<p>By using pointers, we can make more efficient programs. It allows us to manipulate the data the pointer references without copying it multiple times throughout the program.</p>
<p>While this does improve the efficiency of the program, you want to be careful when using them. It is perfectly fine to use normal variables as arguments to functions and return new values from the functions.</p>
<p>One place where pointers are useful is with Receivers.</p>
<h3 id="heading-pointer-receivers">Pointer Receivers</h3>
<p>Instead of receiving just a value, methods can receive a pointer. This allows the method to modify the value that to which the receiver points. Since methods often need to modify their receiver, pointer receivers are more common than value receivers.</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> ball <span class="hljs-keyword">struct</span> {
    color <span class="hljs-keyword">string</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(b *ball)</span> <span class="hljs-title">setColor</span><span class="hljs-params">(color <span class="hljs-keyword">string</span>)</span></span> {
    b.color = color
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    b := ball{
        color: <span class="hljs-string">"white"</span>,
    }
    b.setColor(<span class="hljs-string">"blue"</span>)
    fmt.Println(b.color)
    <span class="hljs-comment">// prints "blue"</span>
}
</code></pre>
<p>Without the pointer receiver, <code>setColor</code> would not be able to modify the <code>color</code> value of the ball.</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> ball <span class="hljs-keyword">struct</span> {
    color <span class="hljs-keyword">string</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(b ball)</span> <span class="hljs-title">setColor</span><span class="hljs-params">(color <span class="hljs-keyword">string</span>)</span></span> {
    b.color = color
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    b := ball{
        color: <span class="hljs-string">"white"</span>,
    }
    b.setColor(<span class="hljs-string">"blue"</span>)
    fmt.Println(b.color)
    <span class="hljs-comment">// prints "white"</span>
}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Pointers are an essential concept in Go programming. Although they can be confusing at first, they are an important tool for making efficient programs. </p>
<p>Hopefully this brief explanation will helps clear up any confusion.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Intro to Deno – Guide for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ What is Deno? Deno is a new JavaScript runtime. It was built by Ryan Dahl, the creator of Node.js.  Dahl had a few things that he regretted doing with Node and wanted to build a runtime that could solve those issues. Deno, like Node, is built on the ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/intro-to-deno/</link>
                <guid isPermaLink="false">66bb92e8add24ba427325111</guid>
                
                    <category>
                        <![CDATA[ Deno ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Brian Barrow ]]>
                </dc:creator>
                <pubDate>Fri, 09 Sep 2022 22:50:17 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/09/Screen-Shot-2022-09-07-at-4.09.00-PM.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="heading-what-is-deno">What is Deno?</h2>
<p>Deno is a new JavaScript runtime. It was built by Ryan Dahl, the creator of Node.js. </p>
<p>Dahl had a few things that he regretted doing with Node and wanted to build a runtime that could solve those issues. Deno, like Node, is built on the V8 JavaScript engine but was built using Rust instead of C++.</p>
<p>One of the main goals of Deno is to bring server side JavaScript closer to browser JavaScript. </p>
<p>If you've written in both Node and browser JavaScript, then you have surely run into the differences in the APIs used in the respective spaces. Deno aims to have the same APIs on the server that you would use in the browser. We'll take a deeper look at specific examples of this later on.</p>
<h2 id="heading-key-features-of-deno">Key Features of Deno</h2>
<h3 id="heading-deno-uses-typescript">Deno Uses TypeScript</h3>
<p>One of the most eye catching features of Deno is that it treats TypeScript as a first class language out of the box. This means you can run or use TypeScript without installing any other external or third party packages. It just works. </p>
<p>TypeScript is increasingly popular in the JavaScript world, and lots of tools and companies are pushing towards using it. It is cool to see a new progressive technology like TypeScript get more attention, and getting first class status in a big project like Deno is a huge step forward.</p>
<h3 id="heading-deno-is-secure-by-default">Deno is Secure by Default</h3>
<p>Deno by default is secure. This means that unless the script is <em>specifically</em> allowed, it cannot access system files, the environment (things like environment variables), or the network. </p>
<p>In order to specifically allow for access to these features, you need to pass the respective flag in the CLI command. Here are some of the ones you'll use the most:</p>
<ul>
<li><strong>Network access</strong>: <code>--allow-net</code>, you can also specify which URLs the code is allowed to access. For example: <code>--allow-net=https://api.deepgram.com</code></li>
<li><strong>File access</strong>: <code>--allow-read</code></li>
<li><strong>Environment access</strong>: <code>--allow-env</code></li>
</ul>
<h3 id="heading-denos-browser-compatibility">Deno's Browser Compatibility</h3>
<p>Like I mentioned above, Deno strives to have the same API as the browser. The biggest of these in my opinion is the support of the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">fetch API</a>. </p>
<p>These days, in most of the JavaScript I write, I use the <code>fetch</code> API. Being able to use the same syntax on my server side code makes it a lot easier to be productive and it makes the load of context switching much smaller.</p>
<h2 id="heading-package-manager">Package Manager</h2>
<p>Deno doesn't have a package manager registry. Node uses <code>npm</code> in order to load third party packages into your project, but Deno loads modules via URLs. </p>
<p>I was honestly confused at first by this. Having "grown up" with Node and npm it was weird to me to not have some sort of package manager or <code>package.json</code> file.</p>
<p>Instead of this centralized registry, Deno allows package developers to host their code wherever they want. If the code is hosted on GitHub, you can register your module on their <a target="_blank" href="https://deno.land/x">hosting service</a> where it is cached. That makes it easier for developers to find and use the module.</p>
<h2 id="heading-es-modules">ES Modules</h2>
<p>Deno also uses ES Modules and does not support the <code>require()</code> syntax. Again, most of the JavaScript I write these days uses modern features like this, so it has been nice to not worry about making sure I'm using the correct syntax depending on which environment I'm coding for.</p>
<h2 id="heading-standard-library">Standard Library</h2>
<p>Deno ships with a <a target="_blank" href="https://deno.land/std@0.138.0">standard library</a> that contains functionality that is audited by the Deno team. This makes it really easy to get started using Deno. </p>
<p>There is no need to look to third party packages to do fairly basic things that are needed in server side code. As a developer, it is comforting knowing that the code I use is officially supported and approved by the Deno team.</p>
<h3 id="heading-testing-module">Testing module</h3>
<p>One of the modules included in the standard library is the <a target="_blank" href="https://deno.land/std@0.109.0/testing">testing module</a>. This module makes writing tests easier in Deno and will make them more consistent across projects. </p>
<p>This might not be a plus for everyone, especially if some have strong opinions on testing libraries. I really like it though. As Deno continues to grow, consistency across projects will make maintaining code and switching projects much easier.</p>
<h2 id="heading-deno-vs-node">Deno vs Node</h2>
<p>The biggest question surrounding Deno is how it compares to Node.</p>
<p>Deno clearly offers some advantages over Node. Being secure by default is definitely an attractive feature, and developers will see the out of the box support for TypeScript as a big win.</p>
<p>On the other hand, Node has a very rich community with an established ecosystem and third party packages that make it easier to get up and running. With <a target="_blank" href="https://deno.com/blog/v1.25#experimental-npm-support">the announcement that Deno will support <em>most</em> npm packages</a> I can see people moving over to Deno sooner rather than later.</p>
<p>Deno also recently released <a target="_blank" href="https://deno.com/deploy">Deno Deploy</a> to public beta. This will allow users to rapidly deploy JavaScript code at the edge. This service might give the Deno company an edge (pun intended) over time and grow the user-base.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The experience I've had with Deno over the last several months has been a lot of fun. I've enjoyed working with it and am excited to see what the future brings for it. </p>
<p>Over the next few weeks I'll be writing several posts diving deeper into the Deno world.  </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Podcast Player with Transcriptions using Vue and Supabase ]]>
                </title>
                <description>
                    <![CDATA[ In this post we will walk through setting up a Podcast Player app using Supabase and Vue 3, including getting transcriptions for the podcasts.  This is a continuation of my previous post on setting up Authentication using Supabase. If you aren't fami... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-podcast-player-with-transcriptions-using-vue-supabase/</link>
                <guid isPermaLink="false">66bb92ddd2bda3e4315491ed</guid>
                
                    <category>
                        <![CDATA[ audio ]]>
                    </category>
                
                    <category>
                        <![CDATA[ projects ]]>
                    </category>
                
                    <category>
                        <![CDATA[ supabase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vue ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Brian Barrow ]]>
                </dc:creator>
                <pubDate>Mon, 28 Feb 2022 23:05:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/02/Build-Podcast-Player-app-w-transcriptions-using-Vue-Supabase@2x.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this post we will walk through setting up a Podcast Player app using Supabase and Vue 3, including getting transcriptions for the podcasts. </p>
<p>This is a continuation of my previous post on <a target="_blank" href="https://www.freecodecamp.org/news/add-supabase-authentication-to-vue/">setting up Authentication using Supabase</a>. If you aren't familiar with getting Supabase set up in your project, I highly recommend going through that post. </p>
<h2 id="heading-the-starting-code-repo">The Starting Code Repo</h2>
<p>Here is the repo from my previous post that will get you to where this post will be starting. You'll just need to set up Supabase and add your credentials/API key to a <code>.env.local</code> file to get up and running. This repo also has styling applied to it that was not included in the previous post.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/briancbarrow/vue-supabase-auth">https://github.com/briancbarrow/vue-supabase-auth</a></div>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You should be familiar with JavaScript, have had some experience with Vue 3, and you should have Node.js and NPM installed on your machine. </p>
<p>If you've gone through the previous post about Supabase Authentication or this other post on <a target="_blank" href="https://developers.deepgram.com/blog/2021/11/getting-started-with-supabase/">Getting Started with Supabase</a> you'll be good to go.</p>
<p>You will also need a <a target="_blank" href="https://console.deepgram.com/signup">free API key from Deepgram</a> for when we get to the transcription section. </p>
<h2 id="heading-getting-started">Getting Started</h2>
<p>Once you have downloaded the <a target="_blank" href="https://github.com/briancbarrow/vue-supabase-auth">repo from above</a> run <code>npm install</code> to get the packages installed for the project. </p>
<p>Add your <code>VITE_SUPABASE_URL</code> and <code>VITE_SUPABASE_ANON_KEY</code> environment variables from your the dashboard of your own Supabase project.</p>
<p>Run <code>npm run dev</code> to get the local dev server started.</p>
<p>Sign in to the app using either the Sign In form or the Magic Link form. Once you get signed in, you should see the HelloWorld component/page with a sign out button at the top. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screen-Shot-2022-02-18-at-2.20.26-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Hello World component</em></p>
<h2 id="heading-how-to-fetch-a-podcast-rss-feed">How to Fetch a Podcast RSS Feed</h2>
<p>The first thing we need to do is add functionality to get a podcast feed into our app. Create a new component in the components folder called <code>PodcastFeed.vue</code>. </p>
<p>Most podcasts have a public RSS feed that we can use to get the information we need with a simple fetch request. </p>
<p>Inside the of the <code>PodcastFeed.vue</code> component create the following form that takes in a RSS feed URL, and hooks up to a button that triggers the fetch request.</p>
<p>Note: I've tried to add comments in the code to help you understand what each part is doing.</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"podcast-input-feed"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"email"</span>&gt;</span>Podcast RSS Feed URL<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>
      <span class="hljs-comment">&lt;!-- binding the url input field to the 'url' data property --&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
        <span class="hljs-attr">type</span>=<span class="hljs-string">"url"</span>
        <span class="hljs-attr">name</span>=<span class="hljs-string">"url"</span>
        <span class="hljs-attr">id</span>=<span class="hljs-string">"url"</span>
        <span class="hljs-attr">v-model</span>=<span class="hljs-string">"url"</span>
        <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"https://rss.your-org.org/feed/"</span>
        <span class="hljs-attr">aria-describedby</span>=<span class="hljs-string">"rss-url"</span>
      /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- hooking the button click to the 'getRssFeed' method --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"getRssFeed()"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>Get Feed<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"../supabase"</span>;
<span class="hljs-keyword">import</span> { store } <span class="hljs-keyword">from</span> <span class="hljs-string">"../store"</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  setup() {
    <span class="hljs-comment">// I am initializing the url to a url I know works, so that I don't need to keep inputing a url as I'm developing.</span>
    <span class="hljs-comment">// feel free to change this to a url of your own choosing</span>
    <span class="hljs-keyword">const</span> url = ref(<span class="hljs-string">"https://anchor.fm/s/3e9db190/podcast/rss"</span>);
    <span class="hljs-comment">// initializing the podcast state to an empty object</span>
    <span class="hljs-keyword">const</span> podcast = ref({});

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRssFeed</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> feedUrl = url.value;
      <span class="hljs-keyword">return</span> (
        fetch(feedUrl)
          <span class="hljs-comment">// this returns a promise so we need to convert it to a string</span>
          .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.text())
          <span class="hljs-comment">// this next line is to parse the xml response</span>
          .then(<span class="hljs-function">(<span class="hljs-params">str</span>) =&gt;</span>
            <span class="hljs-keyword">new</span> <span class="hljs-built_in">window</span>.DOMParser().parseFromString(str, <span class="hljs-string">"text/xml"</span>)
          )
          <span class="hljs-comment">// parsing the data from the xml response and setting it into the podcast state</span>
          .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Data: "</span>, data);
            podcast.value.image_url = data
              .querySelector(<span class="hljs-string">"image"</span>)
              .querySelector(<span class="hljs-string">"url"</span>).innerHTML;
            podcast.value.title = data.querySelector(<span class="hljs-string">"title"</span>).textContent;
            podcast.value.description =
              data.querySelector(<span class="hljs-string">"description"</span>).textContent;
            podcast.value.rss_url = feedUrl;
          })
          .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"ERROR: "</span>, err);
          })
      );
    }
    <span class="hljs-keyword">return</span> {
      url,
      podcast,
      store,

      getRssFeed,
    };
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<p>With that set up, replace the HelloWorld component in the <code>App.vue</code> file with this new <code>PodcastFeed.vue</code> component:</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"store.state.user"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"signout-button"</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"signOut"</span>&gt;</span>Sign Out<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span>
  &lt;!-- Check <span class="hljs-keyword">if</span> user is available <span class="hljs-keyword">in</span> the store, <span class="hljs-keyword">if</span> not show auth compoenent --&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Auth</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"!store.state.user"</span> /&gt;</span></span>
  &lt;!-- If user is available, show the app --&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">v-else</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"app"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PodcastFeed</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> Auth <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Auth.vue"</span>;
<span class="hljs-keyword">import</span> PodcastFeed <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/PodcastFeed.vue"</span>;

<span class="hljs-keyword">import</span> { store } <span class="hljs-keyword">from</span> <span class="hljs-string">"./store"</span>;
<span class="hljs-keyword">import</span> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"./supabase"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">components</span>: {
    PodcastFeed,
    Auth,
  },
  setup() {
    <span class="hljs-comment">// we initially verify if a user is logged in with Supabase</span>
    store.state.user = supabase.auth.user();
    <span class="hljs-comment">// we then set up a listener to update the store when the user changes either by logging in or out</span>
    supabase.auth.onAuthStateChange(<span class="hljs-function">(<span class="hljs-params">event, session</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (event == <span class="hljs-string">"SIGNED_OUT"</span>) {
        store.state.user = <span class="hljs-literal">null</span>;
      } <span class="hljs-keyword">else</span> {
        store.state.user = session.user;
      }
    });

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">signOut</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> { error } = <span class="hljs-keyword">await</span> supabase.auth.signOut();
    }

    <span class="hljs-keyword">return</span> {
      store,

      signOut,
    };
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p>So now the app should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screen-Shot-2022-02-18-at-2.38.40-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>App after adding PodcastFeed.vue</em></p>
<p>When you click the button, the data that comes back from the fetch request will show up in the console. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/xml-data-from-rss.png" alt="Image" width="600" height="400" loading="lazy">
<em>Parsed XML Data</em></p>
<p>In the <code>getRssFeed</code> method we parse that data and then take the information we need and add it to the <code>podcast</code> state data. We need to display that data so that the user can know the request was successful. We also want to add better error messaging in case the request fails. </p>
<p>Create a new component called <code>PodcastInfo.vue</code> and add the following code:</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"podcast-info"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"image-container"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">:src</span>=<span class="hljs-string">"podcast.image_url"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"podcast-text"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"title-desc"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"title"</span>&gt;</span>
          {{ podcast.title }}
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"desc"</span>&gt;</span>
          {{ podcast.description }}
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { store } <span class="hljs-keyword">from</span> <span class="hljs-string">"../store"</span>;
<span class="hljs-keyword">import</span> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"../supabase"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">props</span>: {
    <span class="hljs-attr">podcast</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">Object</span>,
      <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
    },
  },
  <span class="hljs-attr">computed</span>: {},
  <span class="hljs-attr">methods</span>: {},
  setup() {},
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"info-error"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>There was an error with your request<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>Check your RSS feed URL and try again.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<p>Then update the  <code>PodcastFeed.vue</code> to the following in order to bring in the component:</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"podcast-input-feed"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"email"</span>&gt;</span>Podcast RSS Feed URL<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>
      <span class="hljs-comment">&lt;!-- binding the url input field to the 'url' data property --&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
        <span class="hljs-attr">type</span>=<span class="hljs-string">"url"</span>
        <span class="hljs-attr">name</span>=<span class="hljs-string">"url"</span>
        <span class="hljs-attr">id</span>=<span class="hljs-string">"url"</span>
        <span class="hljs-attr">v-model</span>=<span class="hljs-string">"url"</span>
        <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"https://rss.your-org.org/feed/"</span>
        <span class="hljs-attr">aria-describedby</span>=<span class="hljs-string">"rss-url"</span>
      /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- hooking the button click to the 'getRssFeed' method --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"getRssFeed()"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>Get Feed<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Adding in these two new components --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">podcast-info</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"podcast.title &amp;&amp; !requestError"</span> <span class="hljs-attr">:podcast</span>=<span class="hljs-string">"podcast"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"../supabase"</span>;
<span class="hljs-keyword">import</span> { store } <span class="hljs-keyword">from</span> <span class="hljs-string">"../store"</span>;

<span class="hljs-keyword">import</span> PodcastInfo <span class="hljs-keyword">from</span> <span class="hljs-string">"./PodcastInfo.vue"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">components</span>: {
    PodcastInfo,
  },

  setup() {
    <span class="hljs-comment">// I am initializing the url to a url I know works, so that I don't need to keep inputing a url as I'm developing.</span>
    <span class="hljs-comment">// feel free to change this to a url of your own choosing</span>
    <span class="hljs-keyword">const</span> url = ref(<span class="hljs-string">"https://anchor.fm/s/3e9db190/podcast/rss"</span>);
    <span class="hljs-comment">// initializing the podcast state to an empty object</span>
    <span class="hljs-keyword">const</span> podcast = ref({});
    <span class="hljs-keyword">const</span> requestError = ref(<span class="hljs-literal">false</span>);

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRssFeed</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> feedUrl = url.value;
      <span class="hljs-keyword">return</span> (
        fetch(feedUrl)
          <span class="hljs-comment">// this returns a promise so we need to convert it to a string</span>
          .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.text())
          <span class="hljs-comment">// this next line is to parse the xml response</span>
          .then(<span class="hljs-function">(<span class="hljs-params">str</span>) =&gt;</span>
            <span class="hljs-keyword">new</span> <span class="hljs-built_in">window</span>.DOMParser().parseFromString(str, <span class="hljs-string">"text/xml"</span>)
          )
          <span class="hljs-comment">// parsing the data from the xml response and setting it into the podcast state</span>
          .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Data: "</span>, data);
            podcast.value.image_url = data
              .querySelector(<span class="hljs-string">"image"</span>)
              .querySelector(<span class="hljs-string">"url"</span>).innerHTML;
            podcast.value.title = data.querySelector(<span class="hljs-string">"title"</span>).textContent;
            podcast.value.description =
              data.querySelector(<span class="hljs-string">"description"</span>).textContent;
            podcast.value.rss_url = feedUrl;
          })
          .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
            requestError.value = <span class="hljs-literal">true</span>;
          })
      );
    }
    <span class="hljs-keyword">return</span> {
      url,
      podcast,
      store,
      requestError,

      getRssFeed,
    };
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<p>Now when you click the "Get Feed" button, you should see the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screen-Shot-2022-02-18-at-3.05.15-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now that we can get the info displayed, we can wire up the app to save the info to Supabase. </p>
<h2 id="heading-how-to-add-a-table-to-the-supabase-db">How to Add a Table to the Supabase DB</h2>
<p>The first thing we need to do is add a table to our Supabase DB. In the Dashboard for your project on Supabase, select the Table Editor and click the <code>New table</code> button. I name the new table <code>podcasts</code>. Enable the Row Level Security (this makes our DB more secure) and add the following columns:</p>
<ul>
<li>id (this column should be filled in for you when you create a new table)</li>
<li>created_at</li>
<li>name</li>
<li>image_url</li>
<li>description</li>
<li>rss_url</li>
<li>user_id (for this one, we want to link it via foreign key to our users table created by the Auth service. Click the chain-link icon to get that set up and link it to the <code>users</code> table on the <code>id</code> column.)</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screen-Shot-2022-01-19-at-9.08.50-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>podcasts table setup</em></p>
<p>Because we enabled the Row Level Security, the table won't let anything get inserted until we update the policies for it. </p>
<p>Under the Authentication tab there is a section called 'Policies'. There you should see the <code>podcasts</code> table and a button to create a new policy. When you click on it, it will give you the option to create a policy from a template. Choose the template called 'Enable insert access for authenticated users only'. Now, only users that are authenticated have access to insert anything into the table. </p>
<p>When Supabase runs the <code>insert</code> command, it will automatically run a <code>select</code> command and return the newly inserted row. Because of this, we also have to add a policy to the table allowing the user <code>SELECT</code> access. </p>
<p>Create a new policy with the name <code>Enable select based on userid</code> and then in the <code>USING expression</code> section put <code>(uid() = user_id)</code>. That will prevent users from reading other users' information, while still giving them access to their own podcasts in the table.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/select-user-policy.png" alt="Image" width="600" height="400" loading="lazy">
<em>Select based on userid policy</em></p>
<h2 id="heading-how-to-link-up-the-ui-to-the-db-so-the-user-can-save-podcasts">How to Link Up the UI to the DB So the User Can Save Podcasts</h2>
<p>To add a podcast to our DB, we will first add a button to the <code>PodcastInfo</code> component. Add this code to the bottom of the <code>&lt;div class="podcast-info"&gt;</code>:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"addPodcast"</span>&gt;</span>Add to My Podcasts<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Now add a method called <code>addPodcast</code> to the setup function of the component like this. Don't forget to add the <code>props</code> to the argument of the setup function.</p>
<pre><code class="lang-js">setup(props) {
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addPodcast</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// Setting up the podcast object to send to supabase</span>
      <span class="hljs-keyword">const</span> podcast = {
        <span class="hljs-attr">name</span>: props.podcast.title,
        <span class="hljs-attr">image_url</span>: props.podcast.image_url,
        <span class="hljs-attr">description</span>: props.podcast.description,
        <span class="hljs-attr">rss_url</span>: props.podcast.rss_url,
        <span class="hljs-attr">user_id</span>: store.state.user.id,
      };
      <span class="hljs-comment">// calling supabase method to insert into the db</span>
      supabase
        .from(<span class="hljs-string">"podcasts"</span>)
        .insert(podcast)
        .then(<span class="hljs-function">(<span class="hljs-params">{ body }</span>) =&gt;</span> {
          store.addPodcastToStore(body[<span class="hljs-number">0</span>]);
        })
        .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
          <span class="hljs-built_in">console</span>.log(err);
        });
    }

    <span class="hljs-keyword">return</span> {
      addPodcast,
    };
  },
</code></pre>
<p>You can see in the <code>.then</code> statement we call a method from the global store. Update the <code>store.js</code> file to the following:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { reactive } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> store = {
  <span class="hljs-attr">state</span>: reactive({
    <span class="hljs-attr">user</span>: {},
    <span class="hljs-comment">// adding podcasts array to global store</span>
    <span class="hljs-attr">podcasts</span>: [],
  }),

  <span class="hljs-comment">// adding addPodcastToStore method to store object</span>
  addPodcastToStore(podcast) {
    <span class="hljs-built_in">this</span>.state.podcasts.push(podcast);
  },
};
</code></pre>
<p>Now when we click the "Add To My Podcasts" button, the app makes a call to Supabase and then takes the result of that call and adds it to the podcasts array in the global store. (If you are getting a 403 error, make sure you set up the policies correctly. Maybe try restarting the dev server too.)</p>
<p>If a podcast is already in a user's list of podcasts, we don't want to let them click the add button again. To prevent this we need to first call Supabase to get all of the user's podcasts, and then check if the podcast they are looking at is in that list.</p>
<p>This method will not be specific to any one component so we want to create it inside of the global store. That way, any component has access to it. Add this method to the <code>store.js</code> file underneath the <code>addPodcastToStore</code> method:</p>
<pre><code class="lang-js">getPodcastsFromDB() {
    supabase
        .from(<span class="hljs-string">"podcasts"</span>)
        .select(<span class="hljs-string">"*"</span>)
        .then(<span class="hljs-function">(<span class="hljs-params">{ body }</span>) =&gt;</span> {
            <span class="hljs-built_in">this</span>.state.podcasts = body;
        });
},
</code></pre>
<p>Then we want to update the method to be called whenever a user signs in. Inside of <code>App.vue</code> change the <code>onAuthStateChange</code> handler to this:</p>
<pre><code class="lang-js">supabase.auth.onAuthStateChange(<span class="hljs-function">(<span class="hljs-params">event, session</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (event == <span class="hljs-string">"SIGNED_OUT"</span>) {
        store.state.user = <span class="hljs-literal">null</span>;
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// make call to supabase to get Podcasts for the user</span>
        store.getPodcastsFromDB();
        store.state.user = session.user;
    }
});
</code></pre>
<p>Now update the <code>PodcastInfo.vue</code> file to this in order to display to the user if a podcast is already in their library.</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"podcast-info"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"image-container"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">:src</span>=<span class="hljs-string">"podcast.image_url"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"podcast-text"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"title-desc"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"title"</span>&gt;</span>
          {{ podcast.title }}
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"desc"</span>&gt;</span>
          {{ podcast.description }}
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Add check in markup to remove the button if the podcast already exists in the user's list --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"isInUserPodcasts"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"in-podcasts"</span>&gt;</span>In Your Podcasts<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">v-else</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"addPodcast"</span>&gt;</span>Add to My Podcasts<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-comment">// importing computed</span>
<span class="hljs-keyword">import</span> { ref, computed } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> { store } <span class="hljs-keyword">from</span> <span class="hljs-string">"../store"</span>;
<span class="hljs-keyword">import</span> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"../supabase"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">props</span>: {
    <span class="hljs-attr">podcast</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">Object</span>,
      <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
    },
  },
  setup(props) {
    <span class="hljs-comment">// add computed property checking if podcast is in user's podcasts</span>
    <span class="hljs-keyword">const</span> isInUserPodcasts = computed(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">return</span> store.state.podcasts.some(
        <span class="hljs-function">(<span class="hljs-params">podcast</span>) =&gt;</span> podcast.rss_url === props.podcast.rss_url
      );
    });

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addPodcast</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// check if podcast is already in user's podcasts</span>
      <span class="hljs-keyword">if</span> (isInUserPodcasts.value) {
        alert(<span class="hljs-string">"You already have this podcast in your list!"</span>);
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">const</span> podcast = {
          <span class="hljs-attr">name</span>: props.podcast.title,
          <span class="hljs-attr">image_url</span>: props.podcast.image_url,
          <span class="hljs-attr">description</span>: props.podcast.description,
          <span class="hljs-attr">rss_url</span>: props.podcast.rss_url,
          <span class="hljs-attr">user_id</span>: store.state.user.id,
        };
        supabase
          .from(<span class="hljs-string">"podcasts"</span>)
          .insert(podcast)
          .then(<span class="hljs-function">(<span class="hljs-params">{ body }</span>) =&gt;</span> {
            store.addPodcastToStore(body[<span class="hljs-number">0</span>]);
          })
          .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
            <span class="hljs-built_in">console</span>.log(err);
          });
      }
    }

    <span class="hljs-keyword">return</span> {
      <span class="hljs-comment">// exposing the isInUserPodcasts computed property</span>
      isInUserPodcasts,
      addPodcast,
    };
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p>Next, we want to display a list of podcasts that the user has added to their library. We have the list in the global store, so we just need to loop through them to display the needed info. </p>
<p>Add the following to the bottom of the <code>PodcastFeed.vue</code> template:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- Loop through podcasts and display them --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"feeds"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>Your Podcast Feeds<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">v-for</span>=<span class="hljs-string">"pod in store.state.podcasts"</span> <span class="hljs-attr">:key</span>=<span class="hljs-string">"pod.id"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">:href</span>=<span class="hljs-string">"`/podcast/${pod.id}`"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">:src</span>=<span class="hljs-string">"pod.image_url"</span> <span class="hljs-attr">:alt</span>=<span class="hljs-string">"`logo for ${pod.name}`"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span> /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>{{ pod.name }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<h2 id="heading-how-to-set-up-the-other-pages-for-our-podcast-app">How to Set Up the Other Pages for Our Podcast App</h2>
<p>Now that we have the list of podcasts being displayed in the app, we need a way to navigate to an individual podcast. We have the markup set to link to a path like <code>/podcast/{podcast_id}</code>. Now we need to update our app to handle routes like this. </p>
<p>First, install vue-router using <code>npm i vue-router</code>.</p>
<p>Then create a file called <code>router.js</code> with the following code:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Import Vue Router</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> VueRouter <span class="hljs-keyword">from</span> <span class="hljs-string">"vue-router"</span>;

<span class="hljs-comment">// Import the components that will show on the different routes</span>
<span class="hljs-keyword">import</span> PodcastFeed <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/PodcastFeed.vue"</span>;
<span class="hljs-keyword">import</span> PodcastDetail <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/PodcastDetail.vue"</span>;

<span class="hljs-comment">// Set up the routes</span>
<span class="hljs-keyword">const</span> routes = [
  { <span class="hljs-attr">path</span>: <span class="hljs-string">"/"</span>, <span class="hljs-attr">component</span>: PodcastFeed },
  { <span class="hljs-attr">path</span>: <span class="hljs-string">"/podcast/:id"</span>, <span class="hljs-attr">component</span>: PodcastDetail },
];

<span class="hljs-comment">// Initialize the router</span>
<span class="hljs-keyword">const</span> router = VueRouter.createRouter({
  <span class="hljs-attr">history</span>: VueRouter.createWebHistory(),
  routes,
});

<span class="hljs-comment">// Export the router</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router;
</code></pre>
<p>Update <code>main.js</code> to use the router in the Vue app:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { createApp } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> router <span class="hljs-keyword">from</span> <span class="hljs-string">"./router"</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"./App.vue"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>;

<span class="hljs-keyword">const</span> app = createApp(App);
app.use(router);
app.mount(<span class="hljs-string">"#app"</span>);
</code></pre>
<p>Update <code>App.vue</code> to show the <code>router-view</code> component provided by Vue Router:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"store.state.user"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"signout-button"</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"signOut"</span>&gt;</span>Sign Out<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- Check if user is available in the store, if not show auth compoenent --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Auth</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"!store.state.user"</span> /&gt;</span>
  <span class="hljs-comment">&lt;!-- If user is available, show the app --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">v-else</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"app"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">router-view</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">router-view</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<p>Now create a <code>PodcastDetail.vue</code> file that will display the episode info for the podcast:</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
  &lt;!-- Basic layout <span class="hljs-keyword">for</span> showing podcast info --&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"podcast-detail"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">:src</span>=<span class="hljs-string">"podcast.image_url"</span> <span class="hljs-attr">:alt</span>=<span class="hljs-string">"podcast.name"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>{{ podcast.name }}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{{ podcast.description }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>Episodes<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Looping through each episode of a podcast and displaying episode info --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span>
        <span class="hljs-attr">v-for</span>=<span class="hljs-string">"episode in episodes"</span>
        <span class="hljs-attr">:key</span>=<span class="hljs-string">"episode.guid || episode.link"</span>
        <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>
      &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"info"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>{{ episode.title }}<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">audio</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span> <span class="hljs-attr">controls</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">:src</span>=<span class="hljs-string">"episode.url"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"audio/mpeg"</span> /&gt;</span>
            Display
          <span class="hljs-tag">&lt;/<span class="hljs-name">audio</span>&gt;</span>          
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>        
      <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-comment">// Importing necessary methods</span>
<span class="hljs-keyword">import</span> { ref, onMounted } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> { useRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue-router"</span>;
<span class="hljs-keyword">import</span> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"../supabase"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  setup() {
    <span class="hljs-keyword">const</span> route = useRoute();
    <span class="hljs-keyword">const</span> podcast = ref({});
    <span class="hljs-keyword">const</span> episodes = ref([]);

    <span class="hljs-comment">// Getting podcast info from the database</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPodcastData</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> {
        <span class="hljs-attr">data</span>: [podcastinfo],
      } = <span class="hljs-keyword">await</span> supabase.from(<span class="hljs-string">"podcasts"</span>).select().eq(<span class="hljs-string">"id"</span>, route.params.id);
      podcast.value = podcastinfo;

      <span class="hljs-comment">// Making call to episode url to get episode info</span>
      getEpisodes(podcastinfo.rss_url);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getEpisodes</span>(<span class="hljs-params">url</span>) </span>{
      fetch(url)
        .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.text())
        .then(<span class="hljs-function">(<span class="hljs-params">str</span>) =&gt;</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">window</span>.DOMParser().parseFromString(str, <span class="hljs-string">"text/xml"</span>))
        .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
          <span class="hljs-comment">// Finding all the "item" tags in the xml response which will contain the episode info</span>
          <span class="hljs-keyword">const</span> items = data.querySelectorAll(<span class="hljs-string">"item"</span>);
          <span class="hljs-comment">// Looping through each item and getting the episode info and pushing it to the 'episodes' array</span>
          items.forEach(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> {
            <span class="hljs-keyword">let</span> url;

            <span class="hljs-comment">// Not every podcast episode is going to have the `enclosure` tag, so we need to check if it exists</span>
            <span class="hljs-keyword">try</span> {
              url = item.querySelector(<span class="hljs-string">"enclosure"</span>).getAttribute(<span class="hljs-string">"url"</span>);
            } <span class="hljs-keyword">catch</span> (e) {
              <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"error"</span>, e);
              url = item.querySelector(<span class="hljs-string">"link"</span>).innerHTML;
            }

            episodes.value.push({
              <span class="hljs-comment">// this `title` and the `guid` properties looks a little different because the title contains CDATA tags which need to be grabbed with the 'childNodes' property</span>
              <span class="hljs-attr">title</span>: item.querySelector(<span class="hljs-string">"title"</span>).childNodes[<span class="hljs-number">0</span>].textContent,
              <span class="hljs-attr">link</span>: url,
              <span class="hljs-attr">url</span>: url,
              <span class="hljs-attr">description</span>: item.querySelector(<span class="hljs-string">"description"</span>).innerHTML,
              <span class="hljs-attr">pubDate</span>: item.querySelector(<span class="hljs-string">"pubDate"</span>).innerHTML,
              <span class="hljs-attr">guid</span>: item.querySelector(<span class="hljs-string">"guid"</span>).childNodes[<span class="hljs-number">0</span>].textContent,
            });
          });
        })
        .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
          alert(<span class="hljs-string">"Couldn't get episodes"</span>, err);
        });
    }

    onMounted(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Getting podcast info from the database once the component is mounted</span>
      getPodcastData();
    });

    <span class="hljs-keyword">return</span> {
      podcast,
      episodes,
    };
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p>With those changes, we can now see the individual episodes of the podcast and can play them using the <code>&lt;audio&gt;</code> html tag. </p>
<h2 id="heading-how-to-get-the-transcriptions-of-the-podcasts">How to Get the Transcriptions of the Podcasts</h2>
<p>The last step is to get transcriptions for the podcasts, and then save them to our database.</p>
<p>If you haven't yet, you'll need to get a <a target="_blank" href="https://console.deepgram.com/signup">free API key from Deepgram</a> in order to process the audio and get the transcriptions. </p>
<p>Once you get the API key, add it to your <code>.env.local</code> file as <code>VITE_DEEPGRAM_KEY</code>. Make sure you restart your dev server here, otherwise you'll probably get a 403 Forbidden error when we finally call the API.</p>
<p>Then add this code to a deepgram.js file in the <code>src</code> folder.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> deepgramKey = <span class="hljs-keyword">import</span>.meta.env.VITE_DEEPGRAM_KEY;

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deepgram</span>(<span class="hljs-params">url</span>) </span>{
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(
    <span class="hljs-string">"https://api.deepgram.com/v1/listen?punctuate=true&amp;diarize=true&amp;utterances=true"</span>,
    {
      <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
      <span class="hljs-attr">headers</span>: {
        <span class="hljs-attr">Authorization</span>: <span class="hljs-string">`Token <span class="hljs-subst">${deepgramKey}</span>`</span>,
        <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
      },
      <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
        <span class="hljs-attr">url</span>: url,
      }),
    }
  );
  <span class="hljs-keyword">const</span> json = <span class="hljs-keyword">await</span> response.json();
  <span class="hljs-keyword">return</span> json.results;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> deepgram;
</code></pre>
<p>This gives us a utility function we can import into our app in other files to call the Deepgram API in order to get the transcriptions. We added the punctuate, diarize, and utterances to the URL as parameters to get a cleaner transcription that is easier to read. </p>
<p>Now that we have that, we need to add some functionality to the <code>PodcastDetail.vue</code> file. I'll walk through the changes and then later will put up the final code for the file.</p>
<p>First we need to have some state to keep track of the transcriptions we get, and also to have some loading state once we click a button to get a transcription. So we'll add these two lines to our setup function:</p>
<pre><code class="lang-js"><span class="hljs-keyword">let</span> transcriptions = ref({});
<span class="hljs-keyword">const</span> episodeTranscriptionLoading = ref([]);
</code></pre>
<p>Don't forget to add them to the return object of the <code>setup</code> function.</p>
<p>Then add this function to make the request to Deepgram, and then add the transcription to the local <code>transcriptions</code> object.</p>
<pre><code class="lang-js"><span class="hljs-comment">// Function to get a transcription from Deepgram, passing in the episode url</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTranscription</span>(<span class="hljs-params">episode</span>) </span>{
    <span class="hljs-comment">// setting the loading state to true for the episode</span>
    episodeTranscriptionLoading.value.push(episode.guid);
    <span class="hljs-keyword">const</span> transcription = <span class="hljs-keyword">await</span> deepgram(episode.url);
    <span class="hljs-comment">// setting a unique id for the episode transcription</span>
    transcriptions.value[<span class="hljs-string">`<span class="hljs-subst">${podcast.value.id}</span>---<span class="hljs-subst">${episode.guid}</span>`</span>] =
        transcription;
    <span class="hljs-comment">// removing the loading state for the episode</span>
    episodeTranscriptionLoading.value.splice(
        episodeTranscriptionLoading.value.indexOf(episode.guid),
        <span class="hljs-number">1</span>
    );
}
</code></pre>
<p>Make sure you import the deepgram function from <code>deepgram.js</code> at the top of the script tag.</p>
<p>Then update the template to this:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- Basic layout for showing podcast info --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"podcast-detail"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">:src</span>=<span class="hljs-string">"podcast.image_url"</span> <span class="hljs-attr">:alt</span>=<span class="hljs-string">"podcast.name"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>{{ podcast.name }}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{{ podcast.description }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>Episodes<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Looping through each episode of a podcast and displaying episode info --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span>
        <span class="hljs-attr">v-for</span>=<span class="hljs-string">"episode in episodes"</span>
        <span class="hljs-attr">:key</span>=<span class="hljs-string">"episode.guid || episode.link"</span>
        <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>
      &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"info"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>{{ episode.title }}<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">audio</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span> <span class="hljs-attr">controls</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">:src</span>=<span class="hljs-string">"episode.url"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"audio/mpeg"</span> /&gt;</span>
            Display
          <span class="hljs-tag">&lt;/<span class="hljs-name">audio</span>&gt;</span>
            <span class="hljs-comment">&lt;!-- button to get transcriptions --&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
            <span class="hljs-attr">v-if</span>=<span class="hljs-string">"!transcriptions[`${podcast.id}---${episode.guid}`]"</span>
            @<span class="hljs-attr">click.prevent</span>=<span class="hljs-string">"getTranscription(episode)"</span>
            <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>
          &gt;</span>
            {{
              episodeTranscriptionLoading.includes(episode.guid)
                ? "Loading..."
                : "Get Transcription"
            }}
          <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-comment">&lt;!-- box to display the transcription --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
          <span class="hljs-attr">v-if</span>=<span class="hljs-string">"transcriptions[`${podcast.id}---${episode.guid}`]"</span>
          <span class="hljs-attr">class</span>=<span class="hljs-string">"transcription"</span>
        &gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            {{
              transcriptions[`${podcast.id}---${episode.guid}`].channels[0]
                .alternatives[0].transcript
            }}
          <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> 
      <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<h2 id="heading-how-to-save-the-transcriptions">How to Save the Transcriptions</h2>
<p>Now that we can get the transcriptions, we need to add the functionality to save them to Supabase. </p>
<p>First, go create a table in Supabase like we did above, but this time name the table <code>transcriptions</code>. You'll want the following as the columns:</p>
<ul>
<li><strong>id</strong> – varchar (primary) Also remove the "Is Identity" check mark in the settings for this column</li>
<li><strong>podcast_id</strong> – int8</li>
<li><strong>episode_guid</strong> – varchar</li>
<li><strong>transcript</strong> – text</li>
<li><strong>user_id</strong> – uuid (You'll need to link this to the user table by clicking on the link icon)</li>
<li><strong>created_at</strong> – timestamptz</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screen-Shot-2022-02-22-at-4.40.52-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>columns for transcriptions table</em></p>
<p>Once that table is set up, we can add a reactive property called <code>savedTranscriptions</code> to the component and then add the following code to save the transcriptions to Supabase. Then we'll store them in the object <code>savedTranscriptions</code>.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">saveTranscription</span>(<span class="hljs-params">podcastId, episodeGuid</span>) </span>{
    supabase
        .from(<span class="hljs-string">"transcriptions"</span>)
        .insert({
        <span class="hljs-attr">id</span>: <span class="hljs-string">`<span class="hljs-subst">${podcastId}</span>---<span class="hljs-subst">${episodeGuid}</span>`</span>,
        <span class="hljs-attr">podcast_id</span>: podcastId,
        <span class="hljs-attr">episode_guid</span>: episodeGuid,
        <span class="hljs-attr">transcript</span>:
        transcriptions.value[<span class="hljs-string">`<span class="hljs-subst">${podcastId}</span>---<span class="hljs-subst">${episodeGuid}</span>`</span>].channels[<span class="hljs-number">0</span>]
        .alternatives[<span class="hljs-number">0</span>].transcript,
        <span class="hljs-attr">user_id</span>: store.state.user.id,
    })
        .then(<span class="hljs-function">(<span class="hljs-params">{ data: [transcriptObject] }</span>) =&gt;</span> {
        savedTranscriptions.value[transcriptObject.id] =
            transcriptObject.transcript;
    });
}
</code></pre>
<p>Once a user has a transcription saved, we need to display it whenever they re-visit a page. Add this function to get that data from Supabase:</p>
<pre><code class="lang-js"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTranscriptions</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> { <span class="hljs-attr">data</span>: transcriptions } = <span class="hljs-keyword">await</span> supabase
    .from(<span class="hljs-string">"transcriptions"</span>)
    .select()
    .eq(<span class="hljs-string">"podcast_id"</span>, podcast.value.id);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Transcriptions"</span>, transcriptions);
    transcriptions.forEach(<span class="hljs-function">(<span class="hljs-params">transcript</span>) =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"id"</span>, transcript.id);
        savedTranscriptions.value[transcript.id] = transcript.transcript;
    });
}
</code></pre>
<p>We will want this called whenever a user hits this page, but not until we get the podcast information. Add a call to <code>getTranscriptions</code> to the bottom of the <code>getPodcastData</code> function to do that.</p>
<p>The last thing to do is to update the template to include the save buttons and to display transcriptions if they are in the saved objects. The final code for the <code>PodcastDetail.vue</code> should then look like this:</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
  &lt;!-- Basic layout <span class="hljs-keyword">for</span> showing podcast info --&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"podcast-detail"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">:src</span>=<span class="hljs-string">"podcast.image_url"</span> <span class="hljs-attr">:alt</span>=<span class="hljs-string">"podcast.name"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>{{ podcast.name }}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{{ podcast.description }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>Episodes<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Looping through each episode of a podcast and displaying episode info --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span>
        <span class="hljs-attr">v-for</span>=<span class="hljs-string">"episode in episodes"</span>
        <span class="hljs-attr">:key</span>=<span class="hljs-string">"episode.guid || episode.link"</span>
        <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>
      &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"info"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>{{ episode.title }}<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">audio</span> <span class="hljs-attr">class</span>=<span class="hljs-string">""</span> <span class="hljs-attr">controls</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">:src</span>=<span class="hljs-string">"episode.url"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"audio/mpeg"</span> /&gt;</span>
            Display
          <span class="hljs-tag">&lt;/<span class="hljs-name">audio</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
            <span class="hljs-attr">v-if</span>=<span class="hljs-string">"savedTranscriptions[`${podcast.id}---${episode.guid}`]"</span>
            <span class="hljs-attr">disabled</span>
          &gt;</span>
            Transcription Saved
          <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
            <span class="hljs-attr">v-else-if</span>=<span class="hljs-string">"
              !transcriptions[`${podcast.id}---${episode.guid}`] &amp;&amp;
              !savedTranscriptions[`${podcast.id}---${episode.guid}`]
            "</span>
            @<span class="hljs-attr">click.prevent</span>=<span class="hljs-string">"getTranscription(episode)"</span>
            <span class="hljs-attr">class</span>=<span class="hljs-string">""</span>
          &gt;</span>
            {{
              episodeTranscriptionLoading.includes(episode.guid)
                ? "Loading..."
                : "Get Transcription"
            }}
          <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
            <span class="hljs-attr">v-if</span>=<span class="hljs-string">"
              transcriptions[`${podcast.id}---${episode.guid}`] &amp;&amp;
              !savedTranscriptions[`${podcast.id}---${episode.guid}`]
            "</span>
            <span class="hljs-attr">class</span>=<span class="hljs-string">"save"</span>
            @<span class="hljs-attr">click.prevent</span>=<span class="hljs-string">"saveTranscription(podcast.id, episode.guid)"</span>
          &gt;</span>
            Save Transcription
          <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
          <span class="hljs-attr">v-if</span>=<span class="hljs-string">"savedTranscriptions[`${podcast.id}---${episode.guid}`]"</span>
          <span class="hljs-attr">class</span>=<span class="hljs-string">"transcription"</span>
        &gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            {{ savedTranscriptions[`${podcast.id}---${episode.guid}`] }}
          <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
          <span class="hljs-attr">v-else-if</span>=<span class="hljs-string">"transcriptions[`${podcast.id}---${episode.guid}`]"</span>
          <span class="hljs-attr">class</span>=<span class="hljs-string">"transcription"</span>
        &gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            {{
              transcriptions[`${podcast.id}---${episode.guid}`].channels[0]
                .alternatives[0].transcript
            }}
          <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-comment">// Importing necessary methods</span>
<span class="hljs-keyword">import</span> { ref, onMounted } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> { useRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue-router"</span>;
<span class="hljs-keyword">import</span> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"../supabase"</span>;
<span class="hljs-keyword">import</span> { store } <span class="hljs-keyword">from</span> <span class="hljs-string">"../store"</span>;
<span class="hljs-keyword">import</span> deepgram <span class="hljs-keyword">from</span> <span class="hljs-string">"../deepgram"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  setup() {
    <span class="hljs-keyword">const</span> route = useRoute();
    <span class="hljs-keyword">const</span> podcast = ref({});
    <span class="hljs-keyword">const</span> episodes = ref([]);
    <span class="hljs-keyword">let</span> transcriptions = ref({});
    <span class="hljs-keyword">let</span> savedTranscriptions = ref({});
    <span class="hljs-keyword">const</span> episodeTranscriptionLoading = ref([]);

    <span class="hljs-comment">// Getting podcast info from the database</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPodcastData</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> {
        <span class="hljs-attr">data</span>: [podcastinfo],
      } = <span class="hljs-keyword">await</span> supabase.from(<span class="hljs-string">"podcasts"</span>).select().eq(<span class="hljs-string">"id"</span>, route.params.id);
      podcast.value = podcastinfo;

      <span class="hljs-comment">// Making call to episode url to get episode info</span>
      getEpisodes(podcastinfo.rss_url);
      <span class="hljs-keyword">await</span> getTranscriptions();
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getEpisodes</span>(<span class="hljs-params">url</span>) </span>{
      fetch(url)
        .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.text())
        .then(<span class="hljs-function">(<span class="hljs-params">str</span>) =&gt;</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">window</span>.DOMParser().parseFromString(str, <span class="hljs-string">"text/xml"</span>))
        .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
          <span class="hljs-comment">// Finding all the "item" tags in the xml response which will contain the episode info</span>
          <span class="hljs-keyword">const</span> items = data.querySelectorAll(<span class="hljs-string">"item"</span>);
          <span class="hljs-comment">// Looping through each item and getting the episode info and pushing it to the 'episodes' array</span>
          items.forEach(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> {
            <span class="hljs-keyword">let</span> url;

            <span class="hljs-comment">// Not every podcast episode is going to have the `enclosure` tag, so we need to check if it exists</span>
            <span class="hljs-keyword">try</span> {
              url = item.querySelector(<span class="hljs-string">"enclosure"</span>).getAttribute(<span class="hljs-string">"url"</span>);
            } <span class="hljs-keyword">catch</span> (e) {
              <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"error"</span>, e);
              url = item.querySelector(<span class="hljs-string">"link"</span>).innerHTML;
            }

            episodes.value.push({
              <span class="hljs-comment">// this `title` and the `guid` properties looks a little different because the title contains CDATA tags which need to be grabbed with the 'childNodes' property</span>
              <span class="hljs-attr">title</span>: item.querySelector(<span class="hljs-string">"title"</span>).childNodes[<span class="hljs-number">0</span>].textContent,
              <span class="hljs-attr">link</span>: url,
              <span class="hljs-attr">url</span>: url,
              <span class="hljs-attr">description</span>: item.querySelector(<span class="hljs-string">"description"</span>).innerHTML,
              <span class="hljs-attr">pubDate</span>: item.querySelector(<span class="hljs-string">"pubDate"</span>).innerHTML,
              <span class="hljs-attr">guid</span>: item.querySelector(<span class="hljs-string">"guid"</span>).childNodes[<span class="hljs-number">0</span>].textContent,
            });
          });
        })
        .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
          alert(<span class="hljs-string">"Couldn't get episodes"</span>, err);
        });
    }

    <span class="hljs-comment">// Function to get a transcription from Deepgram, passing in the episode url</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTranscription</span>(<span class="hljs-params">episode</span>) </span>{
      <span class="hljs-comment">// setting the loading state to true for the episode</span>
      episodeTranscriptionLoading.value.push(episode.guid);
      <span class="hljs-keyword">const</span> transcription = <span class="hljs-keyword">await</span> deepgram(episode.url);
      <span class="hljs-comment">// setting a unique id for the episode transcription</span>
      transcriptions.value[<span class="hljs-string">`<span class="hljs-subst">${podcast.value.id}</span>---<span class="hljs-subst">${episode.guid}</span>`</span>] =
        transcription;
      <span class="hljs-comment">// removing the loading state for the episode</span>
      episodeTranscriptionLoading.value.splice(
        episodeTranscriptionLoading.value.indexOf(episode.guid),
        <span class="hljs-number">1</span>
      );
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">saveTranscription</span>(<span class="hljs-params">podcastId, episodeGuid</span>) </span>{
      <span class="hljs-built_in">console</span>.log(
        <span class="hljs-string">"saving transcription"</span>,
        transcriptions.value[<span class="hljs-string">`<span class="hljs-subst">${podcastId}</span>---<span class="hljs-subst">${episodeGuid}</span>`</span>]
      );
      supabase
        .from(<span class="hljs-string">"transcriptions"</span>)
        .insert({
          <span class="hljs-attr">id</span>: <span class="hljs-string">`<span class="hljs-subst">${podcastId}</span>---<span class="hljs-subst">${episodeGuid}</span>`</span>,
          <span class="hljs-attr">podcast_id</span>: podcastId,
          <span class="hljs-attr">episode_guid</span>: episodeGuid,
          <span class="hljs-attr">transcript</span>:
            transcriptions.value[<span class="hljs-string">`<span class="hljs-subst">${podcastId}</span>---<span class="hljs-subst">${episodeGuid}</span>`</span>].channels[<span class="hljs-number">0</span>]
              .alternatives[<span class="hljs-number">0</span>].transcript,
          <span class="hljs-attr">user_id</span>: store.state.user.id,
        })
        .then(<span class="hljs-function">(<span class="hljs-params">{ data: [transcriptObject] }</span>) =&gt;</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"saved"</span>, transcriptObject);
          savedTranscriptions.value[transcriptObject.id] =
            transcriptObject.transcript;
        });
    }

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTranscriptions</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> { <span class="hljs-attr">data</span>: transcriptions } = <span class="hljs-keyword">await</span> supabase
        .from(<span class="hljs-string">"transcriptions"</span>)
        .select()
        .eq(<span class="hljs-string">"podcast_id"</span>, podcast.value.id);
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Transcriptions"</span>, transcriptions);
      transcriptions.forEach(<span class="hljs-function">(<span class="hljs-params">transcript</span>) =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"id"</span>, transcript.id);
        savedTranscriptions.value[transcript.id] = transcript.transcript;
      });
    }

    onMounted(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Getting podcast info from the database once the component is mounted</span>
      getPodcastData();
    });

    <span class="hljs-keyword">return</span> {
      podcast,
      episodes,
      transcriptions,
      savedTranscriptions,
      episodeTranscriptionLoading,

      getTranscription,
      saveTranscription,
    };
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>If you've followed the steps above, you should have a working app. <a target="_blank" href="https://github.com/briancbarrow/vue-supabase-auth/tree/final-podcast-feed-transcriptions">Here is the final code</a> if you want to double check against what I have written.</p>
<p>I know I enjoyed making the app. Supabase makes it really easy to get a database/backend up and running. Please reach out to me on <a target="_blank" href="https://twitter.com/the_BrianB">Twitter</a> if you have any questions!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Add Supabase Authentication to a Vue App ]]>
                </title>
                <description>
                    <![CDATA[ In this post we will walk through getting authentication set up using Supabase and Vue 3.  This will also work with Vue 2, but you'll need to move things around to work with the options API. I am using Vue 3 as it is now the default version.  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/add-supabase-authentication-to-vue/</link>
                <guid isPermaLink="false">66bb92dbadd24ba42732510d</guid>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ supabase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vue ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Brian Barrow ]]>
                </dc:creator>
                <pubDate>Thu, 10 Feb 2022 16:40:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/02/Getting-Started-with-supabase-blog@2x.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this post we will walk through getting authentication set up using Supabase and Vue 3. </p>
<p>This will also work with Vue 2, but you'll need to move things around to work with the options API. I am using Vue 3 as it is <a target="_blank" href="https://blog.vuejs.org/posts/vue-3-as-the-new-default.html">now the default version</a>. </p>
<p>Just a heads up – there will not be much styling involved here so that the focus stays on Supabase authentication. </p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You should be familiar with JavaScript and have had some experience with Vue 3. Having some experience with Supabase will help, but isn't necessary. </p>
<p>If you need a quick review of Supabase you can check out <a target="_blank" href="https://developers.deepgram.com/blog/2021/11/getting-started-with-supabase/">a previous post</a> of mine where I go over how to get started using it. </p>
<p>You will also need <a target="_blank" href="https://nodejs.org/en/">Node.js</a> and NPM installed on your machine.</p>
<h2 id="heading-getting-started">Getting Started</h2>
<p>Let's start by building some of the frontend before we start building the database using Supabase. </p>
<p>The first thing we need to do is set up our project. Inside of your terminal and inside of the folder where you want this project to live, run this command:</p>
<pre><code class="lang-bash">npm init vite@latest vue-supabase-auth --template vue
</code></pre>
<p>This will initialize a new Vite project with Vue 3 in a folder called <code>vue-supabase-auth</code>. </p>
<p>Open this up in your code editor of choice and open up the <code>App.vue</code> file inside of the <code>src</code> folder. When I initialized the project, Vite put the script tag above the template tag. My personal preference is to move the template tag to the top, but that isn't necessary.</p>
<h2 id="heading-add-authentication-to-the-app">Add Authentication to the App</h2>
<p>The next step is to add authentication to our app. Supabase gives us the ability to authenticate a user in many different ways. </p>
<p>We'll be walking through how to set up basic email/password authentication, and authentication with a "magic link". A "magic link" is just a link sent to a user's email that, when clicked, will take them to your application and log them in. </p>
<h3 id="heading-get-a-supabase-account">Get a Supabase Account</h3>
<p>If you haven't already, you'll need to sign up for an account on <a target="_blank" href="https://app.supabase.io">Supabase</a>. It asks you to sign up with GitHub, so if you don't have a GitHub account, you should also sign up for one of those.</p>
<p>Once you are signed in, you'll click the green button that says "New Project" and select the default organization that was created when you logged in. Mine was called "briancbarrow's Org." </p>
<p>This will bring up a box where you provide some info about the project. I'll name it <code>basic-auth</code>, give it a strong password, and then I'm going to select the region <code>West US (North California)</code> because that is closest to me.</p>
<p>Once the project is done setting up, go into the Authentication -&gt; Settings and disable the "Enable email confirmations." It just makes things a little smoother for this tutorial.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/disable-email-confirmations.png" alt="Image" width="600" height="400" loading="lazy">
<em>Showing the "Enable email confirmations" setting disabled</em></p>
<h3 id="heading-set-up-supabase-in-the-vue-project">Set up Supabase in the Vue Project</h3>
<p>First, we need to run <code>npm install @supabase/supabase-js</code> to get the JavaScript package to integrate with Supabase.</p>
<p>Then we need to create a <code>supabase.js</code> file in the <code>src</code> folder of the project. That should contain the following:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@supabase/supabase-js'</span>

<span class="hljs-keyword">const</span> supabaseUrl = <span class="hljs-keyword">import</span>.meta.env.VITE_SUPABASE_URL
<span class="hljs-keyword">const</span> supabaseAnonKey = <span class="hljs-keyword">import</span>.meta.env.VITE_SUPABASE_ANON_KEY

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> supabase = createClient(supabaseUrl, supabaseAnonKey)
</code></pre>
<p>As you can see in the code above, we need to set up some environment variables that contain our Supabase keys. Create a <code>.env.local</code> file at the root of the project and add the <code>VITE_SUPABASE_URL</code> and the <code>VITE_SUPABASE_ANON_KEY</code>. You can find your url and anon_key on the dashboard of your Supabase project. </p>
<p>Your <code>.env.local</code> file will look like this:</p>
<pre><code>VITE_SUPABASE_URL=YOUR_SUPABASE_URL
VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
</code></pre><p>We also want to create a central store for data that is needed throughout the app, like user info. Create a <code>store.js</code> file in the <code>src</code> folder and fill it with this code:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { reactive } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> store = {
  <span class="hljs-attr">state</span>: reactive({
    <span class="hljs-attr">user</span>: {},
  }),
};
</code></pre>
<h3 id="heading-create-signin-and-signup-components">Create SignIn and SignUp Components</h3>
<p>Supabase authentication separates the <code>signIn</code> and <code>signUp</code> processes, so we'll need to handle them differently. I decided to create two separate components just to make things a little clearer in my head.</p>
<p>Create a <code>SignUp.vue</code> file in the components folder and add the following code:</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Sign up for an account<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> @<span class="hljs-attr">submit.prevent</span>=<span class="hljs-string">"handleSignup"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"email"</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">v-model</span>=<span class="hljs-string">"email"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"password"</span>&gt;</span>Password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">v-model</span>=<span class="hljs-string">"password"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Sign up<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"../supabase"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  setup() {
    <span class="hljs-keyword">const</span> email = ref(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> password = ref(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> handleSignup = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Use the Supabase provided method to handle the signup</span>
        <span class="hljs-keyword">const</span> { error } = <span class="hljs-keyword">await</span> supabase.auth.signUp({
          <span class="hljs-attr">email</span>: email.value,
          <span class="hljs-attr">password</span>: password.value,
        });
        <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">throw</span> error;
      } <span class="hljs-keyword">catch</span> (error) {
        alert(error.error_description || error.message);
      }
    };

    <span class="hljs-keyword">return</span> {
      email,
      password,
      handleSignup,
    };
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<p>Now create a <code>SignIn.vue</code> file and add the code below. The only differences are the method names that get called, and the text is slightly different in the markup.</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Sign in to your account<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> @<span class="hljs-attr">submit.prevent</span>=<span class="hljs-string">"handleSignin"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"email"</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">v-model</span>=<span class="hljs-string">"email"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"password"</span>&gt;</span>Password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">v-model</span>=<span class="hljs-string">"password"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Sign in<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"../supabase"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  setup() {
    <span class="hljs-keyword">const</span> email = ref(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> password = ref(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> handleSignin = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Use the Supabase provided method to handle the signin</span>
        <span class="hljs-keyword">const</span> { error } = <span class="hljs-keyword">await</span> supabase.auth.signIn({
          <span class="hljs-attr">email</span>: email.value,
          <span class="hljs-attr">password</span>: password.value,
        });
        <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">throw</span> error;
      } <span class="hljs-keyword">catch</span> (error) {
        alert(error.error_description || error.message);
      }
    };

    <span class="hljs-keyword">return</span> {
      email,
      password,
      handleSignin,
    };
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<p>Now we want to create a wrapper component for these two. Create a file named <code>Auth.vue</code> with the code below:</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">sign-up</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"isSignUp"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">sign-in</span> <span class="hljs-attr">v-else</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"isSignUp = !isSignUp"</span>&gt;</span>
      {{
        isSignUp
          ? "Already have an account? Sign In"
          : "Don't have an account yet? Sign Up"
      }}
    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> SignUp <span class="hljs-keyword">from</span> <span class="hljs-string">"./SignUp.vue"</span>;
<span class="hljs-keyword">import</span> SignIn <span class="hljs-keyword">from</span> <span class="hljs-string">"./SignIn.vue"</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">components</span>: { SignUp, SignIn },
  setup() {
    <span class="hljs-keyword">const</span> isSignUp = ref(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">return</span> {
      isSignUp,
    };
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p>This just allows the user to toggle between the <code>SignIn</code> and <code>SignUp</code> views. Now open up <code>App.vue</code> again and update the code to this:</p>
<pre><code class="lang-js">&lt;template&gt;
  &lt;!-- Check <span class="hljs-keyword">if</span> user is available <span class="hljs-keyword">in</span> the store, <span class="hljs-keyword">if</span> not show auth compoenent --&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Auth</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"!store.state.user"</span> /&gt;</span></span>
  &lt;!-- If user is available, show the Hello World component --&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">HelloWorld</span> <span class="hljs-attr">v-else</span> <span class="hljs-attr">msg</span>=<span class="hljs-string">"Hello Vue 3 + Vite"</span> /&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> Auth <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Auth.vue"</span>;
<span class="hljs-keyword">import</span> HelloWorld <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/HelloWorld.vue"</span>;

<span class="hljs-keyword">import</span> { store } <span class="hljs-keyword">from</span> <span class="hljs-string">"./store"</span>;
<span class="hljs-keyword">import</span> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"./supabase"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">components</span>: {
    HelloWorld,
    Auth,
  },
  setup() {
    <span class="hljs-comment">// we initially verify if a user is logged in with Supabase</span>
    store.state.user = supabase.auth.user();
    <span class="hljs-comment">// we then set up a listener to update the store when the user changes either by logging in or out</span>
    supabase.auth.onAuthStateChange(<span class="hljs-function">(<span class="hljs-params">event, session</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (event == <span class="hljs-string">"SIGNED_OUT"</span>) {
        store.state.user = <span class="hljs-literal">null</span>;
      } <span class="hljs-keyword">else</span> {
        store.state.user = session.user;
      }
    });

    <span class="hljs-keyword">return</span> {
      store,
    };
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
<span class="hljs-selector-id">#app</span> {
  <span class="hljs-attribute">font-family</span>: Avenir, Helvetica, Arial, sans-serif;
  <span class="hljs-attribute">-webkit-font-smoothing</span>: antialiased;
  <span class="hljs-attribute">-moz-osx-font-smoothing</span>: grayscale;
  <span class="hljs-attribute">text-align</span>: center;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#2c3e50</span>;
  <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">60px</span>;
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p>This will show the <code>Auth</code> component if a user is not signed in, otherwise it will show the <code>HelloWorld.vue</code> as we had initially set up.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screen-Shot-2022-02-09-at-10.14.27-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sign up form</em></p>
<p>Sign up using your email and a password you create, and you should then see the HelloWorld component again.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screen-Shot-2022-02-09-at-10.18.58-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Hello World component showing after sign up</em></p>
<h3 id="heading-how-to-log-out">How to log out</h3>
<p>Logging out is relatively straightforward. Inside the HelloWorld component, add the following to the bottom of the template tag:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"signOut"</span>&gt;</span>Sign Out<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Then update the script tag on HelloWorld to this:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">setup</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"../supabase"</span>;

defineProps({
  <span class="hljs-attr">msg</span>: <span class="hljs-built_in">String</span>,
});

<span class="hljs-keyword">const</span> count = ref(<span class="hljs-number">0</span>);
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">signOut</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { error } = <span class="hljs-keyword">await</span> supabase.auth.signOut();
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>You can see we are now importing the <code>supabase</code> file we created earlier and then creating a <code>signOut</code> method that gets called on a button click. </p>
<h3 id="heading-authenticate-using-magic-link">Authenticate using Magic Link</h3>
<p>Supabase also offers the ability to send users a magic link to their email which they click, and it takes them to the app and signs them in. The link it sends will redirect them to your site, so we need to make sure we have the correct redirect URL in our Supabase setup. </p>
<p>Navigate to the Auth -&gt; Settings page in the Supabase dashboard for your project and make sure the localhost URL is in the <code>Site URL</code> box. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screen-Shot-2022-02-09-at-10.24.13-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Make sure your site url matches where it should redirect to on login</em></p>
<h4 id="heading-create-the-magiclink-component">Create the MagicLink component</h4>
<p>Create a new file in the components folder called <code>MagicLink.vue</code> and add the following code:</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Sign in With Magic Link<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> @<span class="hljs-attr">submit.prevent</span>=<span class="hljs-string">"handleMagicLink"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"email"</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">v-model</span>=<span class="hljs-string">"email"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Sign in<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"../supabase"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  setup() {
    <span class="hljs-keyword">const</span> email = ref(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> handleMagicLink = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// We call the signIn method from Supabase to send the magic link. We only pass it the email though.</span>
        <span class="hljs-keyword">const</span> { error } = <span class="hljs-keyword">await</span> supabase.auth.signIn({
          <span class="hljs-attr">email</span>: email.value,
        });
        <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">throw</span> error;
      } <span class="hljs-keyword">catch</span> (error) {
        alert(error.error_description || error.message);
      }
    };

    <span class="hljs-keyword">return</span> {
      email,
      handleMagicLink,
    };
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<p>This component is very similar to the <code>SignIn</code> component. It uses the same method, but to get the magic link, we only pass in the email. </p>
<p>Now we need to update <code>Auth.vue</code> to also use the <code>MagicLink</code> component. Update <code>Auth.vue</code> to the following:</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- v-if logic to determine which auth component to show --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">sign-up</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"isSignUp &amp;&amp; !useMagicLink"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">sign-in</span> <span class="hljs-attr">v-else-if</span>=<span class="hljs-string">"!isSignUp &amp;&amp; !useMagicLink"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">magic-link</span> <span class="hljs-attr">v-else</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"!useMagicLink"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">"!useMagicLink"</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"isSignUp = !isSignUp"</span>&gt;</span>
        {{
          isSignUp
            ? "Already have an account? Sign In"
            : "Don't have an account yet? Sign Up"
        }}
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Or<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"toggleMagicLink"</span>&gt;</span>
      {{
        useMagicLink
          ? "Sign in with email and password"
          : "Sign in with magic link"
      }}
    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> SignUp <span class="hljs-keyword">from</span> <span class="hljs-string">"./SignUp.vue"</span>;
<span class="hljs-keyword">import</span> SignIn <span class="hljs-keyword">from</span> <span class="hljs-string">"./SignIn.vue"</span>;
<span class="hljs-keyword">import</span> MagicLink <span class="hljs-keyword">from</span> <span class="hljs-string">"./MagicLink.vue"</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">components</span>: { SignUp, SignIn, MagicLink },
  setup() {
    <span class="hljs-keyword">const</span> isSignUp = ref(<span class="hljs-literal">true</span>);
    <span class="hljs-keyword">const</span> useMagicLink = ref(<span class="hljs-literal">false</span>);

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toggleMagicLink</span>(<span class="hljs-params"></span>) </span>{
      useMagicLink.value = !useMagicLink.value;
    }

    <span class="hljs-keyword">return</span> {
      isSignUp,
      useMagicLink,

      toggleMagicLink,
    };
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p>Now the Auth page should look like this by default:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screen-Shot-2022-02-09-at-10.50.35-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Default Auth view</em></p>
<p>And it should look like this if the user clicks on the "Sign in with magic link" button:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screen-Shot-2022-02-09-at-10.50.42-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Auth view when signing in with magic link</em></p>
<p>If you input your email and click "Sign In", you should get an email with the magic link. Click that link, and you should be redirected to the app as a logged in user, where you'll see the HelloWorld view. </p>
<h2 id="heading-summary">Summary</h2>
<p>Supabase makes setting up authentication relatively easy. They also provide authentication using several social providers like Google, Apple, Github, and many more. </p>
<p>For my basic projects, I like to keep it simple and stick with email/password login or just letting Supabase send a magic link to log them in. </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create and Publish a Vue Component Library ]]>
                </title>
                <description>
                    <![CDATA[ Component libraries are all the rage these days. They make it easy to maintain a consistent look and feel across an application.  I've used a variety of different libraries in my career so far, but using a library is very different than knowing exact... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-and-publish-a-vue-component-library/</link>
                <guid isPermaLink="false">66bb92e5d3d870de4d402c65</guid>
                
                    <category>
                        <![CDATA[ components ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vue ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Brian Barrow ]]>
                </dc:creator>
                <pubDate>Wed, 22 Jul 2020 20:21:15 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/07/trnava-university-BEEyeib-am8-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Component libraries are all the rage these days. They make it easy to maintain a consistent look and feel across an application. </p>
<p>I've used a variety of different libraries in my career so far, but using a library is very different than knowing exactly what goes into making one. </p>
<p>I wanted a more in depth understanding of how a component library is built, and I want to show you how you can get a better understanding too.</p>
<p>To create this component library, we're going to use the <code>vue-sfc-rollup</code> npm package. This package is a very useful utility when starting a component library. </p>
<p>If you have an existing library that you want to use the utility with, refer to the <a target="_blank" href="https://www.npmjs.com/package/vue-sfc-rollup">documentation</a> they provide. </p>
<blockquote>
<p>May 2023 update: vue-sfc-rollup is now considered deprecated. The recommended replacement is Vite, specifically the "library build" configuration. </p>
</blockquote>
<p>You can read on for practice and educational purposes. But <a target="_blank" href="https://www.freecodecamp.org/news/how-to-create-and-publish-a-vue-component-library-update/">here's an updated version of the tutorial you can follow that uses Vite</a>.</p>
<p>This package creates a set of files for the project to start out. As their documentation explains, the files it creates includes the following (SFC stands for Single File Component): </p>
<ul>
<li>a minimal <a target="_blank" href="https://rollupjs.org/">rollup</a> config</li>
<li>a corresponding package.json file with build/dev scripts and dependencies</li>
<li>a minimal babel.config.js and .browserslistrc file for transpiling</li>
<li>a wrapper used by rollup when packaging your SFC</li>
<li>a sample SFC to kick-start development</li>
<li>a sample usage file which can be used to load/test your component/library during development</li>
</ul>
<p>The utility supports both Single File Components as well as a library of components. It is important to note this sentence from the documentation: </p>
<blockquote>
<p>In library mode, there is also an 'index' which declares the components exposed as part of your library.</p>
</blockquote>
<p>All this means is that there is some extra files generated in the setup process.</p>
<h1 id="heading-cool-lets-build-the-library">Cool, let's build the library</h1>
<p>First you'll want to install the <code>vue-sfc-rollup</code> globally:</p>
<p><code>npm install -g vue-sfc-rollup</code></p>
<p>Once that is installed globally, from the command line, go into the directory where you want your library project to be located. Once you are in that folder, run the following command to initialize the project.</p>
<p><code>sfc-init</code></p>
<p>Choose the following options within the prompts:</p>
<ul>
<li><strong>Is this a single component or a library?</strong> Library</li>
<li><strong>What is the npm name of your library?</strong> (this will need to be unique on npm. I used <em>brian-component-lib</em>)</li>
<li><strong>Will this library be written in JavaScript or TypeScript?</strong> JavaScript (feel free to use TypeScript if you know what you're doing)</li>
<li><strong>Enter a location to save the library files:</strong> This is the folder name you want your library to have. It will default to the npm name you gave it above so you can just hit enter.</li>
</ul>
<p>After the setup is complete, navigate to your folder and run an npm install.</p>
<pre><code>cd path/to/my-component-or-lib

npm install
</code></pre><p>Once that is done, you can open the folder up in your editor of choice.</p>
<p>As stated above, there is a sample Vue component built for us. You can find it inside of the <code>/src/lib-components</code> folder. To see what this component looks like, you can run <code>npm run serve</code> and navigate to <a target="_blank" href="http://localhost:8080/">http://localhost:8080/</a></p>
<p>Now let's add our own custom component. Create a new Vue file inside of the <code>lib-components</code> folder. For this example, I am going to imitate the button used in the freeCodeCamp assignment sections, so I'll name it <code>FccButton.vue</code></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/Screen-Shot-2020-07-22-at-10.08.05-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>This is the button component we'll build</em></p>
<p>You can copy and paste this code into your file:</p>
<pre><code class="lang-vue">&lt;template&gt;
  &lt;button class="btn-cta"&gt;{{ text }}&lt;/button&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
  name: "FccButton", // vue component name
  props: {
    text: {
      type: String,
      default: "Enter Button Text Here"
    }
  },
  data() {}
};
&lt;/script&gt;

&lt;style&gt;
.btn-cta {
  background-color: #d0d0d5;
  border-width: 3px;
  border-color: #1b1b32;
  border-radius: 0;
  border-style: solid;
  color: #1b1b32;
  display: block;
  margin-bottom: 0;
  font-weight: normal;
  text-align: center;
  -ms-touch-action: manipulation;
  touch-action: manipulation;
  cursor: pointer;
  white-space: nowrap;
  padding: 6px 12px;
  font-size: 18px;
  line-height: 1.42857143;
}

.btn-cta:active:hover,
.btn-cta:focus,
.btn-cta:hover {
  background-color: #1b1b32;
  border-width: 3px;
  border-color: #000;
  background-image: none;
  color: #f5f6f7;
}
&lt;/style&gt;
</code></pre>
<p>You can see we have the basic template section at the top with the class we want it to have. It also uses the text that the user will pass in. </p>
<p>Inside the script tag we have the Component name and the props that the component will take in. In this case there is only one prop called <code>text</code> that has a default of "Run the Tests". </p>
<p>We also have some styling to give it the look we want it to have.</p>
<p>To see how the component looks, we'll need to add it to the <code>index.js</code> file located in the <code>lib-components</code> folder. Your index.js file should look like this:</p>
<pre><code><span class="hljs-comment">/* eslint-disable import/prefer-default-export */</span>
<span class="hljs-keyword">export</span> { <span class="hljs-keyword">default</span> <span class="hljs-keyword">as</span> FccButton } <span class="hljs-keyword">from</span> <span class="hljs-string">'./FccButton'</span>;
</code></pre><p>You'll also need to import the component into the <code>serve.vue</code> file inside of the <code>dev</code> folder to look like this:</p>
<pre><code>&lt;script&gt;
<span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> { FccButton } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/entry"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Vue.extend({
  <span class="hljs-attr">name</span>: <span class="hljs-string">"ServeDev"</span>,
  <span class="hljs-attr">components</span>: {
    FccButton
  }
});
&lt;/script&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"app"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">FccButton</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span>
</code></pre><p>You might need to run <code>npm run serve</code> again to be able to see the button, but it should be visible when you navigate to <a target="_blank" href="http://localhost:8080/">http://localhost:8080/</a> in your browser.</p>
<p>So, we've built the component we wanted. You will follow this same process for any other component you want to build. Just make sure you are exporting them in the <code>/lib-components/index.js</code> file in order to make them available from the npm package we are about to publish.</p>
<h1 id="heading-publishing-to-npm">Publishing to NPM</h1>
<p>Now that we're ready to publish the library to NPM, we need to go through the build process for it to be packaged up and ready to go. </p>
<p>Before we run the build command, I recommend changing the version number in the <code>package.json</code> file to be <code>0.0.1</code> since this is the very first publish event for our library. We will want more than just one component in the library before we release the official 'first' version. You can read more about semantic versioning <a target="_blank" href="https://docs.npmjs.com/about-semantic-versioning">here</a>.</p>
<p>To do this, we run <code>npm run build</code>.</p>
<p>As the documentation states:</p>
<blockquote>
<p>Running the build script results in 3 compiled files in the <code>dist</code> directory, one for each of the <code>main</code>, <code>module</code>, and <code>unpkg</code> properties listed in your package.json file. With these files are generated, you're ready to go!</p>
</blockquote>
<p>With this command run, we are ready to publish to NPM. Before we do that, make sure you have an account on NPM (which you can do <a target="_blank" href="https://www.npmjs.com/">here</a> if you need to).</p>
<p>Next we'll need to add your account to your terminal by running: </p>
<p><code>npm adduser</code></p>
<h3 id="heading-understanding-packagejson">Understanding package.json</h3>
<p>When we publish to npm, we use the package.json file to control what files are published. If you look at the <code>package.json</code> file that was created when we initially set up the project you'll see something like this:</p>
<pre><code class="lang-json"><span class="hljs-string">"main"</span>: <span class="hljs-string">"dist/brian-component-lib.ssr.js"</span>,
<span class="hljs-string">"browser"</span>: <span class="hljs-string">"dist/brian-component-lib.esm.js"</span>,
<span class="hljs-string">"module"</span>: <span class="hljs-string">"dist/brian-component-lib.esm.js"</span>,
<span class="hljs-string">"unpkg"</span>: <span class="hljs-string">"dist/brian-component-lib.min.js"</span>,
<span class="hljs-string">"files"</span>: [
    <span class="hljs-string">"dist/*"</span>,
    <span class="hljs-string">"src/**/*.vue"</span>
],
</code></pre>
<p>The section under <code>files</code> tells npm to publish everything in our <code>dist</code> folder as well as any <code>.vue</code> files inside of our <code>src</code> folder. You can update this as you see fit, but we'll be leaving it as is for this tutorial.</p>
<p>Because we aren't changing anything with the package.json file, we are ready to publish. To do that we just need to run the following command:</p>
<p><code>npm publish</code></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/hy.gif" alt="Image" width="600" height="400" loading="lazy">
<em>I'm so proud of you!</em></p>
<p>And that is it! Congratulations! You've now published a Vue component library. You can go to <a target="_blank" href="https://www.npmjs.com/">npmjs.com</a> and find it in the registry.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
