<?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: Jesse Martin</title>
    <description>The latest articles on DEV Community by Jesse Martin (@motleydev).</description>
    <link>https://dev.to/motleydev</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%2F201981%2F50fca287-8dd1-4fda-acad-5101f42c15cc.jpg</url>
      <title>DEV Community: Jesse Martin</title>
      <link>https://dev.to/motleydev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/motleydev"/>
    <language>en</language>
    <item>
      <title>A Tutorial using Hasura with Draftbit, a low-code tool for building mobile apps</title>
      <dc:creator>Jesse Martin</dc:creator>
      <pubDate>Thu, 05 Aug 2021 18:02:32 +0000</pubDate>
      <link>https://dev.to/motleydev/a-tutorial-using-hasura-with-draftbit-a-low-code-tool-for-building-mobile-apps-b74</link>
      <guid>https://dev.to/motleydev/a-tutorial-using-hasura-with-draftbit-a-low-code-tool-for-building-mobile-apps-b74</guid>
      <description>&lt;p&gt;In this tutorial we will integrate Hasura with the low-code mobile app development tool Draftbit. We will scaffold a basic application with Draftbit, map the required data to a couple of GraphQL queries, and save those queries behind idiomatic REST endpoints in the Hasura console. As mobile-first development continues to lead many of today’s go-to-market strategies for new initiatives, tools like Draftbit allow teams to iterate on layouts visually, with native code running in the background.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hasura features we’ll use
&lt;/h2&gt;

&lt;p&gt;We will be using Hasura’s ability to generate instant REST endpoints to integrate with the UI components that Draftbit makes available to us. The primary glue to integrate Hasura with Draftbit is creating idiomatic REST endpoints for GraphQL queries in the Hasura console. This will allow us to define a query for Draftbit (which doesn’t support GraphQL), on the Hasura side and save that behind a RESTful endpoint. More on that later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our data model
&lt;/h2&gt;

&lt;p&gt;We’ll be re-using an existing API for our project from an auto-tagging image service. &lt;a href="https://hasura.io/blog/hasura-design-patterns-asynchronous-content-enhancement-and-analysis/"&gt;If you’d like to follow along you can find the original tutorial here&lt;/a&gt;. The concepts involved are universal, so you can feel free to simply adapt the use-case for your individual project.&lt;/p&gt;

&lt;p&gt;As a point of reference, our model reflects the following pattern.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KpbP2l39--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/cESwXjkMXDvlimORQJymOJWRifOLOhyKpjKVM1eYdfAwY_16ZqzaLNFA3xYxMadY76LCY0XuBXThl3RYHJTSgY_Aht9RI71_DbpJ05Sr1fbRNs7NZiGllGVKFD2TcEyLmBGOj0Uf" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KpbP2l39--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/cESwXjkMXDvlimORQJymOJWRifOLOhyKpjKVM1eYdfAwY_16ZqzaLNFA3xYxMadY76LCY0XuBXThl3RYHJTSgY_Aht9RI71_DbpJ05Sr1fbRNs7NZiGllGVKFD2TcEyLmBGOj0Uf" alt="Diagram of content model."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The exact models vary, and include an upload join table with both photo and tags associated with a user.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interface
&lt;/h2&gt;

&lt;p&gt;For the purposes of learning we won’t be creating anything detailed with the interface. We’ll be using stock components from Draftbit and create an interface that let us view all the photos in a list, then view the details (tags) for an individual photo.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tutorial
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A working, accessible &lt;a href="https://hasura.info/dev_jesse"&gt;Hasura project&lt;/a&gt; (not running on localhost) with existing data.&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://draftbit.com?ref=hasura"&gt;A Draftbit account.&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;From Draftbit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a new project from the Dashboard view.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eu-5jogP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/7xZl4QIoCQ7HEHsCBcs4-L67vnQT1rjOCWpBFZpBZXQ57NXeC5rhemwZSvGn7RgHjlLotaLtew4rDbBv9SrovSrOA2xnq18dMtsrSZhwTn9igCDiPqnCskZpERGAQy0hCi9UDqgL" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eu-5jogP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/7xZl4QIoCQ7HEHsCBcs4-L67vnQT1rjOCWpBFZpBZXQ57NXeC5rhemwZSvGn7RgHjlLotaLtew4rDbBv9SrovSrOA2xnq18dMtsrSZhwTn9igCDiPqnCskZpERGAQy0hCi9UDqgL" alt="Draftbit dashboard."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Provide some details about your application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JoSJHrlj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/a8aEa6wuOgYllUYlSl7rixaSElG604QG-mjU-Z_Zmchs7fjgcoE4q9o_VJMOS5YsXzMjVT_mfKonidNrMATu0kougvjPiR7YV8fQnq7X_cMpl2gnUflyBsudEujhVelcRdZ4jFM9" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JoSJHrlj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/a8aEa6wuOgYllUYlSl7rixaSElG604QG-mjU-Z_Zmchs7fjgcoE4q9o_VJMOS5YsXzMjVT_mfKonidNrMATu0kougvjPiR7YV8fQnq7X_cMpl2gnUflyBsudEujhVelcRdZ4jFM9" alt="Name and description for Draftbit project."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And for our demo we’ll be choosing “Posts / Content” and “Feed” as our template starters. Allow the project to finish initializing, then click “Start Building”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eHbgO_Zm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/nDoNbpIDBd639_X22Acibf5-AvlLnELukOTfch-G4F0EIhRODmNk_9FrkZtqwaA5b0BZR0u1qg8f88LvlUTrNqtyZwwUK6_JtxBImDFEgcN5VyyxknJB-BEcKKGNbPOyeElF9_mz" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eHbgO_Zm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/nDoNbpIDBd639_X22Acibf5-AvlLnELukOTfch-G4F0EIhRODmNk_9FrkZtqwaA5b0BZR0u1qg8f88LvlUTrNqtyZwwUK6_JtxBImDFEgcN5VyyxknJB-BEcKKGNbPOyeElF9_mz" alt="Choosing starter templates."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The interface is quite intuitive with standard looking design and preview functionalities. You should be seeing an initial screen with a pre-filled feed of some data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aXFqQu4l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/OaiWceLxQizE8B36gUOHVrLoJqperp8AU_HyDsQh1L2N28kU0eoCiet8tz1ZGpOkAOGvVfRSjtBLOrSxTsEz_KUOA_gfV56VKORj11tKKB3WZiKTlkpVJj0Aec5c_L0sDVl9YG9U" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aXFqQu4l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/OaiWceLxQizE8B36gUOHVrLoJqperp8AU_HyDsQh1L2N28kU0eoCiet8tz1ZGpOkAOGvVfRSjtBLOrSxTsEz_KUOA_gfV56VKORj11tKKB3WZiKTlkpVJj0Aec5c_L0sDVl9YG9U" alt="Editor in Draftbit."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s see how far we can get with these default settings by simply replacing the source content with our API.To do that, we need to select the data tab from the right-hand menu, and choose a custom API for “REST”.&lt;/p&gt;

&lt;p&gt;Many tools including low-code tooling are optimized for REST consumption. This is exactly why Hasura has support for creating REST endpoints – for handling the widest range of use-cases possible.&lt;/p&gt;

&lt;p&gt;And the best part is you still get to leverage your GraphQL experience!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--skKrR_sn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/-A-N0nykdzQ73V_QzzfJ-0lgNHsu321V5hQJHiliHbXkdZ96qFzQ3Gnv5CY9_fjhTlurMIRthZzYJPf_yIkSoLXFkeVAlDCNibJjeNRXFXD1k6sW4x6hwN8uThTBCjwXZl90SLkl" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--skKrR_sn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/-A-N0nykdzQ73V_QzzfJ-0lgNHsu321V5hQJHiliHbXkdZ96qFzQ3Gnv5CY9_fjhTlurMIRthZzYJPf_yIkSoLXFkeVAlDCNibJjeNRXFXD1k6sW4x6hwN8uThTBCjwXZl90SLkl" alt="Add Cloud service screen."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will be creating a base resource here that we can use throughout the application. In this case, I will simply name my resource “Hasura” – and for simplicity, I will provide my Hasura Admin Secret to authenticate the requests. You could use a user context for requests later on. If you would like to see user authentication with low-code, let us know!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Side note: You can create Global variables under the settings tab for the project to avoid pasting string values into the configuration here. These are loaded into the environment during runtime.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--79v5WewK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/tSQ7SpAEouYMOfll6tD-O1FmV35CdOcWEG0Q9hPqhZz5wmutRvVxArjzJjJ8TwgEnr-GGd1Mp4dPlaglVcV-mC4HaoMRO4IJZZIY8Qlp4A57Q8PfgN6XmZwvPh4yeuwufv1MeNpv" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--79v5WewK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/tSQ7SpAEouYMOfll6tD-O1FmV35CdOcWEG0Q9hPqhZz5wmutRvVxArjzJjJ8TwgEnr-GGd1Mp4dPlaglVcV-mC4HaoMRO4IJZZIY8Qlp4A57Q8PfgN6XmZwvPh4yeuwufv1MeNpv" alt="Global variable definition screen."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Returning to our REST API dashboard, we need to configure three critical parts of the resource definition.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b_t4-wB_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/x_L8jKJCzV97sfEmVPilGdD2EgTRK4xrzpSTrRbaJ91AuqOHecrGmEZ_iejai6RAvEeXlr0bb6b3hfSIjA67LfiNb7Fh2U4_655VFYCvrnNEg3kLpfub_ajTuzYgOfbXCDDJ0J4z" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b_t4-wB_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/x_L8jKJCzV97sfEmVPilGdD2EgTRK4xrzpSTrRbaJ91AuqOHecrGmEZ_iejai6RAvEeXlr0bb6b3hfSIjA67LfiNb7Fh2U4_655VFYCvrnNEg3kLpfub_ajTuzYgOfbXCDDJ0J4z" alt="REST API definitions."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to provide the base API path for REST requests to Hasura. This is the part of your API before the “v1/graphql” path segment. Then add “api/rest” at the end.&lt;/p&gt;

&lt;p&gt;Secondly, we need to define the Hasura admin secret header (x-hasura-admin-secret) and the value - which I have defined in a global variable called “HAS”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uvOYeuEE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/xmV-pGW-uYKGtB7trpksgFVOVTolzTWlP3zO1P2N_saJgEDUTClCEs-6lwJ7cA_fq-GOkg5LxPPrCC6sbIk3El92egwxxQ6lpjRHR2m6k_erCOTEFA2Gcjuk9iNh_pjbGEXxtIK-" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uvOYeuEE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/xmV-pGW-uYKGtB7trpksgFVOVTolzTWlP3zO1P2N_saJgEDUTClCEs-6lwJ7cA_fq-GOkg5LxPPrCC6sbIk3El92egwxxQ6lpjRHR2m6k_erCOTEFA2Gcjuk9iNh_pjbGEXxtIK-" alt="REST API headers."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have saved the resource, you need to define some endpoints which will be available to the application. But before we do that, we need to now turn to the Hasura console to define what those will be. Open your Hasura project in a new Tab so we can hop back-and-forth as needed.&lt;/p&gt;

