<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jon</title>
    <description>The latest articles on DEV Community by Jon (@jonmeyers_io).</description>
    <link>https://dev.to/jonmeyers_io</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F157482%2Fe90ff0c3-0174-45e4-a4dc-7653630430c1.jpeg</url>
      <title>DEV Community: Jon</title>
      <link>https://dev.to/jonmeyers_io</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jonmeyers_io"/>
    <language>en</language>
    <item>
      <title>Loading SportsDB into Supabase</title>
      <dc:creator>Jon</dc:creator>
      <pubDate>Fri, 22 Jul 2022 11:18:26 +0000</pubDate>
      <link>https://dev.to/supabase/auto-generated-documentation-in-supabase-5e8o</link>
      <guid>https://dev.to/supabase/auto-generated-documentation-in-supabase-5e8o</guid>
      <description>&lt;h2&gt;
  
  
  What is SportsDB?
&lt;/h2&gt;

&lt;p&gt;In the real world, data is raw and complex, requiring a relational database to store and model it. One example is a complex dataset like &lt;a href="http://www.sportsdb.org/sd" rel="noopener noreferrer"&gt;SportDB&lt;/a&gt;, a sample dataset compiled from multiple sources, encompassing a variety of sports including football, baseball, ice hockey and more. &lt;/p&gt;

&lt;p&gt;SportsDB has been designed to model "Sports Reality" as effectively as possible in a relational database context. The schema inherits from the open SportsML standard, adopting its vocabularies and core approach toward commonalities among the sports. It is capable of supporting queries for the most intense of sports data applications, yet is simple enough for use by those with minimal database experience.&lt;/p&gt;

&lt;p&gt;SportsDB contains more than 80000 rows of data and more than 100 tables. It is often used to model a sports betting application, and it has been utilized in research projects dealing with sports data. The figure below shows SportsDB's schema and the relationships between the various entities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fwww.sportsdb.org%2Fmodules%2Fsd%2Fassets%2Fdownloads%2Fsportsdb-29.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fwww.sportsdb.org%2Fmodules%2Fsd%2Fassets%2Fdownloads%2Fsportsdb-29.jpg" alt="SportsDB ER diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Supabase?
&lt;/h2&gt;

&lt;p&gt;Supabase is a backend-as-a-service built on top of PostgreSQL that can handle complex datasets like SportsDB and provide you with insights. Its easy-to-use APIs and superior querying capabilities make it the ideal backend-as-a-service for building today's modern applications. Supabase also natively includes security features like &lt;a href="https://supabase.com/docs/guides/auth/intro#authentication" rel="noopener noreferrer"&gt;authentication&lt;/a&gt; and &lt;a href="https://supabase.com/docs/guides/auth/intro#authorization" rel="noopener noreferrer"&gt;authorization&lt;/a&gt; using &lt;a href="https://supabase.com/docs/guides/auth/row-level-security" rel="noopener noreferrer"&gt;Row Level Security (RLS)&lt;/a&gt;, which means more time can be spent developing your app.&lt;/p&gt;

&lt;p&gt;Now let's look at how we can load SportsDB data into Supabase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading SportsDB into Supabase
&lt;/h2&gt;

&lt;p&gt;Supabase is built for developers, and you can get started for free using your &lt;a href="https://app.supabase.com" rel="noopener noreferrer"&gt;existing Github account&lt;/a&gt;. Once your Supabase account is set up, you will access the Supabase dashboard. From here, go to &lt;strong&gt;All Projects &amp;gt; New Project&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rhd6qujcxur9rarwive.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rhd6qujcxur9rarwive.png" alt="New Supabase project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give your project a name and set the database password. You can also choose the region and adjust the &lt;a href="https://supabase.com/pricing" rel="noopener noreferrer"&gt;pricing plan&lt;/a&gt; based on the requirements of your project. Now click the &lt;em&gt;Create new project&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3uyf4pv160xby6x1zeb0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3uyf4pv160xby6x1zeb0.png" alt="New Supabase project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your project will take some time to initialize.&lt;/p&gt;

&lt;p&gt;Download the SportsDB dataset using this &lt;a href="http://sportsdb.org/modules/sd/assets/downloads/sportsdb_sample_postgresql.zip" rel="noopener noreferrer"&gt;link&lt;/a&gt;. The SportDB dataset consists of four tables: player table, team table, league table, and competition table. The table names are self-explanatory, so it is easy to find the table you want to work with. These four tables are related to one another through keys (IDs) which identify the relationships between them.&lt;/p&gt;

&lt;p&gt;To run simple queries, we can use the &lt;a href="https://supabase.com/docs/guides/database#the-sql-editor" rel="noopener noreferrer"&gt;built-in&lt;/a&gt; SQL Editor in the Supabase UI. With more complex queries, you can use &lt;a href="https://www.postgresql.org/docs/13/app-psql.html" rel="noopener noreferrer"&gt;psql&lt;/a&gt;, a terminal-based tool to work with a Postgres database. To learn how to install psql, check the documentation &lt;a href="https://www.postgresql.org/download/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To establish a connection with Supabase, you need a few details like Host, User, and Password or you can use an existing connection string. All these details can be found under &lt;em&gt;Connection info&lt;/em&gt; in the &lt;strong&gt;Settings &amp;gt; Database&lt;/strong&gt; section of our Supabase project page.&lt;/p&gt;

&lt;p&gt;Database Information:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyl5nbjmgxpmf0pv150qb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyl5nbjmgxpmf0pv150qb.png" alt="Database Information"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connection String:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fokre1cj83xeyfa2bta10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fokre1cj83xeyfa2bta10.png" alt="Connection String"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, we’re using the connection string to connect to the database.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; The password for the database will be the password that was provided when we created the project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffg5tmb4890iii4outg0d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffg5tmb4890iii4outg0d.png" alt="psql prompt"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you are connected to the database, use the below command to load the SportsDB dataset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="se"&gt;\i&lt;/span&gt;  &amp;lt;path that has the SQL file&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqbiy4u90sumvb94cxhq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqbiy4u90sumvb94cxhq.png" alt="psql example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After executing the command, you should see the SportsDB objects getting created and loaded with data.&lt;/p&gt;

&lt;p&gt;This may take some time, but once the data loading is finished, you can verify these newly created tables from your Supabase project page by going to the Table Editor tab. You can see all the newly created tables here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fttsc41sdbu4ky3xzkqr5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fttsc41sdbu4ky3xzkqr5.png" alt="Populated Table"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now go to the &lt;a href="https://supabase.com/docs/guides/api#accessing-the-docs" rel="noopener noreferrer"&gt;API section&lt;/a&gt; on your Project page. Here we can see that Supabase has created all the required REST API endpoints to perform the CRUD operations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqy19xfo2il5koevqmyif.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqy19xfo2il5koevqmyif.png" alt="Auto-generated documentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let’s make an HTTP request using cURL using anonymous access (key type as “anon”) for simplicity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxytim7di2yb6u16aufy8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxytim7di2yb6u16aufy8.png" alt="Toggle anon key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now copy the required cURL command and run it in your terminal. The output should look like the screenshot below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjoq618w2j0gk4dej68hv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjoq618w2j0gk4dej68hv.png" alt="Select query output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, we have selected all rows from the affiliations table. Similarly, we can perform all the other CRUD operations using the respective APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Whether you have a simple or complex application, Supabase provides an excellent backend-as-a-service allowing you to build apps more easily and faster. With its inbuilt enterprise features such as &lt;a href="https://supabase.com/docs/guides/auth/intro#authentication" rel="noopener noreferrer"&gt;authentication&lt;/a&gt;, &lt;a href="https://supabase.com/docs/guides/storage#files" rel="noopener noreferrer"&gt;file storage&lt;/a&gt;, and &lt;a href="https://supabase.com/docs/guides/api" rel="noopener noreferrer"&gt;autogenerated APIs&lt;/a&gt;, you can now free up your time and focus more on building critical business app requirements, without worrying about the data management issues.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fek4oey8ecztf0sd89n5v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fek4oey8ecztf0sd89n5v.png" alt="Legacy DB meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, become a Supabase developer today by starting &lt;a href="https://app.supabase.com" rel="noopener noreferrer"&gt;a new project on our free tier&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>sportsdb</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Implementing subscriptions with Stripe</title>
      <dc:creator>Jon</dc:creator>
      <pubDate>Wed, 21 Apr 2021 13:37:11 +0000</pubDate>
      <link>https://dev.to/jonmeyers_io/implementing-subscriptions-with-stripe-f7k</link>
      <guid>https://dev.to/jonmeyers_io/implementing-subscriptions-with-stripe-f7k</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/dijonmusters/courses" rel="noopener noreferrer"&gt;Project repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is our last week building a SaaS project with Next.js, Auth0, Vercel and Stripe. We have covered a lot so far! This week we will focus on implementing subscriptions with Stripe. This will allow our users to gain access to all premium courses while their subscription is active.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending User schema
&lt;/h2&gt;

&lt;p&gt;In order to track whether a user is currently subscribed or not, let's extend our Prisma user to contain an &lt;code&gt;isSubscribed&lt;/code&gt; field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// prisma/schema.prisma