&lt;p&gt;The default UI from Draftbit’s feed component included a date, image URL, and title. Let’s fetch the equivalent data from our API. We won’t worry about pagination in this tutorial. I’ll create a named query operation that will request this data. It’s important for the next step that this be a named operation. It’s also a good best practice to name operations in general for tracking down performance leaks in our application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w4MaV_TW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/PSuyw2daobX_dUk5lGCOAyw_rCbfKjwunsqy6503R5OT9VI3Xe3A9U7_keqr_4z1SP93lRUp8H3eAizUTnkOphJvM0O-LI_eOyR-4mALnvXZ95BswmkN9l1ZLcnmIXLe1S1fVnpU" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w4MaV_TW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/PSuyw2daobX_dUk5lGCOAyw_rCbfKjwunsqy6503R5OT9VI3Xe3A9U7_keqr_4z1SP93lRUp8H3eAizUTnkOphJvM0O-LI_eOyR-4mALnvXZ95BswmkN9l1ZLcnmIXLe1S1fVnpU" alt="Hasura API playground."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With our query working, we’ll now navigate to the power feature available to us in Hasura which enables this entire integration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R34VpNJC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/h6O1XlgIX8tAox-TfSyX_XzPcOrPWL6moeiJPH5e1m2OYBo5FaL7oC8Rh6OIiXXqgS1nvv5QjohUtxWfohnNCmxRj96XMx4EyVcrXhlGI5y9eT4Z1iczsQ1ckA4HEkhBwC0C7OP-" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R34VpNJC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/h6O1XlgIX8tAox-TfSyX_XzPcOrPWL6moeiJPH5e1m2OYBo5FaL7oC8Rh6OIiXXqgS1nvv5QjohUtxWfohnNCmxRj96XMx4EyVcrXhlGI5y9eT4Z1iczsQ1ckA4HEkhBwC0C7OP-" alt="Save to REST button."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the REST interface, we can name our operation, choose our protocol, and create our endpoint. See the image below as a reference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uQvi3WtR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/sTPqLOvTXvmLglB7NyqmnwdlnhNq42n7_95pbgsxsIs5LWECWSuXrQxhdFH2K2IuqVSUWw4eF2aBgOOHDgidgEPEINjULsvWZSlay5hOk_9QMJ_dQUwoNceW80bJEBcaZKm0hvzY" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uQvi3WtR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/sTPqLOvTXvmLglB7NyqmnwdlnhNq42n7_95pbgsxsIs5LWECWSuXrQxhdFH2K2IuqVSUWw4eF2aBgOOHDgidgEPEINjULsvWZSlay5hOk_9QMJ_dQUwoNceW80bJEBcaZKm0hvzY" alt="Saving a REST API."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll return to Draftbit and add an endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--glaoJv6p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/fh7RVcRkPPRu9aJn7CMXLo_ee4qss5cpza3YHr43I7LxRKJc2qN1quBl7_VztIMFB2G9gYCv9hBWXW8jLsmF4Blzgb2Z9EEz17CeFEoHDXMUzmu8sqV2ghb97N3RxStUCDmIsta-" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--glaoJv6p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/fh7RVcRkPPRu9aJn7CMXLo_ee4qss5cpza3YHr43I7LxRKJc2qN1quBl7_VztIMFB2G9gYCv9hBWXW8jLsmF4Blzgb2Z9EEz17CeFEoHDXMUzmu8sqV2ghb97N3RxStUCDmIsta-" alt="Creating endpoints in Draftbit: Name."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5q_pxhKJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/NnEWAVJvQFNq2X0EEf60JpfIV5bD9PjqWYbS5GBrRD7Q1tb6OgqVbQulxwYYhttdGuvEkowJi282McdtbzdFTZWx1uZmn2kjLmm_4F8O80KyZd-JoghUUKM5YQ1dra8HOGfsZL1q" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5q_pxhKJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/NnEWAVJvQFNq2X0EEf60JpfIV5bD9PjqWYbS5GBrRD7Q1tb6OgqVbQulxwYYhttdGuvEkowJi282McdtbzdFTZWx1uZmn2kjLmm_4F8O80KyZd-JoghUUKM5YQ1dra8HOGfsZL1q" alt="Creating endpoints in Draftbit: Path."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Skip step three.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O0_y9OsI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/toim5ka3GVEm4UsNLtFeXUPxCAmVuDVyrAef83FDl0A0nM0lEKqtUC_7bzjc4gtz41DbFDWJHhxq_xlLOl_YdLydsBmNo_Za7wS9PsfZMDGji5Vc5D7X_ZKv9J5MDFMCnztN700Z" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O0_y9OsI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/toim5ka3GVEm4UsNLtFeXUPxCAmVuDVyrAef83FDl0A0nM0lEKqtUC_7bzjc4gtz41DbFDWJHhxq_xlLOl_YdLydsBmNo_Za7wS9PsfZMDGji5Vc5D7X_ZKv9J5MDFMCnztN700Z" alt="Creating endpoints in Draftbit: Test."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save that endpoint and click done on the API resource window. We’ve got our first resource from Hasura fetching data!&lt;/p&gt;

&lt;p&gt;Now to iterate over our content, we need to tell our UI to use this new data source. Click on the “Fetch” component in our Layout tree on the left side of the screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dJzNpClp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/MP1_Kp-TfFhl0D7g72zO6vjO2G3Ave2URkB0iXJcVifRm214cWWouMxLkbn_fDNPFeyCm0WhunURQ1-0QdxmjEjF3sVLq6jRPf5crIP6UCkiGguMFjFg4BirLeJy1ph0NrZ8Gslp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dJzNpClp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/MP1_Kp-TfFhl0D7g72zO6vjO2G3Ave2URkB0iXJcVifRm214cWWouMxLkbn_fDNPFeyCm0WhunURQ1-0QdxmjEjF3sVLq6jRPf5crIP6UCkiGguMFjFg4BirLeJy1ph0NrZ8Gslp" alt="Select List component."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the data properties pane on the right side of the window, we’ll need to configure our new data source as the feed provider.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XPUV1jre--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/ua36DI1NV-kibOQh6Dg8YbzA-tV234HHoCQqBklki6r4INDTI3IKE2ZO21kkUFukhqcCfwzTmJVwTXVmNUKyaTFu4_nYJNnkvbbd1MpGvpJSte-iadckKCc83_oeud14C7onU1uy" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XPUV1jre--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/ua36DI1NV-kibOQh6Dg8YbzA-tV234HHoCQqBklki6r4INDTI3IKE2ZO21kkUFukhqcCfwzTmJVwTXVmNUKyaTFu4_nYJNnkvbbd1MpGvpJSte-iadckKCc83_oeud14C7onU1uy" alt="Define data source."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The blank screen means that it is working! Almost…&lt;/p&gt;

&lt;p&gt;Now we need to choose the List component in the Layout tree on the left and ensure we’re still viewing the Data properties pane on the right. For “Data” we need to choose the response from our API that is of an Array type - in our case, it will be the first key in the response called “upload”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WhG50PfC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/Oe9iySO199vyrwYe0yvrDqPjzn0pib6LbNqKlLBdH0RZCvWXum2ilPZLdYQ0pMX3pMzWRFiLaRS3J-Z5gUlWSYP5qe28h6S5eGj3qLX-ZxX2FynKoIhFRv9nkwMaT_97TrxHetwN" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WhG50PfC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/Oe9iySO199vyrwYe0yvrDqPjzn0pib6LbNqKlLBdH0RZCvWXum2ilPZLdYQ0pMX3pMzWRFiLaRS3J-Z5gUlWSYP5qe28h6S5eGj3qLX-ZxX2FynKoIhFRv9nkwMaT_97TrxHetwN" alt="Selecting data to pass in to List."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ll see the list component update now to look as though it’s iterating over “items” - though they are all invisible at the moment. Now we’ll update which pieces of data will be fed into the UI and at which point.&lt;/p&gt;

&lt;p&gt;Select the “image background” component in the Layout tree. From Data, we’ll choose the Source dropdown of the Setup section. We’ll look for “image.url” from the dropdown.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5S1cAeL9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/q3Jjla7q268BsTMfJlaJ-CqxvaNK7LaBVgmLMXIml4HH6WOkE8_bMUfe-gTT66T8uQwNtX5bYd278jCu2j-ja4VWjXzYv6-iqrJJWXvb2GKcQh0mm47erTIJstd8SLVy2V1Nu2jl" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5S1cAeL9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/q3Jjla7q268BsTMfJlaJ-CqxvaNK7LaBVgmLMXIml4HH6WOkE8_bMUfe-gTT66T8uQwNtX5bYd278jCu2j-ja4VWjXzYv6-iqrJJWXvb2GKcQh0mm47erTIJstd8SLVy2V1Nu2jl" alt="Mapping image background."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have live art on the page!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AtM_O63k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/Hszg7I8kheXMnxcd4I9LYF75ENNAHCHqgBX7lB1IDq02l9q3sz6eVlsfERlSSzMFlF4bLE7SGo5Hck6II--3KSM-qPEa3oy4ee0Wuc8VvIr7hhRvjBbCVnx4aiqN9QT1ZYPPfwfY" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AtM_O63k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/Hszg7I8kheXMnxcd4I9LYF75ENNAHCHqgBX7lB1IDq02l9q3sz6eVlsfERlSSzMFlF4bLE7SGo5Hck6II--3KSM-qPEa3oy4ee0Wuc8VvIr7hhRvjBbCVnx4aiqN9QT1ZYPPfwfY" alt="Viewing the current state."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s move on to the date and title.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wr_zg_RO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/skDZb27jUzYyi0YHcmf2m-tNvIFCbr4C3kPpd24m1AsKCdOQ2IcsLaKx_DfpKEETCAbvKDkz8pPmDbwXTRSZpsKTTTanhUt3F4LsndGuo0_s8c2bHeDedbZHd7bWPB6rV1Nl2ps1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wr_zg_RO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/skDZb27jUzYyi0YHcmf2m-tNvIFCbr4C3kPpd24m1AsKCdOQ2IcsLaKx_DfpKEETCAbvKDkz8pPmDbwXTRSZpsKTTTanhUt3F4LsndGuo0_s8c2bHeDedbZHd7bWPB6rV1Nl2ps1" alt="Mapping date and title."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It seems that our date is coming back in an undesired format, let’s create a custom transformer to handle that.&lt;/p&gt;

&lt;p&gt;Click on “custom” in the toolbar across the top. We’ll write a “low-code” transformer that runs on this bit of text.&lt;/p&gt;

&lt;p&gt;Under the “Functions” menu item, click “+ Add” to create a new function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k5K_6F_I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/B847N7GTaueIh6gGLtelMcAd2SGgLNR7ppBg0ua5Ykwytl4RkLRHj3iw4UzLr2DVGCfdMbUmL7CgOd0N7I_UDDgq6WcXIjEel4qCHhTBnWqSar-LTagrf0LYRFeoViojpLbNv91Y" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k5K_6F_I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/B847N7GTaueIh6gGLtelMcAd2SGgLNR7ppBg0ua5Ykwytl4RkLRHj3iw4UzLr2DVGCfdMbUmL7CgOd0N7I_UDDgq6WcXIjEel4qCHhTBnWqSar-LTagrf0LYRFeoViojpLbNv91Y" alt="Create a transformer."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under the information for the function, provide a name and input parameters. For my date, I’ll do a simple Locale String parse. You can run any native Javascript and access some libraries if needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VzuxYseB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/PCQ-cvUkQhzYuFebu2eqYSGo4ABNb8L0XNoiZRgg8tZwObZEUkxloqAVc_aEMZr434bREj1G0pSifHuWDAMvadHV-1W_eBhWjSxA7fpyp14l5b6ptZ4xe4P2NTwFFDbfgj71YpJW" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VzuxYseB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/PCQ-cvUkQhzYuFebu2eqYSGo4ABNb8L0XNoiZRgg8tZwObZEUkxloqAVc_aEMZr434bREj1G0pSifHuWDAMvadHV-1W_eBhWjSxA7fpyp14l5b6ptZ4xe4P2NTwFFDbfgj71YpJW" alt="Code for the transformer."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the new transformer and now we have nicely formatted date and time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W0Msr2zM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/bd3GzycERUsaMkksHA6zdDuM-oEKRueFPZmraEnQxsH6mixJh7i3lisjGE3-76Vhw4Q32YfF8GP_9Ur4J-tB34fj13I60Cv6N6qD6uLTOZeXKsl0ayK-rw8y1txgiC6amT-dNBuD" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W0Msr2zM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/bd3GzycERUsaMkksHA6zdDuM-oEKRueFPZmraEnQxsH6mixJh7i3lisjGE3-76Vhw4Q32YfF8GP_9Ur4J-tB34fj13I60Cv6N6qD6uLTOZeXKsl0ayK-rw8y1txgiC6amT-dNBuD" alt="Previewing the result."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s repeat these steps for our filename.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yST_2AEY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/NW_aY-tSzImh66jzBBTndYW2fUBxuVF50zUTQpLCEYLFactcsFl5OClUxOEQtlV3yZKTQ2A87lXsE9Pf-jckeKfLkIYhXR9Xxk5MIJLTG3gCupyCch7Gd3tIRgcoNFDLk-ySzRoh" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yST_2AEY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/NW_aY-tSzImh66jzBBTndYW2fUBxuVF50zUTQpLCEYLFactcsFl5OClUxOEQtlV3yZKTQ2A87lXsE9Pf-jckeKfLkIYhXR9Xxk5MIJLTG3gCupyCch7Gd3tIRgcoNFDLk-ySzRoh" alt="Adding transformer for title."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code for the second transformer simply replaces some repetitive text in the titles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;niceFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image-from-rawpixel-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-jpeg.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Play around with some of the formatting options for the different components. In my case, I wanted the posters to be full-size. The end effect looks like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M89inV-6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/s2rZzlC2tckWSRbSXG0geIc1TOaOf-vybyqLPvtq12rxl2LV7UbB7-G3rVI-6l3IqY4Gd9PLFKIrP69-ZecPZo7DJRjPTVKXRQaLcBAyRNTBf0ho-P4PfQXivhzJGPcBzDM443Qt" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M89inV-6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/s2rZzlC2tckWSRbSXG0geIc1TOaOf-vybyqLPvtq12rxl2LV7UbB7-G3rVI-6l3IqY4Gd9PLFKIrP69-ZecPZo7DJRjPTVKXRQaLcBAyRNTBf0ho-P4PfQXivhzJGPcBzDM443Qt" alt="Preview updated design."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it’s time to handle individual details. We’ll tell our “Touchable” component to pass the id of this box to the detail view screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BgY5cUCJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/-FJnp2v7GicZNj9cRRBRaNIxxvv24F-mToBKTmMBEX2-TT6brT4uxbF_AH-KHtwSi7jK6MJLhhHH4IOvzHISu7dCi2I8GXOJHz-dBZRHkKDY8VRLSYx92QLDBu8mGh7oP-VE4WUB" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BgY5cUCJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/-FJnp2v7GicZNj9cRRBRaNIxxvv24F-mToBKTmMBEX2-TT6brT4uxbF_AH-KHtwSi7jK6MJLhhHH4IOvzHISu7dCi2I8GXOJHz-dBZRHkKDY8VRLSYx92QLDBu8mGh7oP-VE4WUB" alt="Update date definition for touchable component."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CN-W677F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/WZmWzL5GiVeWNb6j3hYTnIg6yzoTitFc8oSLNL785qq7BKDPJJUYros60dDKBoX8QxDoEGgmxkXRRmKYoeDB_tmKXjlXgkcdvYtdG0hIHZP8eqKxtyai_CRcB6ORqSTSzPLlKhHn" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CN-W677F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/WZmWzL5GiVeWNb6j3hYTnIg6yzoTitFc8oSLNL785qq7BKDPJJUYros60dDKBoX8QxDoEGgmxkXRRmKYoeDB_tmKXjlXgkcdvYtdG0hIHZP8eqKxtyai_CRcB6ORqSTSzPLlKhHn" alt="Update fetch component for top-level."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that configured, we can now navigate to the Article View screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7j0A73_D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/aH0mhH4cG4gGWe2STs14SnyIIH1J59z1i5m8zkfDzum2cldGfXGWVrWQ7HWKJZDDTrj5EHydgvAoE6RrNDK9aSHnBfWbCTLcTbsvE9kX9GtkpumeWKPn5l6Ymlo88zN1rn2Ifi_B" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7j0A73_D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/aH0mhH4cG4gGWe2STs14SnyIIH1J59z1i5m8zkfDzum2cldGfXGWVrWQ7HWKJZDDTrj5EHydgvAoE6RrNDK9aSHnBfWbCTLcTbsvE9kX9GtkpumeWKPn5l6Ymlo88zN1rn2Ifi_B" alt="Update the article view details."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking at the top-level fetch for this view, under the Data panel, we can see that it passes “articleId” as a parameter to the URL structure of the request.&lt;/p&gt;

&lt;p&gt;What this means is we will pass a variable with the value of the ID of an individual upload to a specified REST endpoint. Let’s create that endpoint in Draftbit now.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note, we can provide a test variable that will be run in the case of a non-existing URL parameter, as is the case in development mode.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_V2LNqwT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/Y6MKPAv0kLBNSa0SXy31ZmW_pANe-PcReBjIOExIEog_fF_ccAHnWVEq_k0ERZjDD-iwfm1YCibnFL0JAVPiCb1zmShlUj2g8b5kJgGp4T2xLcaduR1eHdi1WiYxNu-PAm4o9pZ5" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_V2LNqwT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/Y6MKPAv0kLBNSa0SXy31ZmW_pANe-PcReBjIOExIEog_fF_ccAHnWVEq_k0ERZjDD-iwfm1YCibnFL0JAVPiCb1zmShlUj2g8b5kJgGp4T2xLcaduR1eHdi1WiYxNu-PAm4o9pZ5" alt="Defining default case."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we test that now, however, it will fail, since we have not defined the endpoint in Hasura. Let’s do that now. First, we test the query we’d like to persist to an endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CVZ7bACz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/E7BZtWzFkZar-_eUM2UjTxRN7pog0zN2eCI3IRofytF_1DRAs_8snu-D-ORETNdwbhcDs4F7oMzkAazGzKVdaK-7O94NsKu6civ1_W43qM_IahEPUVZVsT36lJR97P_Rdxqmge5U" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CVZ7bACz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/E7BZtWzFkZar-_eUM2UjTxRN7pog0zN2eCI3IRofytF_1DRAs_8snu-D-ORETNdwbhcDs4F7oMzkAazGzKVdaK-7O94NsKu6civ1_W43qM_IahEPUVZVsT36lJR97P_Rdxqmge5U" alt="Creating single endpoint in Hasura."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Second, we need to save that as we did with our listing API call. Using the syntax &lt;em&gt;:variable&lt;/em&gt; lets us define where we’ll accept inputs in the REST API parameters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5pIo7jNW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/LJAPaotttnMv84PNtOncKyb2Rzqm8eDO4dgQandbSvThX-MdSTvxvdyzuQFaa8VFXRicHiZFTbWSOsvGEbCnV_-xf92ku8tPTWQLI5lBv_AXHJ__ZWDoij2VRV8PP0Zlp18gE9bx" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5pIo7jNW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/LJAPaotttnMv84PNtOncKyb2Rzqm8eDO4dgQandbSvThX-MdSTvxvdyzuQFaa8VFXRicHiZFTbWSOsvGEbCnV_-xf92ku8tPTWQLI5lBv_AXHJ__ZWDoij2VRV8PP0Zlp18gE9bx" alt="Save the API endpoint."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back to Drafitbit, we can save that endpoint now that we have a successful Test case run.&lt;/p&gt;

&lt;p&gt;Returning to our detailed view, we can select our top-level Fetch in the Layout browser, choose our saved Hasura service under the data tab, select the Get Single endpoint, and choose the articleId we passed in from our list-view (also defined in our test case).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K7qm-Egj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/vTRFKomGkqUwAmlzcVPn1cZIRxmjp-zDX_DLz4objBRDwyL-tHSKJSraTb6EudJx7TEmC-FZhYu4PTXqwTY72Y5jvGA2xPNMRm19Rfk60Dd3-pK_fxVeN4gZUJXbRBbhlULW-66A" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K7qm-Egj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/vTRFKomGkqUwAmlzcVPn1cZIRxmjp-zDX_DLz4objBRDwyL-tHSKJSraTb6EudJx7TEmC-FZhYu4PTXqwTY72Y5jvGA2xPNMRm19Rfk60Dd3-pK_fxVeN4gZUJXbRBbhlULW-66A" alt="Testing our result."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see by the partial UI loading, that we are at least receiving some form of data. Let’s connect our pieces of data as we did in the previous screen.&lt;/p&gt;

&lt;p&gt;With a little bit of cleanup on the components we need to display our data, re-applying some text transforms, and adjusting the styles, we end up with a rough shape of the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y4vmENvY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/z2V3pfnCx403Qua9h0YD41mQx50puCWwHDWyqz-vIo8kx3UCyw9K5_lHvjqMceCem1ZvbUR71X_vAm6fP8WK-E2O3zrzWhUSgC6KmfXOIxNN6oqESCpQ6ZZzKRJLAwchcQAuC7ft" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y4vmENvY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/z2V3pfnCx403Qua9h0YD41mQx50puCWwHDWyqz-vIo8kx3UCyw9K5_lHvjqMceCem1ZvbUR71X_vAm6fP8WK-E2O3zrzWhUSgC6KmfXOIxNN6oqESCpQ6ZZzKRJLAwchcQAuC7ft" alt="Mapping the content to the layout."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, we’ve done nothing with our tags! Let’s add a way to loop over tag elements. Based on what we’ve seen so far, we can assume we’ll need some combination of another “View” box - typically as a container primitive, some form of a “List” component to iterate over an array of data, and the final form of our tag element.&lt;/p&gt;

&lt;p&gt;Implementation details will vary, but with these primitives we were able to loop over our tags, and output them on the screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R_FzgrKo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/MtumDuQeDq9hhYCor4OP5oKE1v0ILmsLwfm0VoHlLVBWnSGR9Vp2scLI1w5FlHndaiHvgAOL8MsWCetJyxpAat3u0OkB4LpWaci35k2tHTVtQpM6pdibYV2EQP_NdZrGrs34YIv4" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R_FzgrKo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/MtumDuQeDq9hhYCor4OP5oKE1v0ILmsLwfm0VoHlLVBWnSGR9Vp2scLI1w5FlHndaiHvgAOL8MsWCetJyxpAat3u0OkB4LpWaci35k2tHTVtQpM6pdibYV2EQP_NdZrGrs34YIv4" alt="Adding tags to screen."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But we’ll notice that’s a lot of tags! And tags like “meat” don’t seem relevant to our pictures of fruit! Fortunately, our auto-tagging API includes the confidence level of these tags. Let’s adjust the response data from Hasura.&lt;/p&gt;

&lt;p&gt;Here, we are limiting our tags to only those with a confidence greater than 50.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fMikQayR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/d3HWmO0GxhofIOeJErQDB3Yo3q1mIq7xmpB9DQWv3nw8spckLFifG8f7EnSTAqo1PE9g7mMfBm_UOx8I1rsMnZxBYEaImm01II3MJll4msf3z-xTKxSe7xDVpLBsf8tgrHS-dJCh" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fMikQayR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/d3HWmO0GxhofIOeJErQDB3Yo3q1mIq7xmpB9DQWv3nw8spckLFifG8f7EnSTAqo1PE9g7mMfBm_UOx8I1rsMnZxBYEaImm01II3MJll4msf3z-xTKxSe7xDVpLBsf8tgrHS-dJCh" alt="Clean up tags display."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we save that data, we can fetch again from Draftbit to see what we get.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k7qc-gSr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/kvm8imYw9z7SIkowKggotkiKBm-8k1-ZehNE3EvB9dE_cvBcf98IkbMtBTnfpi3pU1KbhA1NWDLjEWsclrTvd_OWib-YKmaJTDfZUHsQGUp3qjZ9mSZvn6UQ-sZ1EopvyMKrGYjU" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k7qc-gSr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/kvm8imYw9z7SIkowKggotkiKBm-8k1-ZehNE3EvB9dE_cvBcf98IkbMtBTnfpi3pU1KbhA1NWDLjEWsclrTvd_OWib-YKmaJTDfZUHsQGUp3qjZ9mSZvn6UQ-sZ1EopvyMKrGYjU" alt="Preview result."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s a considerable improvement! It’s time to preview this app in context, and play with the different pages.&lt;/p&gt;

&lt;p&gt;Pressing the play button gives us a scannable QR code to preview the app directly on our phones, you’ll need to download the Expo app, first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K2tf0fgQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/DQ2-KUpRkBkmt1C8aj2J4kqwXbfA3sOQBnpTotlZSsZJHh4jl8vjbJujvndphBknYvRSHLY8gSO62X-TIH6HcsBGAMXur8R4qB8y8iNfcVbTBX9kP5BvwZbScDeayUpZnSY6LbZu" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K2tf0fgQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/DQ2-KUpRkBkmt1C8aj2J4kqwXbfA3sOQBnpTotlZSsZJHh4jl8vjbJujvndphBknYvRSHLY8gSO62X-TIH6HcsBGAMXur8R4qB8y8iNfcVbTBX9kP5BvwZbScDeayUpZnSY6LbZu" alt="Load preview button for app."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/m12phMatKZk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;With just a few functions, some powerful features from both Draftbit and Hasura, we’ve been able to create a native app from our API with very low coding, indeed.&lt;/p&gt;

&lt;p&gt;Publishing this app is out of scope for this tutorial, but you can follow the guides at Draftbit and use the built-in UI to deliver your app to users around the world.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--96EdH4q6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/dy83W95KviA28O7k5-IsT_AtCVpmmyudvGdYccPDuDb2CBtxLRp7Pn353Utcr9BGGo6BcEWRaqW7GUryleXJfUhUClK8cu5EDH8rUrGEdDg9pD4jlTQ4XFCaBhkBtEUrj8F_vN72" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--96EdH4q6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/dy83W95KviA28O7k5-IsT_AtCVpmmyudvGdYccPDuDb2CBtxLRp7Pn353Utcr9BGGo6BcEWRaqW7GUryleXJfUhUClK8cu5EDH8rUrGEdDg9pD4jlTQ4XFCaBhkBtEUrj8F_vN72" alt="UI to publish app."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Build something with &lt;a href="https://hasura.info/dev_jesse"&gt;your API&lt;/a&gt; today and let us know what you made!  &lt;/p&gt;