model User {
  id Int @id @default(autoincrement())
  email String @unique
  courses Course[]
  stripeId String @unique
  isSubscribed Boolean @default(false)
  createdAt DateTime @default(now())
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will be a simple Boolean field to determine whether our user is allowed to see premium content.&lt;/p&gt;

&lt;p&gt;When building this feature I initially created a separate subscription model with all the relevant data from Stripe - such as frequency of subscription, next payment date, etc. However, I realised this was just unnecessarily duplicating data that was already available in Stripe, and took a huge amount more code to keep in sync.&lt;/p&gt;

&lt;p&gt;Simplifying it down to an &lt;code&gt;isSubscribed&lt;/code&gt; field greatly reduced the amount of code I needed to write, and bugs I was trying to iron out. It is also the one piece of data that really matters to my application. All levels of subscription see the same content, therefore, all I need to know is should they be able to see it or not!&lt;/p&gt;

&lt;p&gt;Let's create a migration for our new changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma migrate dev --name add-isSubscribed-to-user --preview-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Subscription options
&lt;/h2&gt;

&lt;p&gt;From Stripe's dashboard navigate to Products and create the different tiers you would like. I have created one for monthly and one for annual subscriptions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fu3w4h9it%2Fproduction%2F5b644801d2e733328da691009eb78830d73f2df4-2730x604.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fu3w4h9it%2Fproduction%2F5b644801d2e733328da691009eb78830d73f2df4-2730x604.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next we want to display these options in our Next.js application. I have created a new pricing page, and am using the Stripe library to fetch the prices in getStaticProps. Remember this function is called when we build a new version of our application, so will happen very infrequently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/pricing.js

import initStripe from 'stripe'

const PricingPage = ({ prices }) =&amp;gt; {
  console.log(prices)
  // display prices
}

export const getStaticProps = async () =&amp;gt; {
  const stripe = initStripe(process.env.STRIPE_SECRET_KEY)
  const { data: prices } = await stripe.prices.list()

  return {
    props: {
      prices,
    },
  }
}

export default PricingPage

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, Stripe's price type does not give us the product name - such as Basic or Pro. We could hard-code this in the frontend, but I would like to be able to change the name of the product in Stripe, and have it automatically flow through to the frontend.&lt;/p&gt;

&lt;p&gt;To get the product name we are going to fetch the product for each price and create a new plan object that contains an aggregated collection of the bits of data that we care about from price and product.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/pricing.js

export const getStaticProps = async () =&amp;gt; {
  // other stripe stuff

  const productPromises = prices.map(async price =&amp;gt; {
    const product = await stripe.products.retrieve(price.product)
    return {
      id: price.id,
      name: product.name,
      price: price.unit_amount,
      interval: price.recurring.interval,
      currency: price.currency,
    }
  })

  const plans = await Promise.all(productPromises)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are iterating over each price and creating a new promise (request to Stripe for product). We are then using &lt;code&gt;Promise.all&lt;/code&gt; to send all the requests simultaneously and waiting until we get back all of the data.&lt;/p&gt;

&lt;p&gt;At the end of this function &lt;code&gt;plans&lt;/code&gt; should be an array of aggregated data that looks something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const prices = [
  {
    id: 'price-123',
    name: 'Basic',
    price: 2000,
    interval: 'month',
    currency: 'aud',
  },
  // other pricing options
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final file should look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/pricing.js

import initStripe from 'stripe'

const PricingPage = ({ plans }) =&amp;gt; {
  // display plans
}

export const getStaticProps = async () =&amp;gt; {
  const stripe = initStripe(process.env.STRIPE_SECRET_KEY)
  const { data: prices } = await stripe.prices.list()

  const productPromises = prices.map(async price =&amp;gt; {
    const product = await stripe.products.retrieve(price.product)
    return {
      id: price.id,
      name: product.name,
      price: price.unit_amount,
      interval: price.recurring.interval,
      currency: price.currency,
    }
  })

  const plans = await Promise.all(productPromises)

  return {
    props: {
      plans,
    },
  }
}

export default PricingPage

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating a subscription
&lt;/h2&gt;

&lt;p&gt;We are going to create a new serverless function to initiate a subscription session. This is going to look very similar to the &lt;code&gt;charge-card&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/subscription/[priceId].js

import initStripe from 'stripe'
import { withApiAuthRequired, getSession } from '@auth0/nextjs-auth0';
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()
const stripe = initStripe(process.env.STRIPE_SECRET_KEY)

module.exports = withApiAuthRequired(async (req, res) =&amp;gt; {
  const { priceId } = req.query
  const { user: { email }} = getSession(req, res);

  const user = await prisma.user.findUnique({
    where: { email },
  })

  await prisma.$disconnect()

  const lineItems = [
    {
      price: priceId,
      quantity: 1,
    },
  ]

  const session = await stripe.checkout.sessions.create({
    customer: user.stripeId,
    mode: 'subscription',
    payment_method_types: ['card'],
    line_items: lineItems,
    success_url: `${process.env.CLIENT_URL}/success`,
    cancel_url: `${process.env.CLIENT_URL}/cancelled`,
    metadata: {
      userId: user.id,
    },
  })

  res.json({ id: session.id })
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can trigger this from a function anywhere in our frontend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { loadStripe } from "@stripe/stripe-js";
import axios from 'axios'

const processSubscription = async (priceId) =&amp;gt; {
  const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY);
  const { data } = await axios.get(`/api/subscription/${priceId}`);
  await stripe.redirectToCheckout({ sessionId: data.id });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to extend our stripe-hooks API route to listen for the new subscription event. We will need to determine who the Stripe customer is, and update their Prisma record to say they are now subscribed. These will be the lines we are adding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/stripe-hooks

const stripeId = event.data.object.customer

case 'customer.subscription.created':
  if (stripeId) {
    await prisma.user.update({
      where: {
        stripeId,
      },
      data: {
        isSubscribed: true,
      },
    })
  }
  break

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole file should look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/stripe-hooks

import initStripe from 'stripe'
import { buffer } from 'micro'
import { PrismaClient } from '@prisma/client'

const stripe = initStripe(process.env.STRIPE_SECRET_KEY)
const prisma = new PrismaClient()

export const config = { api: { bodyParser: false } }

export default async (req, res) =&amp;gt; {
  const reqBuffer = await buffer(req)
  const signature = req.headers['stripe-signature']
  const signingSecret = process.env.STRIPE_SIGNING_SECRET

  let event

  try {
    event = stripe.webhooks.constructEvent(reqBuffer, signature, signingSecret)
  } catch (err) {
    console.log(err)
    return res.status(400).send(`Webhook Error: ${err.message}`)
  }

  const { metadata } = event.data.object
  const stripeId = event.data.object.customer

  switch (event.type) {
    case 'charge.succeeded':
      if (metadata?.userId &amp;amp;&amp;amp; metadata?.courseId) {
        const user = await prisma.user.update({
          where: {
            id: parseInt(metadata.userId)
          },
          data: {
            courses: {
              connect: {
                id: parseInt(metadata.courseId)
              }
            }
          },
        })
      }
      break
    case 'customer.subscription.created':
      if (stripeId) {
        await prisma.user.update({
          where: {
            stripeId,
          },
          data: {
            isSubscribed: true,
          },
        })
      }
      break
    default:
      console.log(`Unhandled event type ${event.type}`)
  }

  res.send({ received: true })
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We will need to test this through the app again, as we need to know which customer is subscribing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Great! Now our users should be able to subscribe, but what about when they want to change or cancel their subscription?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stripe customer portal
&lt;/h2&gt;

&lt;p&gt;Stripe have once again gone above and beyond and created a customer portal for users to manage their payment details and subscriptions. We need to enable this in the Stripe dashboard and tell it what options we would like to be available.&lt;/p&gt;

&lt;p&gt;Go to Settings &amp;gt; Billing &amp;gt; Customer Portal and enable whatever you would like the customer to be able to manage.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You will need to create new pages for &lt;code&gt;Terms of Service&lt;/code&gt; and a &lt;code&gt;Privacy Policy&lt;/code&gt;. Make sure you set this to your production URL, as Stripe does not know what your &lt;code&gt;localhost&lt;/code&gt; is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we can create a new serverless function to initiate the customer portal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/customer-portal

import { withApiAuthRequired, getSession } from '@auth0/nextjs-auth0';
import initStripe from 'stripe'
import { PrismaClient } from '@prisma/client'

const stripe = initStripe(process.env.STRIPE_SECRET_KEY)
const prisma = new PrismaClient()

module.exports = withApiAuthRequired(async (req, res) =&amp;gt; {
  const { user: { email } } = getSession(req, res);

  const user = await prisma.user.findUnique({
    where: {
      email,
    },
  })

  await prisma.$disconnect()

  const session = await stripe.billingPortal.sessions.create({
    customer: user.stripeId,
    return_url: process.env.CLIENT_URL,
  })

  res.send({
    url: session.url,
  })
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns us the url of the session, so when we write a function to call this in our frontend we need to manually redirect the user to this URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { loadStripe } from '@stripe/stripe-js'
import axios from 'axios'

const loadPortal = async () =&amp;gt; {
  const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY)
  const { data } = await axios.get('/api/customer-portal')
  window.location.href = data.url
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, now our users can manage their own payment and subscription settings, but how do we know they have updated or cancelled their subscription?&lt;/p&gt;

&lt;p&gt;WEBHOOKS!&lt;/p&gt;

&lt;h2&gt;
  
  
  Add events to webhook
&lt;/h2&gt;

&lt;p&gt;This logic should look very similar to &lt;code&gt;customer.subscription.created&lt;/code&gt;. We just want to update the Prisma user with that Stripe ID to have isSubscribed set to &lt;code&gt;true&lt;/code&gt; if they are updating their subscription and &lt;code&gt;false&lt;/code&gt; if they're unsubscribing. These are the lines we will be adding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/stripe-hooks

case 'customer.subscription.updated':
  if (stripeId) {
    await prisma.user.update({
      where: {
        stripeId,
      },
      data: {
        isSubscribed: true,
      },
    })
  }
  break
case 'customer.subscription.deleted':
  await prisma.user.update({
    where: {
      stripeId,
    },
    data: {
      isSubscribed: false,
    },
  })
  break

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole file should look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/stripe-hooks

import initStripe from 'stripe'
import { buffer } from 'micro'
import { PrismaClient } from '@prisma/client'

const stripe = initStripe(process.env.STRIPE_SECRET_KEY)
const prisma = new PrismaClient()

export const config = { api: { bodyParser: false } }

export default async (req, res) =&amp;gt; {
  const reqBuffer = await buffer(req)
  const signature = req.headers['stripe-signature']
  const signingSecret = process.env.STRIPE_SIGNING_SECRET

  let event

  try {
    event = stripe.webhooks.constructEvent(reqBuffer, signature, signingSecret)
  } catch (err) {
    console.log(err)
    return res.status(400).send(`Webhook Error: ${err.message}`)
  }

  const { metadata } = event.data.object
  const stripeId = event.data.object.customer

  switch (event.type) {
    case 'charge.succeeded':
      if (metadata?.userId &amp;amp;&amp;amp; metadata?.courseId) {
        const user = await prisma.user.update({
          where: {
            id: parseInt(metadata.userId)
          },
          data: {
            courses: {
              connect: {
                id: parseInt(metadata.courseId)
              }
            }
          },
        })
      }
      break
    case 'customer.subscription.created':
      if (stripeId) {
        await prisma.user.update({
          where: {
            stripeId,
          },
          data: {
            isSubscribed: true,
          },
        })
      }
      break
    case 'customer.subscription.updated':
      if (stripeId) {
        await prisma.user.update({
          where: {
            stripeId,
          },
          data: {
            isSubscribed: true,
          },
        })
      }
      break
    case 'customer.subscription.deleted':
      if (stripeId) {
        await prisma.user.update({
          where: {
            stripeId,
          },
          data: {
            isSubscribed: false,
          },
        })
      }
      break
    default:
      console.log(`Unhandled event type ${event.type}`)
  }

  res.send({ received: true })
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, we should now get notified from Stripe anytime a user updates or cancels their subscription via the customer portal, so we can stop showing them premium courses, other than those they have purchased directly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;customer.subscription.deleted&lt;/code&gt; event is triggered when the user's subscription expires, not when they click cancel. Depending on the configuration you set in your customer portal settings, this is likely at the end of the period they have already paid for.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We can test this is working through the &lt;a href="https://dashboard.stripe.com" rel="noopener noreferrer"&gt;Stripe dashboard&lt;/a&gt; by going to Customers &amp;gt; Subscriptions, clicking the more options dots and selecting "cancel subscription".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fu3w4h9it%2Fproduction%2F4b0eb4dcde2cf14f424856c6ec0a1829b0fb7368-2758x700.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fu3w4h9it%2Fproduction%2F4b0eb4dcde2cf14f424856c6ec0a1829b0fb7368-2758x700.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we select cancel immediately the event should be triggered, our serverless function should be called and our Prisma user should be updated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This requires the stripe forward command and our next.js dev server to be running.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Production webhooks
&lt;/h2&gt;

&lt;p&gt;Great, now we have this running in development mode but what about our production app?&lt;/p&gt;

&lt;p&gt;Head on over to the &lt;a href="https://dashboard.stripe.com" rel="noopener noreferrer"&gt;Stripe dashboard&lt;/a&gt; and select Developers &amp;gt; Webhooks, and add an endpoint. Here we can tell it which URL to send events to, and which events we actually care about.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fu3w4h9it%2Fproduction%2Fc1b2216149e703be20b5bb6167d056cf0a10ab57-1204x1376.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fu3w4h9it%2Fproduction%2Fc1b2216149e703be20b5bb6167d056cf0a10ab57-1204x1376.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now within our endpoint dashboard we can copy our signing secret.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fu3w4h9it%2Fproduction%2F67cf37d89d4f3fa5c61ea2c9dc6bd7c850a2ed4a-2784x266.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fu3w4h9it%2Fproduction%2F67cf37d89d4f3fa5c61ea2c9dc6bd7c850a2ed4a-2784x266.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And follow the steps from &lt;a href="https://jonmeyers.io/blog/build-a-saas-platform-with-stripe/hosting-on-vercel-automatic-deploys-with-github-and-configuring-custom-domains" rel="noopener noreferrer"&gt;Hosting on Vercel, automatic deploys with GitHub and configuring custom domains&lt;/a&gt; to add a new secret in Vercel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fu3w4h9it%2Fproduction%2Fdb6e4440a5b0911366764f25c91a609365ea11c5-1524x856.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fu3w4h9it%2Fproduction%2Fdb6e4440a5b0911366764f25c91a609365ea11c5-1524x856.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Awesome! Now our stripe-hooks function will work in prod too! So how do we actually restrict the user from seeing the premium content in the app?&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate the premium content
&lt;/h2&gt;

&lt;p&gt;So we have setup all this complicated backend stuff to know when the user has purchased a course and whether or not they have an active subscription, but we haven't actually enforced this in the frontend. There are many ways we can do this but the main thing you should keep in mind is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;YOU CANNOT TRUST THE CLIENT!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A good way to ensure that only premium users can see premium content, is determine what they are allowed to see on the server, and only return the bits for that particular user. An example of this is if I had a video URL link that I only wanted users who have paid to be able to see then I should do that check in &lt;code&gt;getServerSideProps&lt;/code&gt; and only return the &lt;code&gt;videoUrl&lt;/code&gt; field if they have an active subscription or have paid for this particular course.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const getServerSideProps = withPageAuthRequired({
  async getServerSideProps({req, params}) {
    // fetch course and user from Prisma

    const userIsAllowed = course.price === 0 || user.isSubscribed || user.courses.find(course =&amp;gt; course.id === lesson.course.id)

    if (!userIsAllowed) {
      course.videoUrl = null
    }

    return {
      props: {
        course
      }
    }
  }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in the page component itself you could display a video element if they're allowed to view this content, or a buy/subscribe button if they're not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const CoursePage = ({ course }) =&amp;gt; course.videoUrl ? (
  &amp;lt;video src={course.videoUrl} /&amp;gt;
) : (
  &amp;lt;button onClick={handleBuy}&amp;gt;Buy course&amp;lt;/button&amp;gt;
)

export default CoursePage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A more comprehensive example of this logic can be seen in the &lt;a href="https://github.com/dijonmusters/courses/blob/master/pages/lesson/%5Bslug%5D.js" rel="noopener noreferrer"&gt;Lesson component&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That's it! We did it! We built a SaaS project in six weeks using Next.js, Prisma, Auth0 and Stripe. Check out the &lt;a href="https://github.com/dijonmusters/courses" rel="noopener noreferrer"&gt;project repo&lt;/a&gt; for a more complete working application. What now?&lt;/p&gt;

&lt;p&gt;I recommend you take what was covered in this blog series and try to implement something similar but a little bit different. Following steps in a tutorial is a great way to get an understanding of what you can do with a particular library or technology, but the real learning comes from trying to do something you want to do and running into problems!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>stripe</category>
    </item>
    <item>
      <title>Processing payments with Stripe and webhooks</title>
      <dc:creator>Jon</dc:creator>
      <pubDate>Wed, 21 Apr 2021 13:36:11 +0000</pubDate>
      <link>https://dev.to/jonmeyers_io/processing-payments-with-stripe-and-webhooks-187b</link>
      <guid>https://dev.to/jonmeyers_io/processing-payments-with-stripe-and-webhooks-187b</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/dijonmusters/courses"&gt;Project repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This week is all about taking payments with Stripe. We will implement a serverless function to charge a card and implement webhooks to update our Prisma user with courses they have purchased.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending User schema
&lt;/h2&gt;

&lt;p&gt;In order to track which courses a user has purchased we will need to extend our User schema to contain a field for &lt;code&gt;stripeId&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// prisma/schema.prisma

model User {
  id Int @id @default(autoincrement())
  email String @unique
  courses Course[]
  stripeId String @unique
  createdAt DateTime @default(now())
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will be used to map a Prisma user to a Stripe customer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This modification will temporarily break our application as a &lt;code&gt;stripeId&lt;/code&gt; is now a required field, and we are not setting one when we create a user in our application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's create a migration to apply these changes to our DB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma migrate dev --name add-stripe-id-to-user --preview-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up Stripe
&lt;/h2&gt;

&lt;p&gt;First thing you will need to do is &lt;a href="https://dashboard.stripe.com/register"&gt;create a Stripe account&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have created an account and have landed on your Stripe dashboard, you will need to enter your business's details in order to activate your account. This will give you access to production API keys and allow you to process real payments. You do not need to activate your account to complete this series, but something you may want to do if you want to use this in the real world!&lt;/p&gt;

&lt;p&gt;Next we need to install the two Stripe libraries in our application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i stripe @stripe/stripe-js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;stripe&lt;/code&gt; is a backend library that we will use to process payments, and &lt;code&gt;@stripe/stripe-js&lt;/code&gt; is a frontend library that our client will use to initiate a payment session.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we need to modify our .env file to add our new API keys - these can be found in the Stripe dashboard under the "Get your API keys" panel. Make sure you use the "test" keys for local development.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .env

// other secrets
STRIPE_SECRET_KEY=your-secret-key
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=your-publishable-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We must prepend frontend environment variables with &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt;. Variables that do not contain this will only be available to our serverless fuctions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Follow the same logic from &lt;a href="https://jonmeyers.io/blog/build-a-saas-platform-with-stripe/hosting-on-vercel-automatic-deploys-with-github-and-configuring-custom-domains"&gt;Hosting on Vercel, automatic deploys with GitHub and configuring custom domains&lt;/a&gt; to add secrets in Vercel - without this our hosted application will not work.&lt;/p&gt;

&lt;p&gt;Great! Now we should have stripe wired up!&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Stripe customer
&lt;/h2&gt;

&lt;p&gt;We will need to create a Stripe customer to keep a track of purchases and whether a subscription is active. We could do this when the user makes their first purchase, however, we do not know whether that will be when they purchase a particular course or activate their subscription. This would require us to add some logic to each of our payment scenarios to first check if a stripe user exists before charging their account. We can simplify this logic greatly by just creating a Stripe customer at the same time as our Prisma user - the first time a new user signs in to our application.&lt;/p&gt;

&lt;p&gt;Let's modify our auth hook to create a stripe customer before we create a user in Prisma. That way we can use the newly created Stripe ID to create our user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/auth/hooks.js

// other imports
import initStripe from 'stripe'
const stripe = initStripe(process.env.STRIPE_SECRET_KEY)

module.exports = async (req, res) =&amp;gt; {
  // other auth code
  const customer = await stripe.customers.create({
    email,
  })
  const user = await prisma.user.create({
    data: { email, stripeId: customer.id },
  })
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole file should look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/auth/hooks.js

import { PrismaClient } from '@prisma/client'
import initStripe from 'stripe'

const prisma = new PrismaClient()
const stripe = initStripe(process.env.STRIPE_SECRET_KEY)

module.exports = async (req, res) =&amp;gt; {
  try {
    const { email, secret } = JSON.parse(req.body)
    if (secret === process.env.AUTH0_HOOK_SECRET) {
      const customer = await stripe.customers.create({
        email,
      })
      const user = await prisma.user.create({
        data: { email, stripeId: customer.id },
      })
      console.log('created user')
    } else {
      console.log('You forgot to send me your secret!')
    }
  } catch (err) {
    console.log(err)
  } finally {
    await prisma.$disconnect()
    res.send({ received: true })
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, now anytime a new user signs in we should be creating a Stripe customer, then a Prisma user that has a reference to the customer's ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Charging a card with Stripe
&lt;/h2&gt;

&lt;p&gt;Now we want to build a serverless function that can process a payment for a particular course. We will need to tell this function which course the user is purchasing, so will use a &lt;a href="https://nextjs.org/docs/api-routes/dynamic-api-routes"&gt;Dynamic API Route&lt;/a&gt; to pass in the course ID. Let's create a new serverless function at &lt;code&gt;/pages/api/charge-card/[courseId].js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/charge-card/[courseId].js

module.exports = async (req, res) =&amp;gt; {
  const { courseId } = req.query
  res.send(`charging card for course ${courseId}`)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can trigger this serverless function by going to &lt;code&gt;http://localhost:3000/api/charge-card/any-value-you-want&lt;/code&gt;. In this case it should print out "charging card for course any-value-you-want.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The next step would be working out how much we need to charge for the course. We could just pass this along with the request from the frontend, however, this could easily be tinkered with by the user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We can't trust anything from the client!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's make a call to our Prisma DB to find out the real price.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/charge-card/[courseId].js

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

module.exports = async (req, res) =&amp;gt; {
  const { courseId } = req.query

  const course = prisma.course.findUnique({
    where: {
      id: parseInt(courseId),
    },
  })

  await prisma.$disconnect()

  res.send(`charging ${course.price} cents for ${courseId}`)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We use parseInt() here to turn the string we get from the req's query into an integer, which Prisma is expecting for the ID.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next we want to know who the user is that is purchasing this course. This means we want the API route to only be accessible by logged in users. Let's wrap it in &lt;code&gt;withApiAuthRequired&lt;/code&gt; and work out who the user is by their session email.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/charge-card/[courseId].js

import { PrismaClient } from '@prisma/client'
import { withApiAuthRequired, getSession } from '@auth0/nextjs-auth0';

const prisma = new PrismaClient()

module.exports = withApiAuthRequired(async (req, res) =&amp;gt; {
  const { courseId } = req.query
  const { user: { email } } = getSession(req, res)

  const course = prisma.course.findUnique({
    where: {
      id: parseInt(courseId),
    },
  })

  const user = await prisma.user.findUnique({
    where: {
      email,
    },
  })

  await prisma.$disconnect()

  res.send(`charging ${user.email} ${course.price} cents for ${courseId}`)
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we want to tell Stripe what we are actually charging the customer. We do this by creating a list of line items and a payment session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/charge-card/[courseId].js

//other imports
import initStripe from 'stripe'

const stripe = initStripe(process.env.STRIPE_SECRET_KEY)

module.exports = async (req, res) =&amp;gt; {
  // course and user stuff

  const lineItems = [
    {
      price_data: {
        currency: 'aud', // swap this out for your currency
        product_data: {
          name: course.title,
        },
        unit_amount: course.price,
      },
      quantity: 1,
    },
  ]

  const session = await stripe.checkout.sessions.create({
    customer: user.stripeId,
    payment_method_types: ['card'],
    line_items: lineItems,
    mode: 'payment',
    success_url: `${process.env.CLIENT_URL}/success`,
    cancel_url: `${process.env.CLIENT_URL}/cancelled`,
  })

  res.json({ id: session.id })
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to provide a success and cancel url for stripe to forward the user to. These will need to be created at &lt;code&gt;pages/success.js&lt;/code&gt; and &lt;code&gt;pages/cancelled.js&lt;/code&gt;. Additionally, we need to create an environment variable for CLIENT_URL. Follow the previous steps to add this to the .env with the value &lt;code&gt;http://localhost:3000&lt;/code&gt;, and a new secret in Vercel with the value of whatever your hosted URL is - mine is &lt;code&gt;https://courses-saas.vercel.app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lastly we want to wrap all of this in a try/catch block in case something goes wrong. The whole file should look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/charge-card/[courseId].js

import { withApiAuthRequired, getSession } from '@auth0/nextjs-auth0';
import { PrismaClient } from '@prisma/client'
import initStripe from 'stripe'

const prisma = new PrismaClient()
const stripe = initStripe(process.env.STRIPE_SECRET_KEY)

module.exports = withApiAuthRequired(async (req, res) =&amp;gt; {
  try {
    const { courseId } = req.query
    const { user: { email } } = getSession(req, res)

    const course = prisma.course.findUnique({
      where: {
        id: parseInt(courseId),
      },
    })

    const user = await prisma.user.findUnique({
      where: {
        email,
      },
    })

    const lineItems = [
      {
        price_data: {
          currency: 'aud', // swap this out for your currency
          product_data: {
            name: course.title,
          },
          unit_amount: course.price,
        },
        quantity: 1,
      },
    ]

    const session = await stripe.checkout.sessions.create({
      customer: user.stripeId,
      payment_method_types: ['card'],
      line_items: lineItems,
      mode: 'payment',
      success_url: `${process.env.CLIENT_URL}/success`,
      cancel_url: `${process.env.CLIENT_URL}/cancelled`,
    })

    res.json({ id: session.id })
  } catch (err) {
    res.send(err)
  } finally {
    await prisma.$disconnect()
  }
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we need to add a function in our frontend to trigger this payment. This block can be triggered from a button click anywhere in the app, and just needs to be passed a course ID to initiate the payment with Stripe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { loadStripe } from "@stripe/stripe-js";
import axios from 'axios'

const processPayment = async (courseId) =&amp;gt; {
  const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY);
  const { data } = await axios.get(`/api/charge-card/${courseId}`);
  await stripe.redirectToCheckout({ sessionId: data.id });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, we want to know when a course has been purchased so that we can update our user in Prisma. This is made possible by Stripe's webhooks. Similarly to our Auth0 hook, we can subscribe to particular events, and when that happens Stripe will call our serverless function and tell us which user purchased a particular course.&lt;/p&gt;

&lt;p&gt;We get a lot of data from Stripe about the transaction itself, but not which course or Prisma user. Let's modify our charge-card function to pass this across as metadata with the session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/charge-card/[courseId].js

const session = await stripe.checkout.sessions.create({
  // other session stuff

  payment_intent_data: {
    metadata: {
      userId: user.id,
      courseId,
    },
  },
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole file should look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/charge-card/[courseId].js

import { withApiAuthRequired, getSession } from '@auth0/nextjs-auth0';
import { PrismaClient } from '@prisma/client'
import initStripe from 'stripe'

const prisma = new PrismaClient()
const stripe = initStripe(process.env.STRIPE_SECRET_KEY)

module.exports = withApiAuthRequired(async (req, res) =&amp;gt; {
  try {
    const { courseId } = req.query
    const { user: { email } } = getSession(req, res)

    const course = prisma.course.findUnique({
      where: {
        id: parseInt(courseId),
      },
    })

    const user = await prisma.user.findUnique({
      where: {
        email,
      },
    })

    const lineItems = [
      {
        price_data: {
          currency: 'aud', // swap this out for your currency
          product_data: {
            name: course.title,
          },
          unit_amount: course.price,
        },
        quantity: 1,
      },
    ]

    const session = await stripe.checkout.sessions.create({
      customer: user.stripeId,
      payment_method_types: ['card'],
      line_items: lineItems,
      mode: 'payment',
      success_url: `${process.env.CLIENT_URL}/success`,
      cancel_url: `${process.env.CLIENT_URL}/cancelled`,
      payment_intent_data: {
        metadata: {
          userId: user.id,
          courseId,
        },
      },
    })

    res.json({ id: session.id })
  } catch (err) {
    res.send(err)
  } finally {
    await prisma.$disconnect()
  }
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can create an API route that can deal with these events from Stripe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/stripe-hooks

export default async (req, res) =&amp;gt; {
  // check what kind of event stripe has sent us
  res.send({ received: true })
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So that we don't get ourselves into the same problem we had with Auth0 Hooks, let's implement a signing secret to confirm that the request is coming from Stripe.&lt;/p&gt;

&lt;p&gt;Let's first &lt;a href="https://stripe.com/docs/stripe-cli#install"&gt;install the Stripe CLI&lt;/a&gt; to be able to simulate a webhook event. If you have macOS and homebrew installed, we can run this command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install stripe/stripe-cli/stripe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the following to authenticate the CLI with Stripe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stripe login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we should be able to run the following to forward webhook events to our localhost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stripe listen --forward-to localhost:3000/api/stripe-hooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will print out a signing secret to the terminal. Copy this into your .env file with the name &lt;code&gt;STRIPE_SIGNING_SECRET&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .env

// other secrets
STRIPE_SIGNING_SECRET=your-webhook-signing-secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stripe provides a handy helper function called &lt;code&gt;constructEvent&lt;/code&gt; that can confirm whether this request was sent from them. Unfortunately, there is a little bit of tinkering we need to do to get this working in Next.js. &lt;a href="https://codedaily.io/tutorials/Stripe-Webhook-Verification-with-NextJS"&gt;Here is a really good guide&lt;/a&gt; that steps through the process.&lt;/p&gt;

&lt;p&gt;Let's start by installing &lt;code&gt;micro&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i micro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can update our stripe-hooks API route to validate the request is coming from Stripe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/stripe-hooks

import initStripe from 'stripe'
import { buffer } from 'micro'

const stripe = initStripe(process.env.STRIPE_SECRET_KEY)

export const config = { api: { bodyParser: false } }

export default async (req, res) =&amp;gt; {
  const reqBuffer = await buffer(req)
  const signature = req.headers['stripe-signature']
  const signingSecret = process.env.STRIPE_SIGNING_SECRET

  let event

  try {
    event = stripe.webhooks.constructEvent(reqBuffer, signature, signingSecret)
  } catch (err) {
    console.log(err)
    return res.status(400).send(`Webhook Error: ${err.message}`)
  }

  // check what kind of event stripe has sent us

  res.send({ received: true })
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;req&lt;/code&gt; object from Vercel is not structured the way Stripe is expecting, so does not validate properly unless we do a bit of work.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;stripe.webhooks.constructEvent() is a function that Stripe recommends using to confirm that they have sent this request. If it can validate this then it returns the Stripe event, otherwise it will throw an exception, and we will return a 400 status code. Read more &lt;a href="https://stripe.com/docs/webhooks/signatures"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Okay, so now we can forget all about that validation and focus on processing the event we are receiving from Stripe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/stripe-hooks

export default async (req, res) =&amp;gt; {
  // signing logic

  switch (event.type) {
    case 'charge.succeeded':
      // update user in prisma
      console.log('charge succeeded')
      break
    default:
      console.log(`Unhandled event type ${event.type}`)
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;event.type will contain a string for the event that has been triggered. We will extend this later for subscriptions so are using a case statement to keep it clear.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We can test that this is working by running the following command in a new terminal window - this requires the &lt;code&gt;stripe listen&lt;/code&gt; and &lt;code&gt;npm run dev&lt;/code&gt; commands to be running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stripe trigger charge.succeeded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should print out "charge succeeded" to the console.&lt;/p&gt;

&lt;p&gt;Next we need to pull the user and course ID out of the metadata, and update the user's courses they have purchased in Prisma.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/stripe-hooks

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

export default async (req, res) =&amp;gt; {
  // signing logic

  const { metadata } = event.data.object

  switch (event.type) {
    case 'charge.succeeded':
      // update user in prisma
      if (metadata?.userId &amp;amp;&amp;amp; metadata?.courseId) {
        const user = await prisma.user.update({
          where: {
            id: parseInt(metadata.userId)
          },
          data: {
            courses: {
              connect: {
                id: parseInt(metadata.courseId)
              }
            }
          },
        })
      }
      break
    default:
      console.log(`Unhandled event type ${event.type}`)
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;connect&lt;/code&gt; is used to insert an existing course ID into the array of courses for the user. If we wanted to create this course then we would use &lt;code&gt;create&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The full file should look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/stripe-hooks

import initStripe from 'stripe'
import { buffer } from 'micro'
import { PrismaClient } from '@prisma/client'

const stripe = initStripe(process.env.STRIPE_SECRET_KEY)
const prisma = new PrismaClient()

export const config = { api: { bodyParser: false } }

export default async (req, res) =&amp;gt; {
  const reqBuffer = await buffer(req)
  const signature = req.headers['stripe-signature']
  const signingSecret = process.env.STRIPE_SIGNING_SECRET

  let event

  try {
    event = stripe.webhooks.constructEvent(reqBuffer, signature, signingSecret)
  } catch (err) {
    console.log(err)
    return res.status(400).send(`Webhook Error: ${err.message}`)
  }

  const { metadata } = event.data.object

  switch (event.type) {
    case 'charge.succeeded':
      // update user in prisma
      if (metadata?.userId &amp;amp;&amp;amp; metadata?.courseId) {
        const user = await prisma.user.update({
          where: {
            id: parseInt(metadata.userId)
          },
          data: {
            courses: {
              connect: {
                id: parseInt(metadata.courseId)
              }
            }
          },
        })
      }
      break
    default:
      console.log(`Unhandled event type ${event.type}`)
  }

  res.send({ received: true })
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we should have a complete solution where we can trigger a payment for a particular course in our app - we need to do it from the app, rather than the CLI so that it includes our metadata. This will make a request to our charge-card serverless function to create a payment session for that course. The user should then be taken to Stripe's UI where they can enter their credit card details, and then be redirected to our success page after they have been charged. In the background Stripe will call our webhook serverless function, which will update our Prisma user with the newly purchased course!&lt;/p&gt;

&lt;p&gt;Amazing! And our app doesn't need to know anything about our users' credit card details!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;a href="https://stripe.com/docs/api"&gt;Stripe documentation&lt;/a&gt; is fantastic and I highly recommend checking out all the awesome things you can do beyond what we cover in this series!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Next week
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jonmeyers.io/blog/build-a-saas-platform-with-stripe/implementing-subscriptions-with-stripe"&gt;Implementing subscriptions with Stripe&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>stripe</category>
    </item>
    <item>
      <title>Social login with GitHub and Auth0 rules</title>
      <dc:creator>Jon</dc:creator>
      <pubDate>Wed, 21 Apr 2021 13:35:11 +0000</pubDate>
      <link>https://dev.to/jonmeyers_io/social-login-with-github-and-auth0-rules-3g28</link>
      <guid>https://dev.to/jonmeyers_io/social-login-with-github-and-auth0-rules-3g28</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/dijonmusters/courses"&gt;Project repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This week we look at using Auth0's social signon to authenticate with GitHub. We also setup webhooks to create a local user in our Prisma database anytime a new user logs into Auth0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Social login with GitHub
&lt;/h2&gt;

&lt;p&gt;Enabling different social providers is super simple with Auth0. Follow &lt;a href="https://auth0.com/docs/get-started/dashboard/set-up-social-connections"&gt;this guide&lt;/a&gt; to configure a range of social providers - Google, Facebook, Twitter etc. I am just going to setup GitHub.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By default Auth0 has local username and password configured. Disable this to enforce only signing in with social providers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Auth0 Hooks
&lt;/h2&gt;

&lt;p&gt;We are going to setup a webhook that sends a request to one of our serverless functions anytime a new user logs into Auth0. We can create a Rule in Auth0 to do this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function (user, context, callback) {
  // do some stuff
  callback(null, user, context);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Rules are serverless functions that Auth0 will call anytime a user logs in&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Auth0 tells us who the user signing in is, gives us a context object with additional data and a callback function that we can invoke to continue the sign in process.&lt;/p&gt;

&lt;p&gt;The first parameter the callback expects is an error. If this is null or undefined it will continue the sign in process. If this parameter is any truthy value it will throw an exception and stop the sign in process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If we do not invoke the callback function the sign in process will eventually timeout.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's setup a new API route in our Next.js application to handle the request from the Auth0 hook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/auth/hooks.js

module.exports = async (req, res) =&amp;gt; {
  const { email } = JSON.parse(req.body)
  // create user in prisma
  console.log('created user')
  res.send({ received: true })
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We need to call res.send so that the hook receives a 200 status code and can keep going with the sign in process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now let's update our Auth0 hook to send a request to our new endpoint. We will provide the user's email as the body of our request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function (user, context, callback) {
  await request.post('http://localhost:3000/api/auth/hooks', {
    body: JSON.stringify({
      email: user.email,
    })
  });

  callback(null, user, context);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Auth0 hooks like semicolons - you can choose what you prefer elsewhere but probably best to put them in here!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now let's trigger the hook by signing in with our Next.js application.&lt;/p&gt;

&lt;h3&gt;
  
  
  ERROR!
&lt;/h3&gt;

&lt;p&gt;The problem is that this Auth0 hook is running on some remote Auth0 server, not our local machine. Therefore, it has no idea what localhost is. Ngrok to the rescue!&lt;/p&gt;

&lt;h3&gt;
  
  
  Ngrok
&lt;/h3&gt;

&lt;p&gt;This is a tool that forwards a public URL on the internet through to a specific port running on localhost (our Next.js dev server). This is often referred to as tunneling.&lt;/p&gt;

&lt;p&gt;We can install it using npm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -g ngrok
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then forward it to port :3000.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ngrok http 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should give you a URL that you can use to replace "&lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt;" in our Auth0 hook request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function (user, context, callback) {
  await request.post('https://0d4d01c96799.au.ngrok.io/api/auth/hooks', {
    body: JSON.stringify({
      email: user.email,
    })
  });
  callback(null, user, context);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you should be able to trigger a request to our new API route by going through the sign in flow with the Next.js app.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember to set this to your production URL when you deploy your app!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You should see this logging out "created user" to the terminal console, but we're not yet doing that. Let's create a new user in Prisma.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/auth/hooks.js

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

module.exports = async (req, res) =&amp;gt; {
  const { email } = JSON.parse(req.body)

  const user = await prisma.user.create({
    data: { email },
  })

  await prisma.$disconnect()

  console.log('created user')
  res.send({ received: true })
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's wrap that in a try, catch block just so that if we fail to create a user we still send a response to the hook and don't hold up the auth process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/auth/hooks.js

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

module.exports = async (req, res) =&amp;gt; {
  try {
    const { email } = JSON.parse(req.body)
    const user = await prisma.user.create({
      data: { email },
    })
    console.log('created user')
  } catch (err) {
    console.log(err)
  } finally {
    await prisma.$disconnect()
    res.send({ received: true })
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The &lt;em&gt;finally&lt;/em&gt; block will run regardless of whether we successfully created a user or an exception was thrown. Writing cleanup logic here helps &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY&lt;/a&gt; up our code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This should now be creating a new user in Prisma every time a user logs in. Wait, EVERY SINGLE TIME?!?! that's no good!&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: New user every single login!
&lt;/h2&gt;

&lt;p&gt;Lucky we haven't pushed anything to prod. This one could have cost us some money in a high traffic application!&lt;/p&gt;

&lt;p&gt;We only want to create a user the first time time they login, therefore, we need some way to know whether we have successfully created a user in the past. We could expose another API route to ping the Prisma database and make sure a user with this email does not yet exist, but this would require another trip from Auth0 servers across to Vercel. We don't want to keep our user waiting unnecessarily.&lt;/p&gt;

&lt;p&gt;Thankfully, Auth0 give us the ability to set metadata on our user.&lt;/p&gt;

&lt;p&gt;We can set the metadata after creating the user like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user.app_metadata = user.app_metadata || {};
user.app_metadata.localUserCreated = true;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to manually tell Auth0 to persist this metadata like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And can read the metadata to make sure we want to create a user like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!user.app_metadata.localUserCreated) {
  // create prisma user
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full rule should look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function (user, context, callback) {
  user.app_metadata = user.app_metadata || {};

  if (!user.app_metadata.localUserCreated) {
    await request.post('https://0d4d01c96799.au.ngrok.io/api/auth/hooks', {
      body: JSON.stringify({
        email: user.email,
      })
    });
    user.app_metadata.localUserCreated = true;
    await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);
  }
  callback(null, user, context);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's also wrap that in a try catch block to make sure we respond if an exception is thrown.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function (user, context, callback) {
  try {
    user.app_metadata = user.app_metadata || {};

    if (!user.app_metadata.localUserCreated) {
      await request.post('https://0d4d01c96799.au.ngrok.io/api/auth/hooks', {
        body: JSON.stringify({
          email: user.email,
        })
      });
      user.app_metadata.localUserCreated = true;
      await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);
    }
    callback(null, user, context);
  } catch (err) {
    callback(err);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! So now anytime a user signs in and we do not have an account in prisma it will call our API route to create a user.&lt;/p&gt;

&lt;p&gt;WAIT! Do we just have an open API route that will create a user anytime we send a request to it?!? That's no good! How do we know this is coming from Auth0?!?&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: Our API Route to deal with authentication is not authenticated!
&lt;/h2&gt;

&lt;p&gt;Okay, there are a few ways we could solve this. You might think "isn't that what we have that Auth0 library for? Just wrap it in that withApiAuthRequired function you were raving about!"&lt;/p&gt;

&lt;p&gt;Since this is coming from Auth0, and not our Next.js app the session does not actually exist!&lt;/p&gt;

&lt;p&gt;We will need to manually send a secret value from the Auth0 hook and validate it is present and correct in the API route. This is a similar solution to something like API keys that map to a particular user.&lt;/p&gt;

&lt;p&gt;In the Rules menu we can create a new secret.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8uXeIjrP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/f9f64a28ffe363982b4cb6b0a0a1bddc2a0fd2d9-1738x1464.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8uXeIjrP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/f9f64a28ffe363982b4cb6b0a0a1bddc2a0fd2d9-1738x1464.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recommend setting the value to a long &lt;a href="https://onlinehashtools.com/generate-random-sha256-hash"&gt;randomly generated string&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we can access that value in our Auth0 Hook like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;configuration.AUTH0_HOOK_SECRET
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's post this across with our request to the API route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function (user, context, callback) {
  try {
    user.app_metadata = user.app_metadata || {};

    if (!user.app_metadata.localUserCreated) {
      await request.post('https://0d4d01c96799.au.ngrok.io/api/auth/hooks', {
        body: JSON.stringify({
          email: user.email,
          secret: configuration.AUTH0_HOOK_SECRET,
        })
      });
      user.app_metadata.localUserCreated = true;
      await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);
    }
    callback(null, user, context);
  } catch (err) {
    callback(err);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to update our Next.js app's .env file to contain this value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .env

// other secrets
AUTH0_HOOK_SECRET=that-super-secret-value-that-no-one-else-knows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And wrap our create user logic in a check to make sure that value is correct.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { email, secret } = JSON.parse(req.body)

if (secret === process.env.AUTH0_HOOK_SECRET) {
  // create user
} else {
  console.log('You forgot to send me your secret!')
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole API route should look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/auth/hooks.js

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

module.exports = async (req, res) =&amp;gt; {
  try {
    const { email, secret } = JSON.parse(req.body)
    if (secret === process.env.AUTH0_HOOK_SECRET) {
      const user = await prisma.user.create({
        data: { email },
      })
      console.log('created user')
    } else {
      console.log('You forgot to send me your secret!')
    }
  } catch (err) {
    console.log(err)
  } finally {
    await prisma.$disconnect()
    res.send({ received: true })
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the same logic from &lt;a href="https://jonmeyers.io/blog/build-a-saas-platform-with-stripe/hosting-on-vercel-automatic-deploys-with-github-and-configuring-custom-domains"&gt;Hosting on Vercel, automatic deploys with GitHub and configuring custom domains&lt;/a&gt; to add our new Auth0 secrets in Vercel - without this our hosted application will not work.&lt;/p&gt;

&lt;p&gt;Excellent! That's it! We did it!&lt;/p&gt;

&lt;p&gt;Now anytime a new user logs into our Next.js application, Auth0 will let us know so we can create a user in our Prisma database, to keep a track of those extra bits of data that our application cares about!&lt;/p&gt;

&lt;h2&gt;
  
  
  Next week
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jonmeyers.io/blog/build-a-saas-platform-with-stripe/processing-payments-with-stripe-and-webhooks"&gt;Processing payments with Stripe and webhooks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>auth</category>
    </item>
    <item>
      <title>Authentication with Auth0 and Next.js</title>
      <dc:creator>Jon</dc:creator>
      <pubDate>Wed, 21 Apr 2021 13:34:11 +0000</pubDate>
      <link>https://dev.to/jonmeyers_io/authentication-with-auth0-and-next-js-m81</link>
      <guid>https://dev.to/jonmeyers_io/authentication-with-auth0-and-next-js-m81</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/dijonmusters/courses"&gt;Project repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This week was all about users and authentication! The point of our SaaS project is to offer courses that can be purchased individually, or a recurring subscription that unlocks access to everything. In order to accomplish this we need to know some things about the user!&lt;/p&gt;

&lt;h2&gt;
  
  
  Auth0
&lt;/h2&gt;

&lt;p&gt;Given my limited experience with complex auth solutions, I wanted to lean on a third-party service as much as possible. Ideally I want all that complexity abstracted away so I can focus on building really good content - the product I am actually selling!&lt;/p&gt;

&lt;p&gt;One of the big benefits of Next.js, over something like Gatsby or a custom React application, is that we have access to a server at runtime. This means we can validate who the user is and what they should see - something we can't really trust on the client.&lt;/p&gt;

&lt;p&gt;There are numerous auth options compatible with Next.js, varying greatly in amount of code you need to write. My key requirements were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Social login - GitHub&lt;/li&gt;
&lt;li&gt;No need to write session cookie logic&lt;/li&gt;
&lt;li&gt;Convenient functions to lock down pages and API routes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essentially I just want to be able to ask a library "should I show the thing?" and it give me an answer I can trust!&lt;/p&gt;

&lt;p&gt;Auth0 have done just that with an amazing library specifically for Next.js - very creatively called &lt;a href="https://github.com/auth0/nextjs-auth0"&gt;nextjs-auth0&lt;/a&gt;. This allows you to use the power of Auth0 to manage account creation, logging in and out, session cookies etc, and provides a simple set of functions that you can use to created gated content.&lt;/p&gt;

&lt;p&gt;First thing we need to do is create a free auth0 account and a tenant, which can be used to group together applications that share a user database. &lt;a href="https://auth0.com/docs/get-started/learn-the-basics"&gt;Here is a good guide&lt;/a&gt; to get this setup.&lt;/p&gt;

&lt;p&gt;Next we need to install and configure &lt;a href="https://github.com/auth0/nextjs-auth0"&gt;@auth0/nextjs-auth0&lt;/a&gt; in our project. The &lt;a href="https://github.com/auth0/nextjs-auth0/blob/main/README.md"&gt;README&lt;/a&gt; steps through exactly what we need to do to accomplish this!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember to follow the same steps from &lt;a href="https://jonmeyers.io/blog/build-a-saas-platform-with-stripe/hosting-on-vercel-automatic-deploys-with-github-and-configuring-custom-domains"&gt;Hosting on Vercel, automatic deploys with GitHub and configuring custom domains&lt;/a&gt; to add all those Auth0 secrets to Vercel - without this our hosted application will not work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This gives us access to some super awesome helper functions, my favourite of which are:&lt;/p&gt;

&lt;h3&gt;
  
  
  withPageAuthRequired
&lt;/h3&gt;

&lt;p&gt;This is a client-side function that we can use to wrap protected pages that we only want the user to be able to visit if they have logged in. If they are not logged in they will be redirected to the auth0 sign in page. Simply wrap the page-level component in this function like so.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/dashboard.js

import { withPageAuthRequired } from '@auth0/nextjs-auth0';

const Dashboard = withPageAuthRequired(({ user }) =&amp;gt; {
  return &amp;lt;p&amp;gt;Welcome {user.name}&amp;lt;/p&amp;gt;
})

export default Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  useUser
&lt;/h3&gt;

&lt;p&gt;This is a client-side React Hook we can use to get the user object anywhere in any of our components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/index.js

import { useUser } from '@auth0/nextjs-auth0';

const Home = () =&amp;gt; {
  const { user, error, isLoading } = useUser();

  if (isLoading) return &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;;
  if (error) return &amp;lt;div&amp;gt;{error.message}&amp;lt;/div&amp;gt;;

  if (user) {
    return (
      &amp;lt;div&amp;gt;
        Welcome {user.name}! &amp;lt;a href="/api/auth/logout"&amp;gt;Logout&amp;lt;/a&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
  return &amp;lt;a href="/api/auth/login"&amp;gt;Login&amp;lt;/a&amp;gt;;
};

export default Home
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  withPageAuthRequired
&lt;/h3&gt;

&lt;p&gt;This is a serverside function that we can wrap around Next.js' getServerSideProps to ensure the user cannot visit a page unless logged in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/dashboard.js

import { withPageAuthRequired } from '@auth0/nextjs-auth0';

const Dashboard = ({ user }) =&amp;gt; {
  return &amp;lt;div&amp;gt;Hello {user.name}&amp;lt;/div&amp;gt;;
}

export const getServerSideProps = withPageAuthRequired();

export default Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  withApiAuthRequired
&lt;/h3&gt;

&lt;p&gt;This is a serverside function that we can wrap around our API route to ensure that only an authenticated user can send a request to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/courses.js

import { withApiAuthRequired, getSession } from '@auth0/nextjs-auth0';

module.exports = withApiAuthRequired(async (req, res) =&amp;gt; {
  const { user } = getSession(req, res)

  // validate user can view courses

  res.send(courses)
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  User Schema
&lt;/h2&gt;

&lt;p&gt;Auth0 is fantastic for logging a user in and validating that their session is valid, however, if we want to keep a track of other information - such as courses purchased - we will need to create a user in our Prisma db.&lt;/p&gt;

&lt;p&gt;Let's extend our schema by adding a user model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// prisma/schema.prisma

model User {
  id Int @id @default(autoincrement())
  email String @unique
  courses Course[]
  createdAt DateTime @default(now())
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use the email from Auth0 to determine who our user is, therefore, it will need to be unique.&lt;/p&gt;

&lt;p&gt;We will also add a list of users to each course.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// prisma/schema.prisma

model Course {
  id Int @id @default(autoincrement())
  title String @unique
  description String
  lessons Lesson[]
  users User[]
  createdAt DateTime @default(now())
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we're going to use Prisma's migration API to capture the changes to the structure of our database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma migrate dev --name create-user-schema --preview-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This is creating a new migration with the name "create-user-schema" and we need to append "--preview-feature" as this is still experimental.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This may prompt you with some questions about overwriting existing data. Select YES.&lt;/p&gt;

&lt;p&gt;If it cannot apply the migration you can try resetting your database - this will drop the entire db so make sure you don't run it without thinking later!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma migrate reset --preview-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next let's add a price and URL slug to our course schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// prisma/schema.prisma

model Course {
  id Int @id @default(autoincrement())
  title String @unique
  description String
  lessons Lesson[]
  users User[]
  price Int
  slug String @unique
  createdAt DateTime @default(now())
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a slug to our Lesson schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// prisma/schema.prisma

model Lesson {
  id Int @id @default(autoincrement())
  title String @unique
  description String
  courseId Int
  course Course @relation(fields: [courseId], references: [id])
  videoUrl String
  slug String @unique
  createdAt DateTime @default(now())
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole file should look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url = env("DATABASE_URL")
}

model User {
  id Int @id @default(autoincrement())
  email String @unique
  courses Course[]
  createdAt DateTime @default(now())
}

model Course {
  id Int @id @default(autoincrement())
  title String @unique
  description String
  lessons Lesson[]
  users User[]
  price Int
  slug String @unique
  createdAt DateTime @default(now())
}

model Lesson {
  id Int @id @default(autoincrement())
  title String @unique
  description String
  courseId Int
  course Course @relation(fields: [courseId], references: [id])
  videoUrl String
  slug String @unique
  createdAt DateTime @default(now())
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's run the migration command again to snapshot those changes and update our db.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma migrate dev --name add-slugs --preview-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome! We now have our application authenticating with Auth0, protecting our protected stuff and have our database schema ready to go!&lt;/p&gt;

&lt;h2&gt;
  
  
  Next week
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jonmeyers.io/blog/build-a-saas-platform-with-stripe/social-login-with-github-and-auth0-rules"&gt;Social login with GitHub and Auth0 rules&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>auth</category>
    </item>
    <item>
      <title>Hosting on Vercel, automatic deploys with GitHub and configuring custom domains</title>
      <dc:creator>Jon</dc:creator>
      <pubDate>Wed, 21 Apr 2021 13:33:11 +0000</pubDate>
      <link>https://dev.to/jonmeyers_io/hosting-on-vercel-automatic-deploys-with-github-and-configuring-custom-domains-51ok</link>
      <guid>https://dev.to/jonmeyers_io/hosting-on-vercel-automatic-deploys-with-github-and-configuring-custom-domains-51ok</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/dijonmusters/courses"&gt;Project repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This week we are focusing on all things hosting: making our next.js application available to the world wide webs; setting up automatic deploys when we change code; and configuring a custom domain!&lt;/p&gt;

&lt;h2&gt;
  
  
  Build app
&lt;/h2&gt;

&lt;p&gt;We can build a production version of our application by running the build script - this is what our hosting platform will use too!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ERROR!
&lt;/h3&gt;

&lt;p&gt;This is giving us the following error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: connect ECONNREFUSED 127.0.0.1:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's because I made a small blunder in our first week! We are trying to load data from a serverless function, but that serverless function is also built when we build a new version of our application. This would be fine if the "building the serverless functions" step came before the "build our next app" step, but unfortunately that is not the case!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This worked locally because next does some performance magic and rebuilds the page when we request it by refreshing the browser.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Looks like we can't read data for our pre-rendered/statically generated pages from our serverless functions, but that is okay! Every one of our getStaticProps functions is actually a little chunk of server-side logic so we can just build our Prisma queries there!&lt;/p&gt;

&lt;p&gt;Let's create a new utils folder at the root of our project and add a db.js file with the following content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// utils/db.js

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

const getCourses = () =&amp;gt;
  prisma.course.findMany({
    include: {
      lessons: true,
    },
  })

export { getCourses }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can import our getCourses function and call it in our getStaticProps function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/index.js

import CourseList from 'components/CourseList'
import { getCourses } from '../utils/db'

const Index = ({ courses }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Courses&amp;lt;/h1&amp;gt;
      &amp;lt;pre&amp;gt;
        {JSON.stringify(courses, null, 2)}
      &amp;lt;/pre&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export const getStaticProps = async () =&amp;gt; {
  const data = await getCourses()

  return {
    props: {
      courses: data,
    },
  }
}

export default Index
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's run that build again!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  MORE ERRORS!
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Error serializing `.courses[0].createdAt` returned from `getStaticProps` in "/".
Reason: `object` ("[object Date]") cannot be serialized as JSON. Please only return JSON serializable data types.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, this is a weird one but basically when the createdAt date comes back from Prisma, Next.js attempts to turn it into a string (serializing) and is not happy.&lt;/p&gt;

&lt;p&gt;A quick trick we can use here is manually stringify the courses we get back from Prisma and then parse them back into json.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/index.js

import { getCourses } from '../utils/db'

// other component stuff

export const getStaticProps = async () =&amp;gt; {
  const data = await getCourses()

  return {
    props: {
      courses: JSON.parse(JSON.stringify(data)),
    },
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our whole component should look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/index.js

import { getCourses } from '../utils/db'

const Index = ({ courses }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Courses&amp;lt;/h1&amp;gt;
      &amp;lt;pre&amp;gt;
        {JSON.stringify(courses, null, 2)}
      &amp;lt;/pre&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export const getStaticProps = async () =&amp;gt; {
  const data = await getCourses()

  return {
    props: {
      courses: JSON.parse(JSON.stringify(data)),
    },
  }
}

export default Index

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run that build one last time!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  NO ERRORS!
&lt;/h3&gt;

&lt;p&gt;Yay! Our application is building properly! We can run &lt;code&gt;npm start&lt;/code&gt; to run the production build locally and make sure it looks good in the browser.&lt;/p&gt;

&lt;p&gt;This is now hostable! But where to host?!?&lt;/p&gt;

&lt;h2&gt;
  
  
  Vercel
&lt;/h2&gt;

&lt;p&gt;We need to actually host our application to get a public URL that we can give to someone with an internet connection, so they can see our super dope site!&lt;/p&gt;

&lt;p&gt;There are many serverless hosting providers to choose from. My two favourites are Netlify and Vercel. They are both super focused on developer experience and have exceptional free tiers! Absolutely free to get started and you would need to have a pretty successful app to get to the point where you need to pay! Next.js can be hosted on either of these platforms, however, I find Vercel tends to implement new Next.js features a little ahead of Netlify (probably because they're also the creators of Next.js!), so I am going to use them for this SaaS project.&lt;/p&gt;

&lt;p&gt;You will need to create an account with &lt;a href="https://vercel.com"&gt;Vercel&lt;/a&gt; and install the CLI tool with the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -g vercel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can login.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vercel login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And deploy your application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vercel --prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will step you through a few questions about the project. You can just accept all the default options, since Vercel is very much optimised to host Next.js applications!&lt;/p&gt;

&lt;p&gt;That's it! Super simple!&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a secret in Vercel
&lt;/h2&gt;

&lt;p&gt;When running locally we have specified an environment variable (or secret) for connecting to our Supabase DB instance. We need to tell Vercel about this so our application can connect to the DB in prod.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vercel.com/docs/environment-variables"&gt;This is a good article&lt;/a&gt; about the different types of environment variables and how to configure them in Vercel.&lt;/p&gt;

&lt;p&gt;Head over to the Vercel dashboard for you application and go to Settings &amp;gt; Environment Variables. Select "secret" for the type of variable, enter "DATABASE_URL" as the name and then dropdown the value input to "Create a new Secret for null".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gYA73tL6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/91f17c0685998b750a6a8b2726d388cbd33e490f-1538x1056.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gYA73tL6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/91f17c0685998b750a6a8b2726d388cbd33e490f-1538x1056.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now enter your connection string from Supabase as the value, and make the name "@database-url". Secrets always start with "@" in Vercel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8gO70Jrf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/ea46d7827ec33368044e89e3943c503f05e3dba3-842x930.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8gO70Jrf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/ea46d7827ec33368044e89e3943c503f05e3dba3-842x930.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can select this secret from the dropdown to link it to our DATABASE_URL environment variable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--opJgEvSV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/136f11ee8861f73059f0b54ec5810e36a1459e64-1512x844.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--opJgEvSV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/136f11ee8861f73059f0b54ec5810e36a1459e64-1512x844.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This distinction of environment variable separate from secret allows you to share environment variables across different projects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Automatic deploys from GitHub
&lt;/h2&gt;

&lt;p&gt;Next we want to tell Vercel to automatically deploy our application anytime we make changes. To do this we are going to use GitHub. Initialise the repo as a git project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a new GitHub repo. You can do this through the &lt;a href="https://github.com/"&gt;GitHub UI&lt;/a&gt;, however, the gh CLI allows you to stay in your terminal!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install gh

gh repo create courses
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to create a .gitignore file so we don't end up pushing a whole bunch of unnecessary stuff to GitHub - node_modules, easily generated files etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .gitignore

node_modules/
.next/
.DS_Store
out/

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can add, commit and push to GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add .
git commit -m 'create initial project'
git push origin master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have some code in GitHub, let's tell Vercel about it.&lt;/p&gt;

&lt;p&gt;Login to &lt;a href="https://vercel.com"&gt;Vercel&lt;/a&gt; and navigate to your project dashboard.&lt;/p&gt;

&lt;p&gt;Now navigate to Settings &amp;gt; Git and click Connect git repository.&lt;/p&gt;

&lt;p&gt;This will require you to authenticate with GitHub and allow Vercel access to your repos.&lt;/p&gt;

&lt;p&gt;Select your project repo and your production branch - mine is master.&lt;/p&gt;

&lt;p&gt;Now Vercel will automatically deploy a new version of your application anytime you push changes to that branch. Give it a go!&lt;/p&gt;

&lt;p&gt;Make a small change to your home page, commit and push to GitHub. You should see this kicks off a new deployment in your Vercel Dashboard. Once this is done your changes should be live on your deployed site!&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom domain
&lt;/h2&gt;

&lt;p&gt;The last thing I am going to setup this week is a custom domain. This is the only part of this series that is going to cost money, but it is entirely optional! Vercel will give us a public URL that we can use to share our application with the world. A custom domain just allows us to customise things a little more, which may be important for your branding.&lt;/p&gt;

&lt;p&gt;Vercel offer a super easy way to purchase domains through their dashboard, however, I have found that other services often have the same domain names a little bit cheaper. I have a few domains registered with hover.com and have had a really good experience with them so far, but any domain registration service will do. They should all let you add DNS records and change name servers, which is what we need to do.&lt;/p&gt;

&lt;p&gt;Now that you have purchased your amazingly custom domain name, you need to add two DNS records. There should be a menu item for DNS somewhere and it should contain fields for type, hostname and IP address.&lt;/p&gt;

&lt;p&gt;Create one with this info:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type: A
Hostname: @
IP Address: 76.76.21.21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And one with this info:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type: A
Hostname: *
IP Address: 76.76.21.21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The @ means anything looking for this domain, send to Vercel's IP address, and the * does the same for subdomains - such as www.&lt;/p&gt;

&lt;p&gt;This is what this should look like in Hover.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TN0ssPeR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/0da5d4bc7da1bbf4ceb623d6667940877b84bb8d-1920x892.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TN0ssPeR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/0da5d4bc7da1bbf4ceb623d6667940877b84bb8d-1920x892.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, we need to point the nameservers to Vercel. In Hover this is located on the Overview page.&lt;/p&gt;

&lt;p&gt;Update these to the following values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ns1.vercel-dns.com
ns2.vercel-dns.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, this is what it looks like in Hover.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---yyl9YjP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/b881227a34b23de68a9582b1b7f0e1ca925f2313-1922x1628.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---yyl9YjP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/b881227a34b23de68a9582b1b7f0e1ca925f2313-1922x1628.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we need to tell Vercel that this is the domain name we would like to use for our project. Head on over to the Vercel dashboard for your project and navigate to Settings &amp;gt; Domains, and in the Domains input box enter your new custom domain and click Add.&lt;/p&gt;

&lt;p&gt;This will send a request to your domain and make sure it is configured correctly. If everything is all good it will look like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XyXWQlkf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/9910b566160b2744dca69d5ef903205b71b0d3c3-1858x270.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XyXWQlkf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/9910b566160b2744dca69d5ef903205b71b0d3c3-1858x270.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If there is a problem it will look something like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LJeDsJDt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/1da19d61d384d8976f1c4c129b93365a9f06dd35-1862x820.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LJeDsJDt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.sanity.io/images/u3w4h9it/production/1da19d61d384d8976f1c4c129b93365a9f06dd35-1862x820.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes it can take a little bit of time to propagate DNS changes through, so don't panic straight away! Maybe grab a coffee or have a nap. If it is still not working, double check that configuration above.&lt;/p&gt;

&lt;p&gt;If everything is green ticks your application will now be available at your custom domain! Great work!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Awesome! Our application is hosted, we have automatic deploys setup for anytime we push changes to GitHub, and we have (maybe) configured our own custom on-brand domain!&lt;/p&gt;

&lt;h2&gt;
  
  
  Helpful resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://vercel.com/docs/platform/deployments"&gt;Vercel deployment docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=MxR5I5_hOKk&amp;amp;list=PL6bwFJ82M6FXgctyoWXqj7H0GK8_YIeF1&amp;amp;index=3&amp;amp;t=155s"&gt;React 2025 setup video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vercel.com/docs/custom-domains"&gt;Vercel custom domain docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next week
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jonmeyers.io/blog/build-a-saas-platform-with-stripe/authentication-with-auth0-and-nextjs"&gt;Authentication with Auth0 and Next.js&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>vercel</category>
    </item>
    <item>
      <title>Tech stack and initial project setup</title>
      <dc:creator>Jon</dc:creator>
      <pubDate>Wed, 21 Apr 2021 13:32:11 +0000</pubDate>
      <link>https://dev.to/jonmeyers_io/tech-stack-and-initial-project-setup-3of4</link>
      <guid>https://dev.to/jonmeyers_io/tech-stack-and-initial-project-setup-3of4</guid>
      <description>&lt;p&gt;Week one down! How exciting! This week was all about coming up with an idea and configuring the new project. I will be keeping the &lt;a href="https://github.com/dijonmusters/courses"&gt;GitHub repo&lt;/a&gt; up to date as I build out this project so make sure you check that out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Idea
&lt;/h2&gt;

&lt;p&gt;I will be building a video tutorial/course platform that contains a collection of free and paid courses. You will be able to watch any of the free courses once you create an account. For the premium content, you can choose to purchase a single course to own forever, or subscribe on a monthly or yearly basis to access all the premium courses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Readme Driven Development (RDD)
&lt;/h2&gt;

&lt;p&gt;I will be following Tom Preston-Werner's &lt;a href="https://tom.preston-werner.com/2010/08/23/readme-driven-development.html"&gt;Readme Driven Development&lt;/a&gt; methodology, whereby the first thing you create is a readme describing your project. My key takeaways from Tom's article were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Making a product for users is a waste of time if it doesn't provide value&lt;/li&gt;
&lt;li&gt;Thinking about how your software will be used gives you a pathway with achievable milestones&lt;/li&gt;
&lt;li&gt;Helps inform tech decisions&lt;/li&gt;
&lt;li&gt;Creates a shared language and understanding across other devs and stakeholders.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can checkout my &lt;a href="https://github.com/dijonmusters/courses/blob/master/README.md"&gt;readme&lt;/a&gt; to see what I am planning to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;p&gt;As the majority of this project can be statically generated ahead of time I will be building a Jamstack app. This will help keep the loading speed fast for users and keep the hosting costs &lt;del&gt;down&lt;/del&gt; free!&lt;/p&gt;

&lt;h3&gt;
  
  
  Next.js
&lt;/h3&gt;

&lt;p&gt;Since most of the content can be generated at build time I was keen to use something that makes this process simple - Next.js or Gatsby. I went with Next.js as it gives me all that SSG (Static Site Generation) magic I am after, but also offers SSR (Server Side Rendering) if my application does require it in the future!&lt;/p&gt;

&lt;p&gt;Additionally, I really like Next's API for generating static content. You just declare a getStaticProps function, co-located with the page component that uses the data. Next.js will iterate over any components that declare this function and make these requests at build time. I find this workflow to be a little more convenient than Gatsby, and requires less context switching than jumping out of the component and implementing some data fetching in gatsby-node.js.&lt;/p&gt;

&lt;p&gt;That is just personal preference though. Both of these frameworks are absolutely awesome and are perfectly capable of building what we need!&lt;/p&gt;

&lt;p&gt;Setting up Next.js was super simple. Just create a new folder and initialise it as an NPM project. My project will be called "courses".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir courses &amp;amp;&amp;amp; cd courses &amp;amp;&amp;amp; npm init -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now to install Next.js and its dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i next react react-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add some scripts to build and run our application. In the package.json file, replace the test script (that no-one uses in a side project) with the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "export": "next export"
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next.js uses file-based routing so we can create pages simply by putting React components in the pages directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir pages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create an index.js file and add the following code to create a welcoming home page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/index.js

const HomePage = () =&amp;gt; &amp;lt;h1&amp;gt;Welcome to Courses!&amp;lt;/h1&amp;gt;

export default HomePage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have a fully functioning Next.js application. Run the following command and go and visit it at &lt;a href="http://localhost3000"&gt;http://localhost:3000&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  API routes
&lt;/h3&gt;

&lt;p&gt;We will need some serverside code in order to process payments with Stripe and interact with the database. These chunks of serverside code will be quite isolated and single purpose. This is a perfect usecase for serverless functions and Next.js makes this super simple!&lt;/p&gt;

&lt;p&gt;Just create an API folder in the pages directory!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir pages/api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And add a test.js file with the following content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/test.js

module.exports = async (req, res) =&amp;gt; {
  res.send('it works!')
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! It's done! To run this serverless function just go to &lt;a href="http://localhost:3000/api/test"&gt;http://localhost:3000/api/test&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Next.js will pick up any .js files in this api folder and automatically turn them into serverless functions!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Super cool!&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL vs Document DB
&lt;/h3&gt;

&lt;p&gt;We are going to need a database to store information about our users, and remember which courses they have purchased. There are a huge number of options here, but first we need to decide whether we want to use an SQL db - such as PostgreSQL - or a document db - such as MongoDB.&lt;/p&gt;

&lt;p&gt;The biggest factor to consider between these two options is how you want to model relationships between different bits of data. An SQL db can stitch together data from different tables using one complex query, whereas you may need to do multiple queries in a document db, and stitch it together yourself.&lt;/p&gt;

&lt;p&gt;Our application is going to be hosted on a different server to our db - potentially in a different continent - so making a single request, letting the db do some of the hard work and sending back a smaller dataset is likely going to be much more performant.&lt;/p&gt;

&lt;p&gt;Again, the scope of this application is quite small so this is probably not going to be a problem, but since we know we will need at least a relationship between our user and the courses they have purchased, I am going to go with an SQL solution.&lt;/p&gt;

&lt;p&gt;Additionally, the methodology of the Jamstack is all about being able to scale up easily and I think SQL gives us more options than a document db as things get more complex!&lt;/p&gt;

&lt;h3&gt;
  
  
  Supabase
&lt;/h3&gt;

&lt;p&gt;Again, there are a million options for a hosted SQL database. I have used Heroku extensively in the past and would highly recommend, however, I have been looking for an excuse to try Supabase and I think this is it!&lt;/p&gt;

&lt;p&gt;Supabase is an open source competitor to Firebase. They offer a whole bunch of services - db hosting, query builder language, auth etc - however, we are just going to use it as a free db host.&lt;/p&gt;

&lt;p&gt;Head on over to &lt;a href="https://app.supabase.io"&gt;their website&lt;/a&gt; and create an account.&lt;/p&gt;

&lt;p&gt;Once you're at the dashboard click "create a new project" - make sure to use a strong password (and copy it somewhere as we will need it again soon!) and pick a region that is geographically close to you!&lt;/p&gt;

&lt;p&gt;Once it is finished creating a DB, head over to Settings &amp;gt; Database and copy the Connection String. We are going to need this in the next step!&lt;/p&gt;

&lt;h3&gt;
  
  
  Prisma
&lt;/h3&gt;

&lt;p&gt;Now we need to decide how we want to interact with our database. We could just send across big SQL query strings, but we're not living in the dark ages anymore!&lt;/p&gt;

&lt;p&gt;I have a background in Rails and really like the ORM (object relational mapping) style of interacting with databases so I am going to choose Prisma!&lt;/p&gt;

&lt;p&gt;Prisma is a query builder. It basically abstracts away complex SQL queries and allows you to write JavaScript code to talk to the DB. It's awesome! You'll see!&lt;/p&gt;

&lt;p&gt;Let's set it up! First we need to install it as a dev dependency&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -D prisma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we initialise Prisma in our project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we need to create our models - how we want to represent our data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url = env("DATABASE_URL")
}

model Course {
  id Int @id @default(autoincrement())
  title String @unique
  createdAt DateTime @default(now())
  lessons Lesson[]
}

model Lesson {
  id Int @id @default(autoincrement())
  title String @unique
  courseId Int
  createdAt DateTime @default(now())
  course Course @relation(fields: [courseId], references: [id])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are creating a course which has a collection of lessons. A lesson belongs to a course.&lt;/p&gt;

&lt;p&gt;We are just going to focus on our courses for now - users can come later!&lt;/p&gt;

&lt;p&gt;Now we want to update the DATABASE_URL in our .env with that connection string from Supabase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .env

DATABASE_URL="your connecting string"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you replace the password in the connection string with the password you used to create the Supabase project!&lt;/p&gt;

&lt;p&gt;Now we need to make sure we add this .env file to our .gitignore so as to never commit our secrets to GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .gitignore

node_modules/
.next/
.DS_Store
out/
.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, now that we have this hooked up to an actual database, we want to tell it to match our schema.prisma file. We do this by pushing the changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma db push --preview-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to pass the --preview-feature flag as this is an experimental feature, and may change in the future.&lt;/p&gt;

&lt;p&gt;Now we want to install the Prisma client, which we will use to send queries to our database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i @prisma/client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And generate our client based on the schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, let's create a serverless function to create some data in our database, and confirm everything is wired up correctly!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/create-course

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

module.exports = async (req, res) =&amp;gt; {
  await prisma.course.create({
    data: {
      title: 'Learning to code!',
      lessons: {
        create: { title: 'Learn the terminal' },
      },
    },
  })
  // TODO: send a response
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a new course with the title "Learning to code!", but it will also create the first lesson "Learn the terminal".&lt;/p&gt;

&lt;p&gt;This is the power of using a query builder like Prisma! Queries that would be quite complex in SQL are super easy to write and reason about!&lt;/p&gt;

&lt;p&gt;Let's add another prisma query to select the data we have written to the DB and send it back as the response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/create-course.js

module.exports = async (req, res) =&amp;gt; {
  // write to db
  const courses = await prisma.course.findMany({
    include: {
      lessons: true,
    },
  })
  res.send(courses)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our entire function should look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/create-course.js

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

module.exports = async (req, res) =&amp;gt; {
  await prisma.course.create({
    data: {
      title: 'Learning to code!',
      lessons: {
        create: { title: 'Learn the terminal' },
      },
    },
  })
  const courses = await prisma.course.findMany({
    include: {
      lessons: true,
    },
  })
  res.send(courses)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Excellent! Now we can run this serverless function by navigating to &lt;a href="http://localhost:3000/api/create-course"&gt;http://localhost:3000/api/create-course&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You should get back the newly created course and lesson. We can also see this has actually been written to the DB by inspecting our data in the Supabase dashboard.&lt;/p&gt;

&lt;p&gt;I recommend deleting this serverless function to avoid accidentally running it later and adding unnecessary courses! If you want to keep it as a reference, just comment out the code that creates the course.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// api/create-course.js

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

module.exports = async (req, res) =&amp;gt; {
  // await prisma.course.create({
  // data: {
  // title: 'Learning to code!',
  // lessons: {
  // create: { title: 'Learn the terminal' },
  // },
  // },
  // })
  // const courses = await prisma.course.findMany({
  // include: {
  // lessons: true,
  // },
  // })
  // res.send(courses)
  res.send('This is only here as a guide!')
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay! Let's wire this up to Next!&lt;/p&gt;

&lt;h2&gt;
  
  
  SSG
&lt;/h2&gt;

&lt;p&gt;Back in our pages/index.js component we want to query our DB for all courses and display them in a list. We could make this request when a user visits our site, but since this data is not going to change very often this will mean a huge number of unnecessary requests to our API and a lot of users waiting for the same data over and over again!&lt;/p&gt;

&lt;p&gt;What if we just requested this data when we build a new version of our application and bake the result into a simple HTML page. That would speed things up significantly and keep our users happy! A happy user is a user who wants to buy courses!&lt;/p&gt;

&lt;p&gt;Next.js makes this super simple with a function called getStaticProps. Lets extend our index.js file to export this function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const getStaticProps = async () =&amp;gt; {
  const data = await getSomeData()

  return {
    props: {
      data, // this will be passed to our Component as a prop
    },
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since this is going to be run when Next.js is building our application, it will be run in a node process, rather than in a browser. This might seem confusing since it is being exported from a component that will be running in the user's browser, but at build time there is no user - there is no browser!&lt;/p&gt;

&lt;p&gt;Therefore, we will need a way to make a request to our API from node. I am going to use Axios because I really like the API, but any HTTP request library will do!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i axios

// pages/index.js

import axios from 'axios'

// component declaration

export const getStaticProps = async () =&amp;gt; {
  const { data } = await axios.get('http://localhost:3000/api/get-courses')

  return {
    props: {
      courses: data,
    },
  }
}

// component export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whatever we return from getStaticProps will be passed into our component, so let's display that JSON blob in our component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/index.js

const Homepage = ({ courses }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Courses&amp;lt;/h1&amp;gt;
      &amp;lt;pre&amp;gt;
        {JSON.stringify(courses, null, 2)}
      &amp;lt;/pre&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default Homepage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We can pass JSON.stringify additional arguments (null and 2) in order to pretty print our data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our whole component should look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/index.js

import axios from 'axios'

const Homepage = ({ courses }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Courses&amp;lt;/h1&amp;gt;
      &amp;lt;pre&amp;gt;
        {JSON.stringify(courses, null, 2)}
      &amp;lt;/pre&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export const getStaticProps = async () =&amp;gt; {
  const { data } = await axios.get('http://localhost:3000/api/get-courses')

  return {
    props: {
      courses: data,
    },
  }
}

export default Homepage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we just need to create that get-courses serverless function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/api/get-courses.js

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

module.exports = async (req, res) =&amp;gt; {
  const courses = await prisma.course.findMany({
    include: {
      lessons: true,
    },
  })
  res.send(courses)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! We should now have an entire system wired up end-to-end!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js is requesting our courses from the serverless function at build time&lt;/li&gt;
&lt;li&gt;Our serverless function is using Prisma to query the Supabase DB for the courses&lt;/li&gt;
&lt;li&gt;The results are piping through from Supabase -&amp;gt; Serverless function -&amp;gt; Next.js, which is baking them into a static page&lt;/li&gt;
&lt;li&gt;The user requests this page and can see the courses&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tailwind
&lt;/h2&gt;

&lt;p&gt;I also decided to challenge my opinion that Tailwind is just ugly inline styles, and actually give it a try! You will be hearing from me often if I do not like it!&lt;/p&gt;

&lt;p&gt;Let's install it!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -D tailwindcss@latest postcss@latest autoprefixer@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next let's initialise some configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx tailwindcss init -p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also tell Tailwind to remove any unused styles in prod.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// tailwind.config.js

module.exports = {
  purge: ['./pages/ **/*.{js,ts,jsx,tsx}', './components/** /*.{js,ts,jsx,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are going to want to import Tailwind's CSS on every page, so will create an _app.js file, which automatically wraps every page component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'tailwindcss/tailwind.css'
import '../styles/globals.css'

const MyApp = ({ Component, pageProps }) =&amp;gt; &amp;lt;Component {...pageProps} /&amp;gt;

export default MyApp

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, create a styles/globals.css file to import the Tailwind bits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// styles/globals.css

@tailwind base;
@tailwind components;
@tailwind utilities;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome, now we have Tailwind configured. Check out &lt;a href="https://tailwindcss.com/docs"&gt;their docs&lt;/a&gt; for great examples!&lt;/p&gt;

&lt;p&gt;I will not be focusing on the styling aspect of this project throughout the blog series, but feel free to check out &lt;a href="https://github.com/dijonmusters/courses"&gt;the repo&lt;/a&gt; for pretty examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Great resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tom.preston-werner.com/2010/08/23/readme-driven-development.html"&gt;Readme Driven Development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/getting-started"&gt;Next.js docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project-typescript-postgres"&gt;Prisma in Next.js app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://supabase.io/docs/guides/platform"&gt;Supabase docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next week
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jonmeyers.io/blog/build-a-saas-platform-with-stripe/hosting-on-vercel-automatic-deploys-with-github-and-configuring-custom-domains"&gt;Hosting on Vercel, automatic deploys with GitHub and configuring custom domains&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Getting a job in the tech industry</title>
      <dc:creator>Jon</dc:creator>
      <pubDate>Wed, 24 Jun 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/jonmeyers_io/getting-a-job-in-the-tech-industry-3a1b</link>
      <guid>https://dev.to/jonmeyers_io/getting-a-job-in-the-tech-industry-3a1b</guid>
      <description>&lt;p&gt;So you have just finished a CS degree, bootcamp or are just not super engaged at your current role. Time for a new job! But how do you go about being noticed, standing out or getting the attention of your dream company?&lt;/p&gt;

&lt;p&gt;This article will go through some techniques that I have found particularly helpful in my career. I am definitely not a career coach and have no experience as a recruiter, however, in obtaining my last few software development roles I have been able to select the companies I am passionate about working for, and have managed to engage 100% of the companies I have contacted. Okay, there is the lame sales pitch done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't apply for open roles
&lt;/h2&gt;

&lt;p&gt;I know this seems counterintuative - the first places you look for jobs are job boards or LinkedIn, right? Wrong! Sure, these companies have vacancies, but they are publicly inviting people to compete for a small number, or potentially single role. This is a difficult game to win, especially if you're on the lighter side of demonstrated work experience.&lt;/p&gt;

&lt;p&gt;Additionally, these companies know that they have broadcasted their desire to employ someone, so the kinds of people who apply are potentially applying because they want/need any job, not necessarily because this job is their dream job! So you are already defending your reasons for applying for the role, rather than demonstrating how capable you are. Wouldn't it be better if your very first engagement with the company could tick the box for convincing them that this is your dream job, and then you only need to convince them of your capability within the role?!&lt;/p&gt;

&lt;p&gt;The best way to do this is to not apply for open roles. Shortlist - and I mean "short" - a few companies that you would really like to work for. For me and my first serious tech role, this was companies that would really help me learn and grow in my career, that had the "cool tech company" vibe from the outside. There are heaps of resources you can use to find places near you that match your preferences - GlassDoor, LinkedIn, Reddit etc.&lt;/p&gt;

&lt;p&gt;I found it particularly helpful looking at companies that host or sponsor local meetups and conferences, and talking to the speakers about where they work. Chances are if they are putting together interesting talks and mention the companies they work for, they probably have some kind of support from their companies to do these things they are passionate about, and that sounds like the kind of company I want to work for! I live in Melbourne, Australia so my list was Envato, Culture Amp, REA Group and Zendesk.&lt;/p&gt;

&lt;p&gt;Great, now I have a shortlist but who do I contact and what do I say to them? I need to get their attention but I don't want to waste their time with my work/education history in black text on a white background! What can I offer that people actually want?&lt;/p&gt;

&lt;h2&gt;
  
  
  The importance of coffee and beer
&lt;/h2&gt;

&lt;p&gt;Most people like coffee or beer! And throughout the work day, most people would really rather be drinking coffee or beer - depending on what time it is! Why not ask someone from the company if they have time for a coffee during the day or a quick beer after work? Getting someone to sit down with you and have an actual conversation is much more engaging than reading anything you sent them, or speaking with them on the phone. You actually have their attention.&lt;/p&gt;

&lt;p&gt;It also feels a lot like the first stage of an interview to me - but one that you didn't need to compete with others to get. This is focused one-on-one time where you can really tell your story, ask any questions you would like clarified and introduce them to your personality. Additionally, this demonstrates that you are self driven and motivated, and have a real interest in this company - getting you over that first hurdle, and into only needing to convince them of your capabilities, rather than your interest.&lt;/p&gt;

&lt;p&gt;So who do you contact? How do you contact them?&lt;/p&gt;

&lt;p&gt;This is probably the stand alone most important piece of advice from this whole article. If you only take away one thing to actually do, make it this!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sign up for a free trial LinkedIn Premium account.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The second most important piece of advice is make sure you cancel it before the trial runs out as it is surprisingly expensive!&lt;/p&gt;

&lt;p&gt;What a premium account gives you is the ability to privately message people outside your connections. Now to find the actual people. I think a good rule of thumb is to contact three people from each company - at least one of these people from a high up position in the recruitment team and one high up in tech. It's important to mention the names of each of the other people you are contacting in your message so if they do discuss your message in the team it doesn't just look like you blasted everyone at the company with a generic message.&lt;/p&gt;

&lt;p&gt;Make sure it is something short and to the point, with an option for the person to palm you off to someone else is best. Something along the lines of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hi! My name is BLAH. I am really keen to work at NAME OF COMPANY, and wondered if you would have some time to catch up for coffee or beer after work sometime next week? I have also reached out to PERSON and OTHER PERSON, so please let me know if they - or someone else from your team - would be more appropriate! Thanks for your time and look forward to meeting with you soon!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have had 100% success rate with this method. When I contacted all four companies I ended up having a coffee catchup and was given the opportunity to explain who I was and what I was after. In all four cases this gave me an advocate within the company who could push on my behalf. It also allowed me to skip the first level of interviews in most cases.&lt;/p&gt;

&lt;p&gt;Make sure you prepare well for this catchup. This is your opportunity to show them how keen you are to work at this company and ask any questions that may improve your chances. Stalk them on LinkedIn/Twitter, understand their background and ask them good questions!&lt;/p&gt;

&lt;p&gt;Maybe you don't have a great deal of demonstrated work experience in the area of example projects to show your work. What is another way to get their attention?&lt;/p&gt;

&lt;h2&gt;
  
  
  Do something CRAZY!
&lt;/h2&gt;

&lt;p&gt;Okay, not too crazy but you want to stand out! I know this sounds difficult but it doesn't have to be a giant task. Think about something unique that you enjoy doing, that could help you stand out in this role. It could be as simple as bright colours on your resume, or recording a podcast about the things you have learnt about the company that make you want to work there.&lt;/p&gt;

&lt;p&gt;When applying for my most recent role I wanted to work for a company that was very consumer and brand focused. I wanted to build beautiful things that would be appreciated, so I wanted to demonstrate this when getting their attention. I built a clone of their website - using their brand colours, fonts and similar language - but with the content talking about why they should hire me. It worked as an interactive job application, but it stands out much more than black text on a white background! It demonstrated my interest in their specific company and knowledge of their brand.&lt;/p&gt;

&lt;p&gt;Okay, so this is a super time consuming activity, right? You can't build 20 unique websites for companies you want to work for. Exactly! Don't do that! Refer to the point in the "Don't apply for open roles" section. You want to specifically target a small handful of companies that you are actually excited to work for. Ones where you can be your authentic self! You are way more likely to get someone's attention building three properly targeted applications than sending out 100 text-based boring, same same, generic job applications. That's probably why you have felt like this was such a hopeless endeavour in the past. You can't stand out doing the same thing as everyone else!&lt;/p&gt;

&lt;h2&gt;
  
  
  Exaggerate the right things
&lt;/h2&gt;

&lt;p&gt;Okay, you have their attention, now what do they want to hear? All companies and teams have specific things that they value over others, but I would say the most consistent things that companies want from employees are:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Learning&lt;/em&gt;: Companies want you to have a burning desire to learn new things. This is a guarantee that their investment in you is going to increase in value over time. The more things you learn over time, the more problems you can solve for them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Communication&lt;/em&gt;: You need to be able to communicate with other people. Companies don't want you to just take a task and complete it on your own. The tech world has become too complicated. It requires a lot of compromises, empathy and discussion.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Resilience&lt;/em&gt;: Sometimes people disagree on a solution - especially in an industry of strongly opinionated people. You need the ability to navigate this calmly. Companies want you to demonstrate that you are comfortable calmly defending or justifying your idea, as well as listening to others and negotiating.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be yourself
&lt;/h2&gt;

&lt;p&gt;I know this just sounds like standard recruitment fluff, but you really do need to be yourself. I have always had a problem with wearing nicely ironed, tucked in shirts to work. I thought that I was just destined to feel uncomfortable during work hours because that's what you need to do to get paid. I'm sure if you have done any work as a software developer (or any "cool" company) in the last five years you are probably laughing at the moment, but this is definitely not required at good companies with good culture!&lt;/p&gt;

&lt;p&gt;I realised wearing comfortable clothes was something that was legitimately important to me, and anytime I could get away with it I would not iron my shirt, untuck it or change into a t-shirt the moment work was done. I was desperate to be comfortable and my workplace was not okay with it. But other companies were so I started working there instead. Being comfortable at work made me feel respected as an individual and improved my feelings of belonging and acceptance. It also meant I wasn't wasting mental energy feeling frustrated about something that didn't affect the quality of my work in any way, meaning I could focus on doing better work. Find those things that are important to you and find the companies that are comfortable with it. Your company's expectations being aligned with you being yourself is so important to your overall happiness and productivity!&lt;/p&gt;

&lt;h2&gt;
  
  
  Work experience and CS degrees are not important
&lt;/h2&gt;

&lt;p&gt;Obviously anything you have to demonstrate your interest and experience in this field is important to help strengthen your argument, but you do NOT need work experience and you do NOT need a CS degree! Some companies will have this as a requirement so don't work for those companies - they sound like shit places to work. Things that really demonstrate interest and experience are:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Bootcamps&lt;/em&gt;: These are tough and require a lot of time and dedication. Talk about that!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Side projects&lt;/em&gt;: This shows you even want to work on this stuff when you're not being paid to do so!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Building websites for local businesses&lt;/em&gt;: This can help you build a portfolio of projects to demonstrate your ability. You also might be able to help out some businesses that may not be doing too well - especially at the moment - so win win. Additionally, you don't need anyone else's permission to do this! Obviously do not impersonate someone else's business if they do not want you to make the website live, but you could build a website for them before contacting them, then send them the finished version and ask if they would like to use it. This may also lead to some referrals and more work meaning you don't actually need to apply for a new role anyway!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Attending meetups&lt;/em&gt;: Go check out some local meetups that are related to the kind of work you want to do. A lot of them will have representatives from companies that are looking to hire developers that want to spend their spare evenings learning and talking about code! It is also a great opportunity to network with developers from other companies and learn about what it's actually like working there. I absolutely hate doing this but it has really helped me and my career development. Treat it like getting those needles you know are helping you, or going to the dentist.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Short resume&lt;/em&gt;: No one cares about your retail job from 15 years ago if you have three years experience as a developer. Try to keep it as relevent as possible. You can always list your entire work history on LinkedIn and send people there if they want to learn more. Use this resume to list only the things you want to use to entice the person reading it. Try to keep this to one page of distilled, attention grabbing bits of information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Applying for jobs sucks! Try to reduce the pain by increasing your chances of working for the first company you contact! Do what you can to reduce the amount of people you are competing with for attention by doing things out of the ordinary and being extraordinarily interesting! Meeting people in person is super effective - you have their full attention so don't waste it! Find companies that are an approprate fit for your personality so neither of you need to lie. Don't undervalue the stuff outside of work experience and formal degrees. Companies want to invest in good people, so just convince them you are that!&lt;/p&gt;

</description>
      <category>career</category>
      <category>tips</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Using custom hooks to reduce component complexity</title>
      <dc:creator>Jon</dc:creator>
      <pubDate>Thu, 28 May 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/jonmeyers_io/using-custom-hooks-to-reduce-component-complexity-1hjm</link>
      <guid>https://dev.to/jonmeyers_io/using-custom-hooks-to-reduce-component-complexity-1hjm</guid>
      <description>&lt;p&gt;This article continues on from where &lt;a href="https://dev.to/dijonmusters/simple-caching-with-local-storage-49ap"&gt;Simple caching with local storage&lt;/a&gt; left off. Check that out for context on how local storage can be used as a super simple cache, when requesting data from an API.&lt;/p&gt;

&lt;p&gt;In this article we will look at abstracting our request and caching logic into reusable React Hook components. Hooks are a really nice way to bundle up our complicated and confusing code into a package that we don't need to think about anymore, and can reuse across our application and other projects!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3CNT6EIY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.youtube.com/embed/KmdjxIvDPSA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3CNT6EIY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.youtube.com/embed/KmdjxIvDPSA" alt="Using custom hooks to reduce complexity"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This video goes through the same example as the blog, but may be more enjoyable for those that are visual learners/want some extra company!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We have already been using the &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt; hooks that are provided by React to simplify our data logic, but we can do so much more with our own custom hooks!&lt;/p&gt;

&lt;p&gt;The important parts to remember from the previous article are the request functions and our &lt;code&gt;&amp;lt;Skaters /&amp;gt;&lt;/code&gt; component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/utils/request.js

import axios from 'axios'
import { readFromCache, writeToCache } from './cache'

const getFreshData = async (url, cacheResponse = false) =&amp;gt; {
  const { data } = await axios.get(url)
  cacheResponse &amp;amp;&amp;amp; writeToCache(url, data)
  return data
}

const getCachedData = url =&amp;gt; readFromCache(url)

export { getCachedData, getFreshData }


// src/Skaters.js

import React, { useState } from 'react'
import { getCachedData, getFreshData } from './utils/request'

const url = 'https://thps.now.sh/api/skaters'

const renderSkater = ({ name, stance }) =&amp;gt; (
  &amp;lt;div key={name}&amp;gt;
    &amp;lt;p&amp;gt;
      {name} - {stance}
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
)

const Skaters = ({ useCache }) =&amp;gt; {
  const [skaters, setSkaters] = useState([])

  const getSkaters = async () =&amp;gt; {
    setSkaters([])

    if (useCache) {
      const cachedSkaters = getCachedData(url)
      if (cachedSkaters) {
        setSkaters(cachedSkaters)
      }
    }

    const freshSkaters = await getFreshData(url, useCache)
    setSkaters(freshSkaters)
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;{skaters.map(renderSkater)}&amp;lt;/div&amp;gt;
      &amp;lt;button onClick={getSkaters}&amp;gt;Load&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default Skaters

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's first look at refactoring our request logic as a custom React Hook. We can leave the old functions there as a reference and create a new &lt;code&gt;hooks&lt;/code&gt; folder under the &lt;code&gt;src&lt;/code&gt; directory. Inside this new folder create a new file named &lt;code&gt;useRequest.js&lt;/code&gt;. By convention all hooks must start with the word &lt;code&gt;use&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's start with creating the skeleton for our useRequest hook, which will take in a url as a parameter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const useRequest = url =&amp;gt; {}

export default useRequest

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we are going to need some state and the ability to trigger our requests when our hook is being consumed, so let's bring in &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useState, useEffect } from 'react'

const useRequest = url =&amp;gt; {
  const [data, setData] = useState()

  useEffect(() =&amp;gt; {
    // request data
    // call setData with new value
  }, [])

  return data
}

export default useRequest

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should look pretty familiar. We have a &lt;code&gt;data&lt;/code&gt; variable that is being returned from our hook. Anytime we update the value of that variable - by using &lt;code&gt;setData&lt;/code&gt; - it will trigger a re-render for anything consuming our hook. You can think of this as a &lt;code&gt;live&lt;/code&gt; variable. Any component using that variable does not need to understand when or why it will change, but anytime it does change the component will be told to re-render with the new value. Magic!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useEffect&lt;/code&gt; is where we will add some logic for requesting fresh data from the API and updating our &lt;code&gt;data&lt;/code&gt; variable with the response. We are giving it an empty array of dependencies &lt;code&gt;[]&lt;/code&gt; so that this logic only runs when the hook is first consumed - meaning we are not requesting the data from the API over and over again, just once when our page is loaded. This is slightly different to the example in our previous article - where we were loading data based off a button click - but we don't want our users to have to wait for the page to be loaded and then click a button to see data. We can just give it to them as soon as we can!&lt;/p&gt;

&lt;p&gt;Let's bring in axios, make a request for our fresh data and update the &lt;code&gt;data&lt;/code&gt; value with the &lt;code&gt;response&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useState, useEffect } from 'react'
import axios from 'axios'

const useRequest = url =&amp;gt; {
  const [data, setData] = useState()

  const getFreshData = async () =&amp;gt; {
    const { data: response } = await axios.get(url)
    setData(response)
  }

  useEffect(() =&amp;gt; {
    getFreshData()
  }, [])

  return data
}

export default useRequest

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Something that may look a little weird here is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { data: response } = await axios.get(url)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;{ data: response }&lt;/code&gt; part is destructuring &lt;code&gt;data&lt;/code&gt; from the response, but we already have a &lt;code&gt;data&lt;/code&gt; variable in scope. &lt;code&gt;data&lt;/code&gt; is the name of our state variable. This will cause a naming collision, as we won't know which &lt;code&gt;data&lt;/code&gt; variable we are referring to. So the &lt;code&gt;{ data: response }&lt;/code&gt; part is destructuring &lt;code&gt;data&lt;/code&gt; and immediately renaming the variable to &lt;code&gt;response&lt;/code&gt;. This makes our code a little clearer to read aswell, as on the next line we are setting our &lt;code&gt;data&lt;/code&gt; variable to be equal to the &lt;code&gt;response&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Awesome! Now we have a useRequest hook that can be consumed by any component that needs to request data from an API. Using this hook in our &lt;code&gt;&amp;lt;Skaters /&amp;gt;&lt;/code&gt; component would look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const url = 'https://thps.now.sh/api/skaters'
const skaters = useRequest(url)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gosh, that is so much simpler! But now our component would need to check whether the &lt;code&gt;skaters&lt;/code&gt; variable contained data before rendering it. Also, if we follow the &lt;code&gt;useRequest&lt;/code&gt; logic, the &lt;code&gt;data&lt;/code&gt; variable is initialised as &lt;code&gt;null&lt;/code&gt;, and then its value is magically be updated to an array when the &lt;code&gt;response&lt;/code&gt; comes back from the API. That will require some additional rendering logic in our &lt;code&gt;&amp;lt;Skaters /&amp;gt;&lt;/code&gt;component to determine whether our request is still waiting for the response (loading).&lt;/p&gt;

&lt;p&gt;Why don't we refactor our useRequest hook to provide this information, as determining the &lt;code&gt;loading&lt;/code&gt; state of our &lt;code&gt;data&lt;/code&gt; does feel like the responsibility of our request hook, rather than our rendering component. Plus it is super simple to do!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useState, useEffect } from 'react'
import axios from 'axios'

const useRequest = url =&amp;gt; {
  const [data, setData] = useState()

  const getFreshData = async () =&amp;gt; {
    const { data: response } = await axios.get(url)
    setData(response)
  }

  useEffect(() =&amp;gt; {
    getFreshData()
  }, [])

  const loading = !data

  return {
    data,
    loading,
  }
}

export default useRequest

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All we have changed are the last few lines of our hook. We created a &lt;code&gt;loading&lt;/code&gt; variable - set to whether we actually have data or not - and instead of returning the &lt;code&gt;data&lt;/code&gt; variable, we are returning an object with our &lt;code&gt;data&lt;/code&gt; and &lt;code&gt;loading&lt;/code&gt; states.&lt;/p&gt;

&lt;p&gt;Now our consuming component would look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const url = 'https://thps.now.sh/api/skaters'
const { data, loading } = useRequest(url)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And again we could use that renaming while destructuring trick to give our &lt;code&gt;data&lt;/code&gt; some context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const url = 'https://thps.now.sh/api/skaters'
const { data: skaters, loading } = useRequest(url)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now, remaining positive and assuming everything is going to go according to plan is always a good idea ... except in programming! We have a lovely interface exposing our loading and data states, but no way to tell if something went wrong. Let's add error handling. We can wrap our fetching logic in a &lt;code&gt;try catch&lt;/code&gt;, which will attempt to run what is in the try block and then trigger the catch block if an error occurs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try {
  // try something
} catch (e) {
  // an error happened
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see what that would look like wrapping our request logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useState, useEffect } from 'react'
import axios from 'axios'

const useRequest = url =&amp;gt; {
  const [data, setData] = useState()
  const [error, setError] = useState()

  const getFreshData = async () =&amp;gt; {
    try {
      const { data: response } = await axios.get(url)
      setData(response)
    } catch (e) {
      setError(e)
    }
  }

  useEffect(() =&amp;gt; {
    getFreshData()
  }, [])

  const loading = !data &amp;amp;&amp;amp; !error

  return {
    data,
    loading,
    error,
  }
}

export default useRequest

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few small changes here. We added an &lt;code&gt;error&lt;/code&gt; variable with &lt;code&gt;useState&lt;/code&gt;, wrapped our fetching logic in a &lt;code&gt;try catch&lt;/code&gt;, updated our loading state to account for errors, and exposed the &lt;code&gt;error&lt;/code&gt; variable to our consumers.&lt;/p&gt;

&lt;p&gt;Awesome! Now our consuming component would look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const url = 'https://thps.now.sh/api/skaters'
const { data: skaters, loading, error } = useRequest(url)

if (loading) return &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;
if (error) return &amp;lt;p&amp;gt;There was an error!&amp;lt;/p&amp;gt;

// At this point we are confident that we have
// our data so we can just render it!
return skaters.map(renderSkaters)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last thing we need to do here is implement our caching from the previous article. We can do this within the same hook and not need to change our consuming interface. All we need to do is modify our &lt;code&gt;getFreshData&lt;/code&gt; to write the API response to the cache and create a new function to attempt to &lt;code&gt;getCachedData&lt;/code&gt; first. This is what our final &lt;code&gt;useRequest&lt;/code&gt; hook looks like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useState, useEffect } from 'react'
import axios from 'axios'
import { readFromCache, writeToCache } from './cache'

const useRequest = url =&amp;gt; {
  const [data, setData] = useState()
  const [error, setError] = useState()

  const getFreshData = async () =&amp;gt; {
    try {
      const { data: response } = await axios.get(url)
      writeToCache(url, response)
      setData(response)
    } catch (e) {
      setError(e)
    }
  }

  const getCachedData = () =&amp;gt; {
    const cachedData = readFromCache(url)
    cachedData &amp;amp;&amp;amp; setData(cachedData)
  }

  useEffect(() =&amp;gt; {
    getCachedData()
    getFreshData()
  }, [])

  const loading = !data &amp;amp;&amp;amp; !error

  return {
    data,
    loading,
    error,
  }
}

export default useRequest

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before refactoring our &lt;code&gt;&amp;lt;Skaters /&amp;gt;&lt;/code&gt; component let's take a quick look at what we had in the previous article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/Skaters.js

import React, { useState } from 'react'
import { getCachedData, getFreshData } from './utils/request'

const url = 'https://thps.now.sh/api/skaters'

const renderSkater = ({ name, stance }) =&amp;gt; (
  &amp;lt;div key={name}&amp;gt;
    &amp;lt;p&amp;gt;
      {name} - {stance}
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
)

const Skaters = ({ useCache }) =&amp;gt; {
  const [skaters, setSkaters] = useState([])

  const getSkaters = async () =&amp;gt; {
    setSkaters([])

    if (useCache) {
      const cachedSkaters = getCachedData(url)
      if (cachedSkaters) {
        setSkaters(cachedSkaters)
      }
    }

    const freshSkaters = await getFreshData(url, useCache)
    setSkaters(freshSkaters)
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;{skaters.map(renderSkater)}&amp;lt;/div&amp;gt;
      &amp;lt;button onClick={getSkaters}&amp;gt;Load&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default Skaters

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It contains a lot of logic around caching and requesting that is not really related to skaters. Let's have a look at the refactored version and see what it's responsible for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/Skaters.js

import React from 'react'

const url = 'https://thps.now.sh/api/skaters'

const renderSkater = ({ name, stance }) =&amp;gt; (
  &amp;lt;div key={name}&amp;gt;
    &amp;lt;p&amp;gt;
      {name} - {stance}
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
)

const Skaters = () =&amp;gt; {
  const { data: skaters, loading, error } = useRequest(url)

  if (loading) return &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;
  if (error) return &amp;lt;p&amp;gt;There was an error!&amp;lt;/p&amp;gt;

  return skaters.map(renderSkater)
}

export default Skaters

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wow! Firstly, it's a lot smaller, easier to read and the component doesn't need to know anything about caching or fetching logic. It simply uses our &lt;code&gt;useRequest&lt;/code&gt; hook which handles the complexity and exposes our three different states: &lt;code&gt;loading&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt;. This is a fairly common pattern for data fetching libraries - such as &lt;a href="https://www.apollographql.com/docs/react/v3.0-beta/"&gt;Apollo Client&lt;/a&gt; for GraphQL.&lt;/p&gt;

&lt;p&gt;This example does not implement the ability to make a request without using the cache. This is because the cache is cool! You wanna use the cache! Forever and always! Right? I guess if you really want to implement the ability to switch off the cache, or just take a look at the full working example, check out the &lt;a href="https://github.com/dijonmusters/thps-with-hooks/"&gt;THPS with hooks repo.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>custom</category>
      <category>react</category>
      <category>hooks</category>
    </item>
    <item>
      <title>Simple caching with local storage</title>
      <dc:creator>Jon</dc:creator>
      <pubDate>Tue, 17 Mar 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/jonmeyers_io/simple-caching-with-local-storage-49ap</link>
      <guid>https://dev.to/jonmeyers_io/simple-caching-with-local-storage-49ap</guid>
      <description>&lt;p&gt;Computers are fast. Like, really fast! So why do we see so many loading spinners on the web and have to wait so long for things to load? Because networks are slow! Sending information across the world is slow and by using wonderful 3rd party services to solve our problems - Netlify, Stripe, AWS etc - our webapps could be making requests to several places around the world, before the user gets a chance to interact with the page. This is a complicated problem to solve and probably requires a lot of smart devops people and tech leads, or can we solve part of it in JS? Spoilers, we can!&lt;/p&gt;

&lt;p&gt;This video above goes through the same example as this blog, but may be more enjoyable for those that are visual learners/want some extra company while implementing caching!&lt;/p&gt;

&lt;p&gt;Let's say we have a request that is hitting a Tony Hawks Pro Skater API and requesting all skaters.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: I am using the axios library for simplicity's sake but this could definitely be done with fetch&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import axios from 'axios'

const getSkaters = async () =&amp;gt; {
  const url = 'https://thps.now.sh/api/skaters'

  // wait for a response from the API
  const { data } = await axios.get(url)

  return data
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns a response that looks something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "name": "Tony Hawk",
    "style": "Vert",
    "stance": "Goofy",
    "speed": 6,
    "air": 7,
    "hangtime": 5
  },
  {
    "name": "Bob Burnquist",
    "style": "All around",
    "stance": "Regular",
    "speed": 5,
    "air": 6,
    "hangtime": 5
  },
  ...
]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everytime we call the &lt;code&gt;getSkaters()&lt;/code&gt; function we are waiting for our app to send a request to the Tony Hawk API. This could only take a matter of milliseconds in an ideal scenario - low API traffic, small dataset, our app and API hosted in similar geographic locations - but to address what I'm sure you're all thinking, there are surely millions of people all day every day that wanna hit that API non stop to get those sweet skater stats!&lt;/p&gt;

&lt;p&gt;Now we can't do much about the first request, as the client has no idea about player stats until our API tells them, however, we could implement our own way of caching that data the first time - making it available without a full round trip to the API server. Now how will we build a cache? A cache is just some data that we remember we got back from a particular request (or URL). Thankfully the browser has this wonderful and simple way to store data called &lt;code&gt;localStorage&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;localStorage.setItem(key, data) // writes the data against that key

localStorage.getItem(key) // returns the data associated with that key

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LocalStorage allows us to read and write key/value pairs to and from the browser's storage. To write some data to localStorage we need a key - something unique to identify our data - and the data we want to store there. Let's build some convenience functions for interfacing with localStorage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/utils/cache.js

const writeToCache = (url, data) =&amp;gt;
  localStorage.setItem(url, JSON.stringify(data))

const readFromCache = url =&amp;gt; JSON.parse(localStorage.getItem(url)) || null

export { readFromCache, writeToCache }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully you are comfortable with the ES6 syntax, arrow functions and implicit returns used above. If not, these are two functions that abstract reading and writing from the cache. What is on the right-hand side of &lt;code&gt;=&amp;gt;&lt;/code&gt; is what is returned from the function.&lt;/p&gt;

&lt;p&gt;We are using the &lt;code&gt;url&lt;/code&gt; as our key, as this will always be unique.&lt;/p&gt;

&lt;p&gt;There are a few other weird things going on here, particularly &lt;code&gt;parse&lt;/code&gt; and &lt;code&gt;stringify&lt;/code&gt;. These functions are the inverse of each other. One takes a json object and turns it into a big string (stringify), the other takes a big string and turns it into a json object (parse). This is required because localStorage only allows us to store strings.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;||&lt;/code&gt; syntax is used to provide a fallback value. So we are checking whether there is any data stored against that url that we can parse into a json object, if not we want to return &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;export&lt;/code&gt; is used to expose these functions from the file so that other files can &lt;code&gt;import&lt;/code&gt; them.&lt;/p&gt;

&lt;p&gt;Next, we want to abstract our request out of our &lt;code&gt;getSkaters()&lt;/code&gt; function and make it more generic, and cache-able.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/utils/request.js

import axios from 'axios'
import { writeToCache } from './cache'

const getFreshData = async (url, cacheResponse = false) =&amp;gt; {
  const { data } = await axios.get(url)
  cacheResponse &amp;amp;&amp;amp; writeToCache(url, data)
  return data
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, we are creating a new file to abstract away our reusable logic. Our &lt;code&gt;getFreshData()&lt;/code&gt; function now takes a &lt;code&gt;url&lt;/code&gt; and a &lt;code&gt;cacheResponse&lt;/code&gt; boolean. The &lt;code&gt;url&lt;/code&gt; is used to request our data from the API, and &lt;code&gt;cacheResponse&lt;/code&gt; tells our function whether we want to write that response to the cache. Let's extend this file to contain a &lt;code&gt;getCachedData()&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const getCachedData = url =&amp;gt; readFromCache(url)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one is really just wrapping &lt;code&gt;readFromCache&lt;/code&gt;, but it means anything that needs to request data can just use this one file, reguardless of whether they want fresh or cached content. The final version of the file should look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import axios from 'axios'
import { readFromCache, writeToCache } from './cache'

const getFreshData = async (url, cacheResponse = false) =&amp;gt; {
  const { data } = await axios.get(url)
  cacheResponse &amp;amp;&amp;amp; writeToCache(url, data)
  return data
}

const getCachedData = url =&amp;gt; readFromCache(url)

export { getCachedData, getFreshData }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's create our &lt;code&gt;Skaters&lt;/code&gt; component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState } from 'react'

const renderSkater = ({ name, stance }) =&amp;gt; (
  &amp;lt;div key={name}&amp;gt;
    &amp;lt;p&amp;gt;
      {name} - {stance}
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
)

const Skaters = () =&amp;gt; {
  const [skaters, setSkaters] = useState([])

  return (
    &amp;lt;&amp;gt;
      &amp;lt;div&amp;gt;{skaters.map(renderSkater)}&amp;lt;/div&amp;gt;
      &amp;lt;button&amp;gt;Load&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

export default Skaters

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is simply interating over an array of skaters and displaying the name and stance of each one.&lt;/p&gt;

&lt;p&gt;Let's extend it to take a &lt;code&gt;useCache&lt;/code&gt; prop, and make a request for either the fresh or cached version of the skaters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState } from 'react'
import { getCachedData, getFreshData } from './utils/request'

const url = 'https://thps.now.sh/api/skaters'

const renderSkater = ({ name, stance }) =&amp;gt; (
  &amp;lt;div key={name}&amp;gt;
    &amp;lt;p&amp;gt;
      {name} - {stance}
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
)

const Skaters = ({ useCache }) =&amp;gt; {
  const [skaters, setSkaters] = useState([])

  const getSkaters = async () =&amp;gt; {
    setSkaters([])

    if (useCache) {
      const cachedSkaters = getCachedData(url)
      if (cachedSkaters) {
        setSkaters(cachedSkaters)
      }
    } else {
      const freshSkaters = await getFreshData(url, useCache)
      setSkaters(freshSkaters)
    }
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;{skaters.map(renderSkater)}&amp;lt;/div&amp;gt;
      &amp;lt;button onClick={getSkaters}&amp;gt;Load&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default Skaters

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, now we can render our Skaters component, either displaying a list of skaters from the cache or requesting a fresh copy from the API. There are a couple of bugs here though.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;We can't actually display skaters from the cache if we have not yet made a request for fresh skaters and written them to the cache.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once we have written the skaters to the cache once, they are there forever. We can never overwrite them or get fresh skaters if the API updates.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Something that will fix both of these problems, and improve our user experience significantly is making a request for the cached version first and then requesting the fresh copy regardless of whether it was in the cache or not. This means if we have a cached version, it will display immediately and when fresh content is available it will automatically update the UI. This sounds complicated but all we really do is remove our &lt;code&gt;else&lt;/code&gt; wrapping the fresh request logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState } from 'react'
import { getCachedData, getFreshData } from './utils/request'

const url = 'https://thps.now.sh/api/skaters'

const renderSkater = ({ name, stance }) =&amp;gt; (
  &amp;lt;div key={name}&amp;gt;
    &amp;lt;p&amp;gt;
      {name} - {stance}
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
)

const Skaters = ({ useCache }) =&amp;gt; {
  const [skaters, setSkaters] = useState([])

  const getSkaters = async () =&amp;gt; {
    setSkaters([])

    if (useCache) {
      const cachedSkaters = getCachedData(url)
      if (cachedSkaters) {
        setSkaters(cachedSkaters)
      }
    }

    const freshSkaters = await getFreshData(url, useCache)
    setSkaters(freshSkaters)
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;{skaters.map(renderSkater)}&amp;lt;/div&amp;gt;
      &amp;lt;button onClick={getSkaters}&amp;gt;Load&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default Skaters

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome! Now our users will see the cached version immediately, and receive any updates once the API responds. Here is a gif demonstrating what this looks like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SyE93RqR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://monoglot.dev/static/simple-caching-337719fe91ead5e7b31de0cde216f5f0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SyE93RqR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://monoglot.dev/static/simple-caching-337719fe91ead5e7b31de0cde216f5f0.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, we can't provide the cached version the first time our users visit the page, but everytime they come back to see those sweet sweet skater stats, we can make the experience feel much more responsive!&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://thps.now.sh/"&gt;live version&lt;/a&gt; of this example or the &lt;a href="https://github.com/dijonmusters/thps"&gt;GitHub repo&lt;/a&gt; to see how it all clicks together!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: You will need to clear local storage in the dev tools to see the first request again in the cached version (right).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the next blog we will look at abstracting the &lt;code&gt;getCachedData()&lt;/code&gt; and &lt;code&gt;getFreshData()&lt;/code&gt; into hooks that we can use in any application!&lt;/p&gt;

</description>
      <category>caching</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