</description>
      <category>mobile</category>
    </item>
    <item>
      <title>Hasura Fit: Setting up Hasura</title>
      <dc:creator>Jesse Martin</dc:creator>
      <pubDate>Thu, 13 Aug 2020 17:16:08 +0000</pubDate>
      <link>https://dev.to/hygraph/hasura-fit-setting-up-hasura-3pc4</link>
      <guid>https://dev.to/hygraph/hasura-fit-setting-up-hasura-3pc4</guid>
      <description>&lt;p&gt;In this step of our multi-part tutorial, we are going to configure Hasura as our application’s back-end. Hasura is an open-source, GraphQL flavored “back-end-as-a-service” which plays very well with GraphCMS given they both share the GraphQL underpinnings. The back-end-as-a-service simply means that we’ll get a thin UI sitting on top of our database and be offered some user-friendly features, such as taking advantage of Postgres’ hook system but with the convenience of a UI and some additional USPs from the BaaS provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Hasura?
&lt;/h2&gt;

&lt;p&gt;Apart from the above-mentioned reason that Hasura has native support for GraphQL, Hasura also released a new feature called ‘remote joins’ which allows us to essentially federate our schemas without having to annotate the schemas. If you’re unfamiliar with the federation concept, it basically means that each “section” or “group of concerns” of our application would be split into multiple schemas. These schemas could be developed in isolation of each other since they are often maintained by different teams with a different set of concerns and stakeholders.&lt;/p&gt;

&lt;p&gt;Typically, you’d need to provide some kind “decoration”, in GraphQL this is done with directives, to tell one schema when to look at another schema for the answers. In traditional database design, this would be identifying a foreign key when one table needs to look at another table.&lt;/p&gt;

&lt;p&gt;In Hasura, we have the convenience of simply providing our API URL, and through the power of GraphQL’s introspection (the ability to understand everything an API makes available to us), the Hasura tooling lets us use a very convenient drop-down to identify which field needs to be connected to our schema. This will be covered a little more later.&lt;/p&gt;

&lt;p&gt;Lastly, the reason we chose Hasura is that it has built-in support for authentication and user permissions, which will be important to this application since we have multiple users creating their own, private content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Variables in Heroku
&lt;/h2&gt;

&lt;p&gt;To begin, we are going to use the newly released Hasura cloud to quickly set-up a new instance of Hasura. You could just as easily use their deployable solution on Heroku (our database will be hosted there, anyways) or any other server stack supported by them, but we’ll focus on the cloud version to keep things simple.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.hasura.io/"&gt;Create an account with Hasura Cloud.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W_yjc-lG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/5k4c7prRmOAGnGqGVPB6" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W_yjc-lG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/5k4c7prRmOAGnGqGVPB6" alt="hasura-onboard-1.png" width="800" height="880"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create the database on Heroku&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--068xpvrm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/JaqDH8kQTezS67SGBTTa" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--068xpvrm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/JaqDH8kQTezS67SGBTTa" alt="hasura-choose-heroku.png" width="800" height="878"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ll need to authenticate (or create an account with Heroku first) after which the Hasura Cloud interface will do automated provisioning for you.&lt;/p&gt;

&lt;p&gt;Once you create the project, the app will go through a number of installation steps (all automated) and then prompt you to launch the console (main application) of Hasura. When you launch the console, it will open in a new tab, return to the Hasura Cloud UI to add our environment variables.&lt;/p&gt;

&lt;p&gt;Note various values available to you here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LahC9ImB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/Nov7TsCUR12mb3eiztAc" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LahC9ImB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/Nov7TsCUR12mb3eiztAc" alt="hasura-project-success.png" width="800" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will need to create a few variables for this project:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GRAPHCMS_FULL_ACCESS_TOKEN&lt;/code&gt;&lt;br&gt;
This is our access token from GraphCMS which we defined in our GraphCMS API. Note, you’ll need to use the format &lt;code&gt;Bearer &amp;lt;TOKEN_VALUE&amp;gt;&lt;/code&gt; since we use the full string as Hasura’s env values don’t allow us to use string interpolation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HASURA_GRAPHQL_ADMIN_SECRET&lt;/code&gt;&lt;br&gt;
For protecting our console (if we aren’t using Hasura Cloud) and also for protecting synchronizing function from Auth0.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HASURA_GRAPHQL_JWT_SECRET&lt;/code&gt;&lt;br&gt;
Our application will use tokens from Auth0 to authorize our requests to Hasura. We need to configure Hasura to recognize Auth0’s JWT format. To do this, provide the following value as the environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jwk_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;YOUR_AUTH0_DOMAIN&amp;gt;/.well-known/jwks.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"claims_format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replacing &lt;code&gt;&amp;lt;YOUR_AUTH0_DOMAIN&amp;gt;&lt;/code&gt; with your actual Auth0 application domain.&lt;br&gt;
&lt;code&gt;HASURA_GRAPHQL_UNAUTHORIZED_ROLE&lt;/code&gt;&lt;br&gt;
Define this as “public” - which means that users who attempt to access our API without authentication will be assigned the “public” role, which we can assign specific privileges for in our database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Content Model
&lt;/h2&gt;

&lt;p&gt;The majority of our product or editorial content was modeled in GraphCMS. We’ll simply use two models (or tables) in Hasura. Our user table, which is populated from Auth0 plus an additional array relationship called sessions to a table also called sessions.&lt;/p&gt;

&lt;p&gt;An array relation simply means that we are defining a one-to-many or many-to-many relation. If we were creating a many-to-one or a one-to-one this would be an object relation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Users Table
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BV80Wpto--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/vM5BCO46Q7Wx3gZw0I7T" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BV80Wpto--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/vM5BCO46Q7Wx3gZw0I7T" alt="hasura-relationships.png" width="800" height="254"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8br8Rcll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/WwcccGqYRCrUtmesGUxO" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8br8Rcll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/WwcccGqYRCrUtmesGUxO" alt="hasura-users-table.png" width="612" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sessions Table
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KzlB74H5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/gXwFT15VTTSb137w1OPg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KzlB74H5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/gXwFT15VTTSb137w1OPg" alt="hasura-sessions-table.png" width="734" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Remote Schema
&lt;/h2&gt;

&lt;p&gt;You’ll note in the session table above, we defined a workout “slug” - this will serve as our foreign key for GraphCMS when we define a remote schema relationship. Defining and federating a remote schema couldn’t be easier in Hasura. To enable our Remote schema, go to the “remote schema” tab at the top of the window, and add in the values, your settings should be similar to the attached graphic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RVJlRNVn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/PRLtFIsETNKrD9i0sPvj" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RVJlRNVn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/PRLtFIsETNKrD9i0sPvj" alt="hasura-remote-schema.png" width="800" height="917"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that for the Authorization header, we are using a value from our environment, our &lt;code&gt;GRAPHCMS_FULL_ACCESS_TOKEN&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the Relationship
&lt;/h2&gt;

&lt;p&gt;Return to the relationship tab of the &lt;code&gt;sessions&lt;/code&gt; table. At the bottom of the tab window, there’s a section called “Remote Relationships.”&lt;/p&gt;

&lt;p&gt;Name the field “workout” and choose the Remote Schema of GraphCMS (or whatever you labeled the remote schema in the previous step).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mHTS-wdw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/oLzNzlKtS365oIUvLjzg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mHTS-wdw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/oLzNzlKtS365oIUvLjzg" alt="hasura-remote-join.png" width="733" height="1015"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The trick here is that we are passing the value to the &lt;code&gt;slug&lt;/code&gt; input argument of our &lt;code&gt;where&lt;/code&gt; input type. The &lt;code&gt;slug&lt;/code&gt; value is coming from the &lt;code&gt;slug&lt;/code&gt; column of our session.&lt;/p&gt;

&lt;p&gt;That’s it.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DQfItG7m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/2e2en6wcTty9xVCNHkcm" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DQfItG7m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/2e2en6wcTty9xVCNHkcm" alt="hasura-fit-one-api.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Web-hook
&lt;/h2&gt;

&lt;p&gt;We use a webhook in the project to perform a sort of “pre-calculated aggregation” of the popularity of our workouts. This allows us to sort by popularity without needing to touch our user database if we were to distribute our GraphCMS API to another location that was un-aware of our Hasura project. In Hasura, these are called “Events”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4iGLwhSL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/Y7Ch7f6vQX6CyXDYlEtw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4iGLwhSL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/Y7Ch7f6vQX6CyXDYlEtw" alt="hasura-fit-webhook.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We define the trigger to occur when the table &lt;code&gt;sessions&lt;/code&gt; receive a new &lt;code&gt;insert&lt;/code&gt; and then we send a payload to the Web-hook URL defined.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gotchas
&lt;/h2&gt;

&lt;p&gt;There are some limitations to the remote schema joins for now. One of which is that you cannot use secure roles in the fields of a remote join. That is, you can’t extend the access controls from Hasura to the fields of GraphCMS. Hasura is working to enable this as is GraphCMS.&lt;/p&gt;

&lt;p&gt;There are also some notable gotchas:&lt;/p&gt;

&lt;h3&gt;
  
  
  No exposed ID Type
&lt;/h3&gt;

&lt;p&gt;Their ID scalar is not exposed by Hasura so we wouldn’t be able to use the direct ID of the workout entry from GraphCMS since meshing them would cause a type incompatibility error (even though both read as a text string.) That means we needed to create a slug entry in our GraphCMS model to support matching a foreign key from Hasura.&lt;/p&gt;

&lt;h3&gt;
  
  
  Required Fields
&lt;/h3&gt;

&lt;p&gt;Because the input argument type &lt;code&gt;where&lt;/code&gt; is required by GraphCMS for filtering by workout, we have to pass this value in as an empty argument when querying content, even though it is dynamically inserted at run-time. It’s less elegant than it could be, but a small price to pay for the amazing flexibility of adding a meshed-in third-party API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;That's it! You've made it to the end. Now you have all the pieces you need to create elegant, composed, distributed, and iterative APIs and services. Tweet out your success or check out GraphCMS for even more great resources.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Hasura Fit: Setting up Vercel</title>
      <dc:creator>Jesse Martin</dc:creator>
      <pubDate>Thu, 13 Aug 2020 17:15:57 +0000</pubDate>
      <link>https://dev.to/hygraph/hasura-fit-setting-up-vercel-h36</link>
      <guid>https://dev.to/hygraph/hasura-fit-setting-up-vercel-h36</guid>
      <description>&lt;h2&gt;
  
  
  Why Vercel?
&lt;/h2&gt;

&lt;p&gt;Vercel is an incredibly straight-forward hosting environment for our code. It provides a number of runtimes so that the most popular code can simply be uploaded to a Vercel project and it will run. Since our project is Javascript based, it will run just fine.&lt;/p&gt;

&lt;p&gt;We’ll be using two separate Vercel projects for our tutorial primarily for educational purposes and as a separation of concerns.&lt;/p&gt;

&lt;p&gt;First, we’ll be deploying our NextJs framework code to Vercel to run our primary web application.&lt;/p&gt;

&lt;p&gt;Second, we’ll be running a very simple server function that acts as webhook for our Hasura project to update our content in GraphCMS later-on.&lt;/p&gt;

&lt;p&gt;To begin, create an account with &lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Variables in Vercel
&lt;/h2&gt;

&lt;p&gt;Assuming you have forked and cloned both the Hasura-Fit and the Hasura-Fit-Hooks repos locally, you’ll now need to add them to your own version control account (ie. Github). If you only cloned and didn’t fork, you can &lt;a href="https://docs.github.com/en/github/using-git/changing-a-remotes-url"&gt;see how to change the URL here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have your code, pushed to version control, you can import those projects into Vercel from your account console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GjLUhkBY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/K4jFMgZwQ2Ofd8eS3LdS" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GjLUhkBY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/K4jFMgZwQ2Ofd8eS3LdS" alt="vercel-import-project.png" width="800" height="237"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3fXS9AWC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/OZiEkB8ATSeoRFURW1hE" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3fXS9AWC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/OZiEkB8ATSeoRFURW1hE" alt="vercel-choose-repo.png" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you have imported your project, you will have the ability to define your variables under the Settings / Environment Variables page. It’s possible your project might error on the first build since the environment variables are not yet defined. Don’t worry about that yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hzp-0zFT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/tz1DlZitQRe9wQ9xfzcB" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hzp-0zFT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/tz1DlZitQRe9wQ9xfzcB" alt="vercel-environment-variables.png" width="800" height="751"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;VERCEL_URL&lt;/code&gt; in this case is a system default variable that will be dynamically populated by the Vercel environment with a special URL that is either referring to a branch from your repo or the master branch.&lt;/p&gt;

&lt;p&gt;The remainder of the variables can be copied from the NextJs variables, with production urls being used where needed.&lt;/p&gt;

&lt;p&gt;Next: &lt;a href="https://dev.to/blog/hasura-fit-setting-up-hasura"&gt;Set up Hasura&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Hasura Fit: Setting up Serverless Functions</title>
      <dc:creator>Jesse Martin</dc:creator>
      <pubDate>Thu, 13 Aug 2020 17:15:44 +0000</pubDate>
      <link>https://dev.to/hygraph/hasura-fit-setting-up-serverless-functions-332m</link>
      <guid>https://dev.to/hygraph/hasura-fit-setting-up-serverless-functions-332m</guid>
      <description>&lt;h2&gt;
  
  
  Why Vercel Serverless Functions?
&lt;/h2&gt;

&lt;p&gt;There are many serverless providers out there. Many of them are incredibly easy to set-up. Because we are hosting our NextJs web app on Vercel (the same company that made the framework) - it makes sense to stay in the same ecosystem.&lt;/p&gt;

&lt;p&gt;It’s also incredibly easy to set up. If you inspect the code involved, it really is a matter of defining the function you want to handle a serverless request, use the CLI to publish and you have a “micro-service” ready to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Variables
&lt;/h2&gt;

&lt;p&gt;The Vercel CLI for local testing does not support the &lt;code&gt;.env.[environemnt]&lt;/code&gt; pattern defined in our Nextjs guide, so we’ll simply use a &lt;code&gt;.env&lt;/code&gt; file, being sure to avoid checking this file into version control. Begin by copying the template with &lt;code&gt;cp ./.env.template ./.env&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We’ll add:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GRAPHCMS_MUTATION_TOKEN&lt;/code&gt;&lt;br&gt;
&lt;code&gt;GRAPHCMS_API&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Both of which we can find in the GraphCMS API Settings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SDkbLgHr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/r5ETiPJEQpHXyfyFx3AG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SDkbLgHr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/r5ETiPJEQpHXyfyFx3AG" alt="graphcms-api-access.png" width="490" height="348"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cz-dKohL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/MOHGocbR8eRlnGveYYQZ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cz-dKohL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/MOHGocbR8eRlnGveYYQZ" alt="graphcms-api-endpoint.png" width="776" height="225"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nb9g4IgZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/I6VQkxxAT3eEOcUTV7GD" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nb9g4IgZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/I6VQkxxAT3eEOcUTV7GD" alt="graphcms-api-token.png" width="732" height="157"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Server Functions
&lt;/h2&gt;

&lt;p&gt;Our code for the webhook handler is rather succinct.&lt;/p&gt;

&lt;p&gt;Essentially, we perform three actions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We inspect incoming requests to ensure that we have a successful payload that includes the slug of a workout performed by a user from Hasura.&lt;/li&gt;
&lt;li&gt;We fetch the popularity field of that workout from our GraphCMS API directly.&lt;/li&gt;
&lt;li&gt;We mutate the GraphCMS workout model by incrementing the popularity field by one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M89bgSmU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/3MfR6LV9RR6fBR9x4o3U" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M89bgSmU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/3MfR6LV9RR6fBR9x4o3U" alt="hasura-webhook.png" width="784" height="733"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, all the code is provided in the repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GRAPHCMS_API&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GRAPHCMS_MUTATION_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;new&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;workout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;popularity&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/master&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`query GetWorkout($slug: String) {
            workout(where: {
              slug: $slug
            }) {
                title
                id
                slug
                popularity
            }
          }`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mutationResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/master&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`mutation IncWorkout($slug: String!, $popularity: Int) {
            updateWorkout(where: {slug: $slug}, data: {popularity: $popularity}) {
              id
              popularity
            }
            publishWorkout(where: {slug: $slug}, to: PUBLISHED) {
              id
            }
          }
          `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;popularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;popularity&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;popularity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;updateWorkout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mutationResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`ID &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; popularity is now &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;popularity&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next: &lt;a href="https://dev.to/blog/hasura-fit-setting-up-vercel"&gt;Set up Vercel&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Hasura Fit: Setting up NextJs</title>
      <dc:creator>Jesse Martin</dc:creator>
      <pubDate>Thu, 13 Aug 2020 17:15:35 +0000</pubDate>
      <link>https://dev.to/hygraph/hasura-fit-setting-up-nextjs-4fi3</link>
      <guid>https://dev.to/hygraph/hasura-fit-setting-up-nextjs-4fi3</guid>
      <description>&lt;h2&gt;
  
  
  Why NextJS?
&lt;/h2&gt;

&lt;p&gt;NextJs is a powerful framework that lets us combine the best of server-side execution and static site generation. Out of the box we get server-rendered content, static resource compilation and API routes ensuring a protected execution environment.&lt;/p&gt;

&lt;p&gt;Why that’s important for us is that we are able to handle our user authentication and Auth0 code in server-side routes where our Auth0 Client secret is protected from our app’s users, while still getting access to really fast pages loading content that doesn’t need to check the server first.&lt;/p&gt;

&lt;p&gt;All of the code has been written already in this example repo, but we’ll look at the different parts of the framework to get a better understanding of how it works.&lt;/p&gt;

&lt;p&gt;To begin with, you’ll need to provide your own application variables. We’ll be assuming that our code is deployed to Vercel as that is what this tutorial recommends, but the choice is up to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Variables
&lt;/h2&gt;

&lt;p&gt;First, start by creating a copy of the &lt;code&gt;.env.local.template&lt;/code&gt; file and rename it to &lt;code&gt;.env.local&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cp ./env.local.template .env.local&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Returning to the Auth0 console and navigate to your application, you’ll need:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AUTH0_CLIENT_SECRET&lt;/code&gt;&lt;br&gt;
&lt;code&gt;AUTH0_CLIENT_ID&lt;/code&gt;&lt;br&gt;
&lt;code&gt;AUTH0_DOMAIN&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Additionally, you’ll need to &lt;a href="https://onlinehashtools.com/generate-random-md5-hash?count=1&amp;amp;format=*&amp;amp;binary-base=false&amp;amp;octal-base=false&amp;amp;decimal-base=false&amp;amp;hex-base=true&amp;amp;custom-base=false&amp;amp;base=36&amp;amp;separator=%5Cn&amp;amp;lowercase=true&amp;amp;uppercase=false&amp;amp;randomcase=false"&gt;define a random secret&lt;/a&gt; for the &lt;code&gt;SESSION_COOKIE_SECRET&lt;/code&gt; as well as the &lt;code&gt;SESSION_COOKIE_LIFETIME&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Lastly, you’ll need to define &lt;code&gt;HASURA_ADMIN_SECRET&lt;/code&gt; which will be used to protect your login if you choose not to use Hasura Cloud.&lt;/p&gt;

&lt;p&gt;We define all of these values in a file called “.env.local” because we will define all of the values again in our Vercel admin panel for our project. NextJs supports naming files &lt;code&gt;.env.[environment]&lt;/code&gt; as a pattern for mapping specific environment variables to those Node environments. It looks for these files out of the box. The .local prefix is a reminder for us to not push this file into source control (ie. Github).&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating the Required Pages in Next
&lt;/h2&gt;

&lt;p&gt;All of the needed pages for the demo exist already in the project repository, but for an explanation, we’ll describe them here.&lt;/p&gt;
&lt;h3&gt;
  
  
  lib/config
&lt;/h3&gt;

&lt;p&gt;Our config file declares various export values for use throughout our application. Because we are using sever side and client side authentication, we need to define different variables for different contexts. Because we are hosting in Vercel, we need to handle some work-around behaviour to handle our preview URLs (branch deploys) vs our aliased production URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * Settings exposed to the server.
   */&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;production&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;production&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preview&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_LIVE_VERCEL_APP_ALIAS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`https://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VERCEL_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;AUTH0_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;AUTH0_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;AUTH0_SCOPE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openid profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;AUTH0_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/callback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;POST_LOGOUT_REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;SESSION_COOKIE_SECRET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SESSION_COOKIE_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;SESSION_COOKIE_LIFETIME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SESSION_COOKIE_LIFETIME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * Settings exposed to the client.
   */&lt;/span&gt;
  &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;AUTH0_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;AUTH0_SCOPE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openid profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;AUTH0_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/callback`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;POST_LOGOUT_REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The URL helper function simply allows us to define which base-url will be used in the various Node environments such as preview, development and local.&lt;/p&gt;

&lt;p&gt;Note how we don’t expose our &lt;code&gt;AUTH0_CLIENT_SECRET&lt;/code&gt; in our client context, but only in the server? This is part of what makes a framework like NextJS so powerful is the ability to mix contexts like this.&lt;/p&gt;

&lt;h3&gt;
  
  
  lib/auth0
&lt;/h3&gt;

&lt;p&gt;The auth0 file in our lib creates a new configuration object for the Auth0 SDK. It creates all the required configurations and methods we’ll need to work directly with Auth0, and then exports it for use in other files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initAuth0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@auth0/nextjs-auth0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth0Instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;initAuth0&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_SCOPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_HEROKU_APP_URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;postLogoutRedirectUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;POST_LOGOUT_REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cookieSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SESSION_COOKIE_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cookieLifetime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SESSION_COOKIE_LIFETIME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;storeIdToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;storeRefreshToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;storeAccessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;auth0Instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  lib/user
&lt;/h3&gt;

&lt;p&gt;This is highly specific React code and will be covered in the NextJs tutorial. The basic gist of this code is that it implements a React hook to check our &lt;code&gt;/api/me&lt;/code&gt; route if the user is still logged-in, if they are, it returns our user data, if not, it redirects a user to a login path and additionally includes some helper state to determine if the browser is still checking for our logged-in status.&lt;/p&gt;

&lt;h3&gt;
  
  
  pages/api/login
&lt;/h3&gt;

&lt;p&gt;Our login function takes a request to the route &lt;code&gt;/api/login&lt;/code&gt; from our website, which then in turn uses our Auth0 lib to send all the client ID and secret information we need to Auth0 along with a secondary parameter called “redirectTo” which is the url that should be called from Auth0’s servers after we succeed or fail to log-in to the app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../lib/auth0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;redirectTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;referer&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api/callback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  pages/api/callback
&lt;/h3&gt;

&lt;p&gt;Our callback URL handles a request coming FROM Auth0, more specifically, this is a callback that is invoked when a user tries to signup. See the diagram below.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_8CwY940--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/PH2Rln5HRnujvyP9oA8k" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_8CwY940--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/PH2Rln5HRnujvyP9oA8k" alt="hasura-fit-callbacks.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our application tries to login, which sends us to Auth0, Auth0 uses a form from their server (in our case) which then sends us back to our callback url that we defined in our login request above. If we had a successful login, that request has tokens corresponding the scopes we requested and whether or not we want to use refresher tokens. Because there can be any number of tokens depending on our settings, the Auth0 library is able to simply take the request coming back from the Auth0 servers, identify which parts are important for our authentication, and save them where they need to go in our browser.&lt;/p&gt;

&lt;p&gt;That code looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../lib/auth0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;redirectTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  pages/api/me
&lt;/h3&gt;

&lt;p&gt;This route is perhaps the most opaque in terms of understanding the inner workings of the code, but the concept is straight forward. The SDK checks to see if the user still has a valid token to access data from Auth0, if not, it uses the refresh token to generate a new one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../lib/auth0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;me&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  pages/api/logout
&lt;/h3&gt;

&lt;p&gt;In a near mirror of our callback code, the Auth0 SDK takes a the request to logout and simply removes all the cookies for logging in and ensures no protected content is accessible anymore.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../lib/auth0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleLogout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We won’t bother outlining the remaining files in NextJs here as they pertain to the presentational layer and edge of out of the scope of this tutorial.&lt;/p&gt;

&lt;p&gt;Generally speaking, the actual rendered pages will include a function that follows a similar pattern as the following code snippet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getServerSideProps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Here you can check authentication status directly before rendering the page,&lt;/span&gt;
  &lt;span class="c1"&gt;// however the page would be a serverless function, which is more expensive and&lt;/span&gt;
  &lt;span class="c1"&gt;// slower than a static page with client side authentication&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://hasura-fit.herokuapp.com/v1/graphql&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`{
            sessions {
              created_at
              workout(stage: PUBLISHED, where: {}) {
                title
              }
            }
          }
          `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The specific function here &lt;code&gt;getServerSideProps&lt;/code&gt; allows us to guarantee a server-side execution which means privileged access. That’s why we are able to use our authorization header here in the same file as our rendered page because we know this part of the code only runs on the server. This is one of the hidden powers of NextJs.&lt;/p&gt;

&lt;p&gt;In the above code, our server will look for a valid session rookie, if it doesn’t find one, it will return us to the login page, otherwise, it will execute a request with our auth token that originally came from Auth0 and is intended for use at Hasura. It will read all the data about my user’s workout session activity, and then, through the remotely joined schema, Hasura will also look up the workout data itself (the title) directly from GraphCMS but resolve it to us in one unified server.&lt;/p&gt;

&lt;p&gt;One last “complex” query that deserves a special mention is the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;fragment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Rep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RepMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;repetition&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;fragment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Amrap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AmrapMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;fragment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DurationMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;fragment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Circuit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Circuit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;repetitions&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;movements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RepMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Rep&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AmrapMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Amrap&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DurationMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Premium&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$authed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;!)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;workouts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;popularity_DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;popularity&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;warmup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$authed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RepMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Rep&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AmrapMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Amrap&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DurationMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$authed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RepMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Rep&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AmrapMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Amrap&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DurationMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Circuit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Circuit&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;coolDown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$authed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RepMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Rep&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AmrapMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Amrap&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DurationMovement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code requests the content for our homepage. In this complex query we define re-usable pieces of query (fragments) to be used in multiple locations of our query body. We also pass in a variable called “authed” which has come from a check to see if the user has a valid session. If it does, it includes the workout data, if it doesn’t, it get’s skipped according to the rule defined in the &lt;code&gt;@include&lt;/code&gt; directive’s &lt;code&gt;if&lt;/code&gt; argument. This way a user can see the title and basic information about a workout program, but not the full “product” data in this case.&lt;/p&gt;

&lt;p&gt;Next: &lt;a href="https://dev.to/blog/hasura-fit-setting-up-serverless-functions"&gt;Set up the Serverless Functions&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Hasura Fit: Setting up the Auth Flow</title>
      <dc:creator>Jesse Martin</dc:creator>
      <pubDate>Thu, 13 Aug 2020 17:15:29 +0000</pubDate>
      <link>https://dev.to/hygraph/hasura-fit-setting-up-the-auth-flow-2o4e</link>
      <guid>https://dev.to/hygraph/hasura-fit-setting-up-the-auth-flow-2o4e</guid>
      <description>&lt;p&gt;Special thanks to &lt;a href="https://twitter.com/sambego?lang=en"&gt;Sam Bellen&lt;/a&gt; for helping me troubleshoot some tricky parts of the Auth0 ecosystem.&lt;/p&gt;

&lt;p&gt;In this part of our tutorial, we’ll be adding the authentication layer to our web app which lets us leverage the power of Auth0 and their social sign-on ecosystem of tooling to let us, onboard users, quickly and easily.&lt;/p&gt;

&lt;p&gt;A core tenant of modern web application architecture is “do not write your own authentication” - basically, it’s a problem that’s easy enough to get wrong and also easy enough to make you think you got it right. Find either an open-source platform where hundreds of developers contribute to the robustness of the code-base or a SaaS company for essentially the same reasons but when you need an SLA or a few convenience features behind it.&lt;/p&gt;

&lt;p&gt;Prior Art:&lt;br&gt;&lt;br&gt;
&lt;a href="https://hasura.io/docs/1.0/graphql/manual/guides/integrations/auth0-jwt.html"&gt;https://hasura.io/docs/1.0/graphql/manual/guides/integrations/auth0-jwt.html&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/auth0/nextjs-auth0"&gt;https://github.com/auth0/nextjs-auth0&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Auth0?
&lt;/h2&gt;

&lt;p&gt;Auth0 has been one of the mainstay players in the authentication space for a while, they support nearly every type of “single sign-on” - which just means re-using an account from something like your workplace or Facebook - and they have excellent developer tooling to help us integrate with our stack. They handle password resets/forgets, generating custom scope in our Auth tokens (we’ll cover this in a few moments) and more.&lt;/p&gt;

&lt;p&gt;If you need an enterprise-grade Auth library (or a generous free tier with reasonable pricing for your side projects) - Auth0 is a great solution.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating an Account
&lt;/h2&gt;

&lt;p&gt;The first thing you need to do is create an account with Auth0, after that, you need to create an Application. From the applications tab of your project, you can click on “Create Application”.&lt;/p&gt;

&lt;p&gt;Note: &lt;strong&gt;It’s critical you choose “Regular Web Application”&lt;/strong&gt; as we’ll be working with server-side authorization and the library we are using expects “Authorization Code&lt;/p&gt;
&lt;h3&gt;
  
  
  Understanding the different types of authentication flows
&lt;/h3&gt;

&lt;p&gt;Choosing SPA would result in a PKCE flow, which returns a shape of data our library isn’t expecting and is also not needed since we are authorizing server side. PKCE simply adds an additional layer of indirection before the exchange of tokens occurs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Account Settings
&lt;/h2&gt;

&lt;p&gt;The “variables” will differ for production context, but all things held constant, your localhost will stay the same.&lt;/p&gt;

&lt;p&gt;We only need to handle the settings for Allowed Callback URLs (where we return to after authentication) and the allowed Logout URLs (where we return to after logging out.)&lt;/p&gt;

&lt;p&gt;Web origins is only for contexts where the Sign-On form is EMBEDDED in the application and by default, all our allowed URLs are included in CORS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Allowed CallbackURL&lt;/strong&gt;. &lt;br&gt;
Localhost: &lt;a href="http://localhost:3000/api/callback"&gt;http://localhost:3000/api/callback&lt;/a&gt;. &lt;br&gt;
Vercel: &lt;a href="https://gcms-hasura-fit-%5C*.vercel.app/api/callback"&gt;https://gcms-hasura-fit-\*.vercel.app/api/callback&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Using an asterisk (*) in the URL is known as a wildcard and this allows you to match all urls that match the first-half before the asterisk and the last half after the asterisk and anything in the middle. This is done so that if you want to use continuous deployment (where you get a deploy every time you push code to a code repository like Github) you’ll get previews in both your master branch and any other branches where you might test out new features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt; Allowed Logout URLs&lt;/strong&gt;. &lt;br&gt;
Localhost: &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt;. &lt;br&gt;
Vercel: &lt;a href="https://gcms-hasura-fit-%5C*.vercel.app"&gt;https://gcms-hasura-fit-\*.vercel.app&lt;/a&gt;. &lt;/p&gt;
&lt;h2&gt;
  
  
  Understanding our Auth Config
&lt;/h2&gt;

&lt;p&gt;In our NextJS project we have abstracted the authentication service as a library under our &lt;code&gt;/lib&lt;/code&gt; directory where we can call our auth service from multiple places in our application. To communicate with Auth0 - we will use their SDK (software development kit) to send requests to Auth0 in the correct shape.&lt;/p&gt;

&lt;p&gt;The init method looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth0Instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;initAuth0&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_SCOPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH0_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://hasura-fit.herokuapp.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;postLogoutRedirectUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;POST_LOGOUT_REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cookieSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SESSION_COOKIE_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cookieLifetime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SESSION_COOKIE_LIFETIME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;storeIdToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;storeRefreshToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;storeAccessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Client Secrets and IDs
&lt;/h3&gt;

&lt;p&gt;Auth0 is a hosted application (SaaS) which has many different projects in their database. To tell Auth0 which one is yours, you need to use the ID and the Secret they gave you in your signup process to properly identify which project in their servers is yours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scopes
&lt;/h3&gt;

&lt;p&gt;In our application, when we call our login service, we need to tell our Authorization service (Auth0) what kind of permissions the holder of the returned token should have. In our case - we are asking for the &lt;code&gt;profile&lt;/code&gt; and &lt;code&gt;opened&lt;/code&gt; information about the user. Different services around the web might have other scopes such as “reading e-mails” or “updating calendar” - it’s common for services to restrict the permissions available to the token key they give you based on these scopes you are asking for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Audience
&lt;/h3&gt;

&lt;p&gt;Because we will be using the access token provided to access a DIFFERENT service (our hosted Heroku app) we need to tell Auth0 that the token we are asking for will be used at a different site.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hcF3IHkw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/NHLlyFtAQOAjHDyOrwz9" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hcF3IHkw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/NHLlyFtAQOAjHDyOrwz9" alt="hasura-fit-token-req.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Redirects
&lt;/h3&gt;

&lt;p&gt;See the information above about the redirect URLs.&lt;/p&gt;
&lt;h3&gt;
  
  
  Session
&lt;/h3&gt;

&lt;p&gt;The session configuration allows us to define which tokens should be saved in the browser, if at all, when it should expire and a secret key to encrypt the data.&lt;/p&gt;
&lt;h2&gt;
  
  
  Custom Rules
&lt;/h2&gt;

&lt;p&gt;Auth0 allows specific rules (or functions) to be executed when certain things happen in Auth0. We need to create a special function for when we send a token to our web app and we need to create a special rule for creating a copy of our Auth0 user in our Hasura database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PpKpidJ8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/CuXMfSBSwSQcFFXZDs3w" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PpKpidJ8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/CuXMfSBSwSQcFFXZDs3w" alt="auth0-rules.png" width="800" height="565"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Modify Token: hasura-jwt-claim
&lt;/h3&gt;

&lt;p&gt;To modify the token we send to our client which will in turn be used to access Hasura, we’ll use the following rule&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://hasura.io/jwt/claims&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idToken&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-hasura-default-role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="c1"&gt;// do some custom logic to decide allowed roles&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-hasura-allowed-roles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-hasura-user-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_id&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Synchronize Users: insert-user
&lt;/h3&gt;

&lt;p&gt;To synchronize our users, we’ll need an additional rule which creates a mutation in Hasura every time a new user signs up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasuraAdminSecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xxxx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_HASURA_ENDPOINT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upsertUserQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
        mutation($userId: String!){
          insert_users(objects: [{ id: $userId }], on_conflict: { constraint: users_pkey, update_columns: [] }) {
            affected_rows
          }
        }`&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graphqlReq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;upsertUserQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;variables&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-hasura-admin-secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hasuraAdminSecret&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;graphqlReq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
           &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
           &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m8DDABY8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/U09eTWJmSiyeqvkI6n1T" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m8DDABY8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/U09eTWJmSiyeqvkI6n1T" alt="hasura-fit-copy-user.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;To continue this tutorial, our next step will be to configure NextJs to be up and running which will be needed to configure Auth0 to work with Hasura.&lt;/p&gt;

&lt;p&gt;Next: &lt;a href="https://dev.to/blog/hasura-fit-setting-up-nextjs"&gt;Set up NextJs&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Hasura Fit: Setting up GraphCMS</title>
      <dc:creator>Jesse Martin</dc:creator>
      <pubDate>Thu, 13 Aug 2020 17:15:13 +0000</pubDate>
      <link>https://dev.to/hygraph/hasura-fit-setting-up-graphcms-18a3</link>
      <guid>https://dev.to/hygraph/hasura-fit-setting-up-graphcms-18a3</guid>
      <description>&lt;p&gt;In this portion of the multi-part tutorial, we’ll be creating our data model that acts primarily as our product information manager. Since the product being offered is a complex content structure at the end of the day, GraphCMS is the perfect tool for this job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why GraphCMS?
&lt;/h2&gt;

&lt;p&gt;The most important reason why we chose GraphCMS is because the author works there. Ok, full disclosures out of the way, GraphCMS is one of the fastest methods out there to create a hosted GraphQL API. By simply clicking together models and types you can create complex relationships, including the often verbose “many-to-many” relationships within seconds.&lt;/p&gt;

&lt;p&gt;GraphCMS also lets us have a content editing backend where our theoretical marketing team would be able to update the product info (our product being workout programs) and be able to update our application’s content without needing the traditional heightened access controls like accessing a user database would entail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w5JFswRj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/OGhGkpIES0Ks3pLMOj6I" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w5JFswRj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/OGhGkpIES0Ks3pLMOj6I" alt="graphcms-editor.png" width="800" height="456"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cZTDuv0N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/MkQ6NSF4TOCzwBNAfnL9" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cZTDuv0N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/MkQ6NSF4TOCzwBNAfnL9" alt="graphcms-union-types.png" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is our separation of concerns between Hasura and GraphCMS - Hasura handles our user data and GraphCMS handles our product data.&lt;/p&gt;

&lt;p&gt;The last reason we are using GraphCMS, and one of the core USPs of GraphCMS, is that it supports GraphQL mutations as well! This means that we can write data back into our content model. This is helpful because from our application data hooks, whenever a user completes a workout, we are able to simply update an integer of “popularity” on one of our workout programs so that we have a pre-compiled analytics for sorting these workouts later without the need to worry about secure and costly aggregation of our entire user database when our front-end team simply wants to sort which workouts are the most popular.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Variables in GraphCMS
&lt;/h2&gt;

&lt;p&gt;GraphCMS doesn’t have environment variables per se but it does have permanent auth tokens which we’ll use to authorize access to our API. Particularly because of our mutation API, it’s important to separate who has access to mutate our content vs who has access to just read our content. We can also separate access to draft vs published content in the situation where you have a preview environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Content Model
&lt;/h2&gt;

&lt;p&gt;We’ll be doing most of our fancy content modeling in GraphCMS. Our content model will be taking advantage of a number of content modeling concepts such as composition, extending base types and creating union relationships of one or more types.&lt;/p&gt;

&lt;p&gt;I’d like to invent a term with you called “SCUR” - this idea is not new but let’s give it a name. SCUR stands for “smallest composable unit, reasonable”. What this means is that we should try to break our content model into the smallest entry we possible can within reason balancing the concerns of time, resource and maintainability.&lt;/p&gt;

&lt;p&gt;My model has the following primitives: A base workout type, say, a pushup. We extend on that with composition to create different types of this workout, pushup exercises that are a function of time (5-second count), pushups that are a function of repetition (25 pushups) and pushups that are a function of endurance (AMRAP - as many reps as possible, usually within a minute). Later if I want to identify the average caloric value of an exercise or some kind of additional data to my base exercise type, I can do that without the need to update a bunch of locations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MPnum89q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/1UMpCtwlRUmHv6QUFYQ7" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MPnum89q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/1UMpCtwlRUmHv6QUFYQ7" alt="hasura-fit-model.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, our base workout is also one of two types, either a body weight exercise which can be done with just your own body’s weight or an exercise dependent on equipment such as boxing or jump-roping.&lt;/p&gt;

&lt;p&gt;Another type we have is a set, or a grouping of exercises together that require some kind of order. Since these could be a mixture of time exercises, repetition exercises or AMRAP exercises, and even sub-sets, we need to use a union type relation that lets us define the possibility of any of these valid inputs.&lt;/p&gt;

&lt;p&gt;These sets, mixed with warmups (also a union type to our exercise matrix) and cool-downs (also a union type) along with some content description, images and more create the full exercise protocol or programming and a complete product in our system.&lt;/p&gt;

&lt;p&gt;And now our content model from GraphCMS is complete!&lt;/p&gt;

&lt;p&gt;Next: &lt;a href="https://dev.to/blog/hasura-fit-setting-up-auth0"&gt;Set up Auth0&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Hasura Fit: Complete Guide / Start Here</title>
      <dc:creator>Jesse Martin</dc:creator>
      <pubDate>Thu, 13 Aug 2020 17:15:06 +0000</pubDate>
      <link>https://dev.to/hygraph/hasura-fit-complete-guide-start-here-3cp4</link>
      <guid>https://dev.to/hygraph/hasura-fit-complete-guide-start-here-3cp4</guid>
      <description>&lt;p&gt;This is a multi-part tutorial on creating a fitness app with modern web-technologies. It utilises user accounts (Auth0), User data (Hasura), Editorial content (GraphCMS), serverless functions (Vercel) and the popular React framework NextJs (also hosted on Vercel).&lt;/p&gt;

&lt;p&gt;The rough flow of our application is as follows.&lt;/p&gt;

&lt;p&gt;A user comes to our application which shows content required no authentication. They decide to join the program and signup.&lt;/p&gt;

&lt;p&gt;When signing up, they are redirected to Auth0 where a new user account is created, which in turn creates a new user account in Hasura for later reference, and then sends an authentication token back to our user in the web app with special permissions to be re-used with our Hasura API and a few modified parameters to help us confirm who our user is there.&lt;/p&gt;

&lt;p&gt;The web application updates with this new information and then shows authenticated content, such as our workout programs, which comes from Hasura at the query level, but is resolved from GraphCMS through a federated API mesh which we configured in Hasura.&lt;/p&gt;

&lt;p&gt;Once our user performs one of the workouts, this action will save a new session in our Hasura database and at the same time, trigger a web hook which increments the popularity of that workout in our GraphCMS content API.&lt;/p&gt;

&lt;p&gt;Here’s a rough flow chart for the following steps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WcbnRLcW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/mB4SywivRBiQ3iUdOMKA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WcbnRLcW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.graphcms.com/mB4SywivRBiQ3iUdOMKA" alt="hasura-fit-master-diagram.png" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find errors, typos, or updates to the functionality, pull requests are always welcome!&lt;/p&gt;

&lt;p&gt;Next: &lt;a href="https://dev.to/blog/hasura-fit-setting-up-graphcms"&gt;Set up GraphCMS&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Project Sketches</title>
      <dc:creator>Jesse Martin</dc:creator>
      <pubDate>Fri, 08 May 2020 12:17:02 +0000</pubDate>
      <link>https://dev.to/motleydev/project-sketches-5dgk</link>
      <guid>https://dev.to/motleydev/project-sketches-5dgk</guid>
      <description>&lt;p&gt;Like a good 100k+ of you out there, I follow the excellent “learn in public” advocate &lt;a href="https://dev.to/emmabostian"&gt;Emma Bostian&lt;/a&gt;. If you don’t follow her, go raise that number. She shared a Tweet that is more of a “meme” among developers than anything else at this point. The classic syndrome of the "project graveyard" of personal Github repos.&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--oKETWI0Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1239969385579773952/756CZbQE_normal.jpg" alt="Emma Bostian 🐞 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Emma Bostian 🐞
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        &lt;a class="mentioned-user" href="https://dev.to/emmabostian"&gt;@emmabostian&lt;/a&gt;

      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      My GitHub is more like a project graveyard of half-finished, half-assed projects than a set of project repositories that showcase my skills.
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      06:46 AM - 08 May 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1258649502493413376" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1258649502493413376" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1258649502493413376" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;If you haven't seen this actual meme that somewhat painfully describes the problem, well, here you go. It's one of my favorites.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q3S89kR8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/exl94fphai9xb3clkmad.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q3S89kR8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/exl94fphai9xb3clkmad.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To be honest, the vast majority of my projects never even make it to Github, to begin with. The new unlimited private repositories are starting to change that, thank you Github!&lt;/p&gt;

&lt;p&gt;But why? Why do we feel this way? Why do we as developers hold on to this notion that our projects need to be world-changing endeavors that just need a pitch deck and cheesy .io domain to go on to greatness?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Let's stop even calling them full side-projects and just call them "project sketches."&lt;/p&gt;
&lt;/blockquote&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--LIRrYH8s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1204366626738622466/ufPGhfrp_normal.jpg" alt="Jesse Martin profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Jesse Martin
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        &lt;a class="mentioned-user" href="https://dev.to/motleydev"&gt;@motleydev&lt;/a&gt;

      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      &lt;a href="https://twitter.com/EmmaBostian"&gt;@EmmaBostian&lt;/a&gt; Call them "project sketches". Not every two bits of code we put together need to become unicorn startups. You wouldn't call a sketchbook a collection of half-finished, half-assed masterpieces.
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      06:54 AM - 08 May 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1258651396603359233" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1258651396603359233" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1258651396603359233" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;Yes, I liked something I tweeted so I wrote an article about it. Even worse, it was just something I replied. 🤦🏻‍♂️&lt;/p&gt;

&lt;h2&gt;
  
  
  Be Bold
&lt;/h2&gt;

&lt;p&gt;But the point is real. We don't call drawings in a sketchbook "masterpieces in progress", sometimes it's worth just coding something for the exercise. You will draw a lot of circles and triangles on paper &lt;a href="https://twitter.com/DesignUXUI/status/490179377099845632?s=20"&gt;before you finish drawing the owl&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, feel free to explore. Make a blog with one page and one post if you want to, create buttons that don't actually do anything and simple exercise in this powerful medium that we have. Don't be afraid to share a sketch - and to be able to call it a sketch! Not everything needs to be a WIP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shameless Plug
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Justifying writing this at work&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I recently released a small CLI for bootstrapping projects AND sketches using &lt;a href="https://graphcms.com/"&gt;the CMS that I work for&lt;/a&gt;. It's a great way to get pixels on the page, quickly.&lt;/p&gt;

&lt;p&gt;I used &lt;code&gt;npx create-gcms-app with-nextjs&lt;/code&gt; to build and deploy this in less than 10 minutes. Five minutes for a coffee and choosing the name, 'cause, naming things is hard. A few of those minutes to figure out why &lt;code&gt;100vh&lt;/code&gt; gave me overflow. Sketches.&lt;/p&gt;

&lt;p&gt;So here you go, a project sketch, with cool developer domain and pitch deck all in one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gobebold.now.sh/"&gt;https://gobebold.now.sh/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/2e85l"&gt;
&lt;/iframe&gt;
&lt;/p&gt;




&lt;p&gt;Thanks for reading. If you like this, &lt;a href="https://twitter.com/motleydev"&gt;follow me on Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, PS, &lt;a href="https://graphcms.recruitee.com/"&gt;the company I work for is hiring&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>writing</category>
    </item>
    <item>
      <title>Run your own conf, the serverless way</title>
      <dc:creator>Jesse Martin</dc:creator>
      <pubDate>Wed, 29 Jan 2020 15:02:18 +0000</pubDate>
      <link>https://dev.to/motleydev/run-your-own-conf-the-serverless-way-3ghi</link>
      <guid>https://dev.to/motleydev/run-your-own-conf-the-serverless-way-3ghi</guid>
      <description>&lt;p&gt;We’ve got another open source starter project for you built on production-grade serverless technologies. Today’s project is all about Headless CMS for events and conferences! If you’ve ever tried to put on a meetup, conference, or other type of event, you’ll know that there’s roughly two halves to the content flow of a conference: the speakers, and the talk submissions.&lt;/p&gt;

&lt;p&gt;Useful links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs-graphcms-events-starter.now.sh"&gt;Check out the project.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/graphCMS/nextjs-graphcms-events-starter"&gt;Look at the repo on GitHub.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://graphcms.com/contact/"&gt;Reach out to us to discuss GraphCMS and events.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are a number of services out there that let you manage conference agendas, and then there are a number of other services that let you accept talk submissions, but there are very few that give you the flexibility that your conference needs.&lt;/p&gt;

&lt;p&gt;With the powerful backing of a headless content repo, the flexibility of serverless technologies, and the power of modern developer tooling, you can quickly build a system that lets you run multiple conferences all around the world with granular control over the content, submission flows, and more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oSZuEaIz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/1B3QEIscQGmEUPIaXokK" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oSZuEaIz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/1B3QEIscQGmEUPIaXokK" alt="woman-holding-iphone-7-in-front-of-the-palm-tree.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Content Structure
&lt;/h2&gt;

&lt;p&gt;Implied in the name of &lt;a href="https://graphcms.com"&gt;GraphCMS&lt;/a&gt; is the idea of graph structures. Graphs are incredibly powerful and expressive models to define relationships with. We’ve written more about graphs on &lt;a href="https://graphcms.com/docs/introduction/headless-thinking"&gt;this page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gG5lTnrd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/bFWIOrcqSFaL9bySz5gd" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gG5lTnrd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/bFWIOrcqSFaL9bySz5gd" alt="Events Starter.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you notice the relationship between tracks, talk and person - we are able to provide semantic information on the RELATIONSHIP, defining how each node sees the other. The context changes depending on the relationship. In graph speak, this is called an edge.&lt;/p&gt;

&lt;p&gt;The reason we break our nodes down into such granularity is that it affords us ultimate content composition. We can now re-use the same talk and even the same track at multiple events.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Service Mesh
&lt;/h2&gt;

&lt;p&gt;The Event Starter is powered by a handful of APIs, but only three service providers. First, GraphCMS houses the entire content structure. All submissions, events, schedules and more are housed within GraphCMS. Second, we utilize Postmark as a transactional email service to deliver confirmation and notification emails. Lastly, we use the powerful NextJs framework hosted in Now (both products of Zeit) that allow us to “repackage” our &lt;a href="https://graphcms.com"&gt;GraphCMS&lt;/a&gt; APIs as both triggers for our notification process (via GraphCMS webhooks) and as a processor of our email response - you can accept and approve of a talk directly from an email!&lt;/p&gt;

&lt;p&gt;Here’s a guide to help us understand what’s happening in the service architecture.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wdGsMJsp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/WUvWZubSpqpPLevNZYlj" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wdGsMJsp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/WUvWZubSpqpPLevNZYlj" alt="service-mesh.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GraphCMS&lt;/strong&gt;&lt;br&gt;
When looking at the content architecture above as well as the service architecture, &lt;a href="https://graphcms.com"&gt;GraphCMS&lt;/a&gt; hosts all the content, acts as the “submission database” and notifies our API endpoints when submissions have been added and when their acceptance status changes (via Webhook).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next / Now&lt;/strong&gt;&lt;br&gt;
Next lets us build out our web application front-end as well as define our API functions that will run in the Now environment. Now hosts our web application as well as the lambda functions from our APIs and powers the connections between the different services. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Postmark&lt;/strong&gt;&lt;br&gt;
Postmark delivers all our transactional emails with a flexible but simple templating system and a “no-fuss” API. &lt;/p&gt;

&lt;p&gt;Any of the above resources could be replaced by the services you already use, but hopefully, this gives you an idea of how to get started connecting the best of breed APIs together to architect any kind of submission/approval process – your way.&lt;/p&gt;

&lt;p&gt;The code for the example can be found over on &lt;a href="https://github.com/graphCMS/nextjs-graphcms-events-starter"&gt;GitHub&lt;/a&gt;. There you will find instructions on how to set up, configure, and manage the project yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Dp4Xn2Gl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/KEASQaIS12BcEvDISOJs" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Dp4Xn2Gl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/KEASQaIS12BcEvDISOJs" alt="charging-iphone-xs.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>serverless</category>
      <category>headlesscms</category>
    </item>
    <item>
      <title>Beyond Static, a Gatsby Tale</title>
      <dc:creator>Jesse Martin</dc:creator>
      <pubDate>Wed, 29 Jan 2020 14:36:17 +0000</pubDate>
      <link>https://dev.to/motleydev/beyond-static-a-gatsby-tale-338n</link>
      <guid>https://dev.to/motleydev/beyond-static-a-gatsby-tale-338n</guid>
      <description>&lt;p&gt;TLDR;&lt;br&gt;
Here's the important stuff.&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--LIRrYH8s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1204366626738622466/ufPGhfrp_normal.jpg" alt="Jesse Martin profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Jesse Martin
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        &lt;a class="mentioned-user" href="https://dev.to/motleydev"&gt;@motleydev&lt;/a&gt;

      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Big thanks to &lt;a href="https://twitter.com/bitgrip"&gt;@bitgrip&lt;/a&gt; for inviting me to the Headlessons meetup last night! As promised, here's the repo for the project I showed, and the Demo:&lt;br&gt;&lt;br&gt;🛠️ Repo: &lt;a href="https://t.co/djPYpoUg9W"&gt;github.com/GraphCMS/examp…&lt;/a&gt;&lt;br&gt;⚡️ Demo: &lt;a href="https://t.co/Fyotlqqa6m"&gt;gatsby-static-dynamic-hotel.now.sh&lt;/a&gt;&lt;br&gt;&lt;br&gt;Built with &lt;a href="https://twitter.com/gatsbyjs"&gt;@gatsbyjs&lt;/a&gt; and &lt;a href="https://twitter.com/GraphCMS"&gt;@GraphCMS&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      16:15 PM - 23 Jan 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1220379534928662528" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1220379534928662528" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1220379534928662528" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;Follow me for &lt;del&gt;my personal vanity&lt;/del&gt; engaging content about building software and stories about my kids.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;In the world of static site generators, one rule remains constant - the build is the gospel. Whatever was at build time will be until a new build occurs. It's part of what makes them so fast, anything sourced from a database, flat-file, CMS, or what have you - becomes stamped into code in HTML, CSS, and JavaScript. Once that transformation occurs, there's no need for data fetching, transformation or template rendering - that part is done! Browsers can simply show you exactly what the server sends.&lt;/p&gt;

&lt;p&gt;But sometimes our data, you know, changes. Imagine running a stock exchange on a static site?! Even modern ecommerce sites have pricing that can vary hundreds of times a day to reflect real-time &lt;del&gt;price manipulation&lt;/del&gt; supply and demand forces.&lt;/p&gt;

&lt;p&gt;So what's a JAMing developer to do? Well, the obvious solution is to build the parts that have a longer "TTL (time-to-live)" and fetch the changing bits from the client.&lt;/p&gt;

&lt;p&gt;When dealing with Gatsby, however, that presents a challenge. One of my favorite things about Gatsby is the content mesh API that it creates. You can throw nearly any data source at it, even plain text files, and someone in the community will have created a plugin that parses the data and puts it into a flexible GraphQL API. From there, you can query all the data you want, and push the data through React templates. It's really a fantastic experience.&lt;/p&gt;

&lt;p&gt;But it only works at build time. The API goes away, the data is stamped to a persistent state for rehydration, the clowns get back in the car and go home. If you want to query data from the browser, you'll need to reference back to the original data source that the Gatsby plugin you are using is sourcing from. Most external systems are still exposing a REST interface which means you now need to work with two different API protocols.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Hero emerges
&lt;/h2&gt;

&lt;p&gt;Thankfully, a growing number of online services are beginning to expose a native GraphQL interface, too! We at &lt;a href="https://graphcms.com"&gt;GraphCMS&lt;/a&gt; have been native GraphQL from the beginning, and when you source content from us, you can use the same knowledge base and experience you've gathered building the static site to now fetch content straight from the original source.&lt;/p&gt;

&lt;p&gt;Let's look at the two places we fetch data in our demo example. Our domain, for context, is a hotel listing website, cleverly named "Gotell" that fetches available rooms dynamically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OrixjJzO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hcj2trfb43cthtl11yo1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OrixjJzO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hcj2trfb43cthtl11yo1.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm available for naming your Startup if you need me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eo9MSZH---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6q7oj8egpamlampbdc5j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eo9MSZH---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6q7oj8egpamlampbdc5j.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To generate this index page, we fetch our data in the &lt;code&gt;gatsby-node.js&lt;/code&gt; file. As I generated multiple demos for this talk, you'll notice I fetch TWO batches of data and merge them together, this is not needed in most cases but I chose to do that since I used the separate data sources for other demos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;graphql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createPage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;

  &lt;span class="c1"&gt;// import various templates needed...&lt;/span&gt;


  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;minimalQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;graphql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`
      query {
        gcms {
          hotels {
            id
            slug
            name
          }
        }
      }
    `&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minimalQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;minHotels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;minimalQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gcms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hotels&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extendedQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;graphql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`
      query {
        gcms {
          hotels {
            id
            description
            photos {
              url
            }
          }
        }
      }
    `&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;extendedQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extendedHotels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;extendedQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gcms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hotels&lt;/span&gt;

  &lt;span class="c1"&gt;// Create a merged data set, what would essentially be one large query"&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hotels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minHotels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extendedHotels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="cm"&gt;/*
   Demo One!
   Creating a single large index from the content
  */&lt;/span&gt;

  &lt;span class="nx"&gt;createPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/demo-one/hotels&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hotelIndexPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;hotels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We pass that list of hotels to the &lt;code&gt;pageContext&lt;/code&gt; where it will be converted to HTML at &lt;strong&gt;build&lt;/strong&gt; time. From the template, we fetch our dynamic content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Hotels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;pageContext&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hotels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateHotels&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hotels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;roomsFetched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setRoomsFetched&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isCurrent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;postData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GATSBY_GCMS_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`query {
            hotels {
              id
              rooms
            }
          }`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isCurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;updateHotels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hotels&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nx"&gt;setRoomsFetched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isCurrent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SEO&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Demo One"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-wrap"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hotels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HotelBox&lt;/span&gt; &lt;span class="na"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hotel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;roomsFetched&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;roomsFetched&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An important detail here is that we push the &lt;code&gt;pageContext&lt;/code&gt; data into the React state and iterate over that array instead of directly from pageContext. This allows us to update our state with the fetched data from our &lt;a href="https://overreacted.io/a-complete-guide-to-useeffect/"&gt;&lt;code&gt;useEffect&lt;/code&gt; hook&lt;/a&gt; and update the entries where applicable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;There's nothing really more complex about this example. The basic process follows fetching data at build time, then fetching partial data at client load time and using an updater pattern to let us combine the data on the page. The key benefit here is being able to use GraphQl for both parts. If you examine the two code samples above we are writing nearly identical query syntax in both cases (our &lt;code&gt;gatsby-source-graphql&lt;/code&gt; plugin adds a new top-level type which introduces an extra level of nesting for the build-time query.)&lt;/p&gt;

&lt;p&gt;Removing the cognitively expensive context switching from any other API source to the data-mesh GraphQL API is a major win. The only catch is that you need a system that natively supports GraphQL - &lt;a href="https://graphcms.com"&gt;which is something we are happy to help you with!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>gatsby</category>
      <category>graphql</category>
      <category>headlesscms</category>
    </item>
    <item>
      <title>The Secret Languages of Culture - and Slack</title>
      <dc:creator>Jesse Martin</dc:creator>
      <pubDate>Wed, 06 Nov 2019 10:16:29 +0000</pubDate>
      <link>https://dev.to/motleydev/the-secret-languages-of-culture-and-slack-2c19</link>
      <guid>https://dev.to/motleydev/the-secret-languages-of-culture-and-slack-2c19</guid>
      <description>&lt;p&gt;I’m guilty of a particular hand gesture that, for whatever reason, seems ingrained in my muscle memory though I have no remembrance of how it got there. The gesture has zero significance to me, and, interestingly enough, seems relatively common amongst various other Americans I’ve come to know since moving overseas as an expat.&lt;/p&gt;

&lt;p&gt;The gesture involves, more or less, slapping of an open hand (usually the left) on to the closed fist of the right - or sort of bringing them together - with either the unintended or intended consequence of a slight popping sound happening in the hollow of the right hand. It’s used often to start a sentence, often if order needed to be called first, in a group of peers. Think of it as a gym class version of chiming a wine glass.&lt;/p&gt;

&lt;p&gt;The gesture also happens to have incredibly crass and sometimes perverse meaning in foreign cultures. No doubt, the gesture I learned as an innate communication mechanism was simply borrowed from the cultures where its original meaning remains intact, but, like most things in the US, it simply lost its original flavor and now floats around in the stew we call American culture.&lt;/p&gt;

&lt;p&gt;And culture is funny like that. A shared history of one community can quickly leave newcomers completely in the dark, if not entirely excluded. Yell “ABC” in the salesroom of a startup and you’ll likely get a handful of laughs. If you’re completely confused by that sentence, you’re not part of that culture. Look up Glengarry Glen Ross if you want in.&lt;/p&gt;

&lt;p&gt;Culture imbues our identity and the subtexts in which we communicate with co-workers and friends. Order an espresso at lunch in the US and it means, “let’s keep the conversation going.” Order an espresso at lunch in Germany and it means, “we’re done here.”&lt;/p&gt;

&lt;p&gt;Five years ago it was common to hear people make jokes about “nazis” in the US to mean anyone who was stingy. No soup for you (Seinfeld). You could never make that joke in Germany. Further, even in the US today, it’s been so co-opted by the “left” that it’s now meant to generally disparage anyone with a different opinion. In Germany, its true meaning and the horrors it implies remains prevalent and relevant.&lt;/p&gt;

&lt;p&gt;A culture I’ve known primarily amongst my male friends is to communicate through quoting movies. Really, it’s amazing how much of the day-to-day has already been covered in the, er, “classics.” But even that communication, shared between people with familiar geo-contextual backgrounds, creates a culture of exclusion for those that didn’t see the same films. Access, availability, and opportunity become filters for an entire language that others may not have.&lt;/p&gt;

&lt;p&gt;Prevalent in the tech industry (and many “knowledge worker” industries) is what has been called “the Slackification” of the modern office. A trend that encompasses many things, but non-the-least of which includes a predominance of text-based, emoji laden conversation.&lt;/p&gt;

&lt;p&gt;Beyond just the normal, “standardized” set of emoji which has been blessed by the majority of tech companies, platforms such as Slack allow for custom emoji. It’s in particular with these custom emojis that we are exposed to a lot of cultural risk of both excluding others from the way we talk and even causing unintended offense.&lt;/p&gt;

&lt;p&gt;A subset of developers I’ve worked with, being the meme-versed subculture that they (we) are, adore the “Pepe” meme for all forms of “reactions” to things people say. Coming from a US culture where Pepe has taken on a decidedly racist, misogynistic tone, it’s difficult to ignore. Further, being an old man in startup years (over 30) there’s a generation of meme usage (and emojified memes) that I simply don’t know. Not only myself but a class of us “older ones” where coworkers not even 10 years our junior have a subset of references that are lost on me. And the knife cuts both ways, there’s a wide range of 80's and 90's hit songs that I tend to quote with obnoxious regularity that are completely lost on a subculture that simply didn’t listen to the same music.&lt;/p&gt;

&lt;p&gt;My children are now at the age where it’s amusing to create their own language as they play with the rules of what’s understood by the majority. The creation of custom Slack emojis is really no different. With a workforce that is diversifying at a rate never before seen, it’s important for all of us to not forget the standards of intercultural communication and to watch out for the pitfalls of creating “secret languages” that exclude and even accidentally harm others. The internet turned 50 this year and in many ways, we are more connected today than at nearly any other point in recorded history. With effort and an awareness of other’s cultural backgrounds, we can make sure the next 50 only bring us closer together.&lt;/p&gt;

</description>
      <category>culture</category>
      <category>slack</category>
      <category>communication</category>
    </item>
  </channel>
</rss>
