<?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: arnu515</title>
    <description>The latest articles on DEV Community by arnu515 (@arnu515).</description>
    <link>https://dev.to/arnu515</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%2F406164%2F7a619321-9edf-458c-bd4f-76a540668cd8.png</url>
      <title>DEV Community: arnu515</title>
      <link>https://dev.to/arnu515</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arnu515"/>
    <language>en</language>
    <item>
      <title>SupportAddress — An email-first support platform</title>
      <dc:creator>arnu515</dc:creator>
      <pubDate>Mon, 09 Jun 2025 00:04:09 +0000</pubDate>
      <link>https://dev.to/arnu515/supportaddress-an-email-first-support-platform-1lo5</link>
      <guid>https://dev.to/arnu515/supportaddress-an-email-first-support-platform-1lo5</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/postmark"&gt;Postmark Challenge: Inbox Innovators&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://support.aarnavpai.in" rel="noopener noreferrer"&gt;SupportAddress&lt;/a&gt; is an email-first support platform that lets your customers reach out for support directly through email. There's no learning difficult eccentric software, creating accounts, or checking back at a webpage every now-and-then to see if the support ticket has been updated; all of that is done via email!&lt;/p&gt;

&lt;p&gt;Create subgroups within organisations to automatically sort incoming tickets using AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The application is hosted live at &lt;a href="https://support.aarnavpai.in" rel="noopener noreferrer"&gt;https://support.aarnavpai.in&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Testing instructions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sign in/up at &lt;a href="https://support.aarnavpai.in" rel="noopener noreferrer"&gt;https://support.aarnavpai.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;If you're signing up, you'll receive an OTP in your email. Enter that, and then choose a display name and password.&lt;/li&gt;
&lt;li&gt;Once logged in, you'll be taken to the dashboard, where you'll be asked to join or create an organisation. Create an organisation.&lt;/li&gt;
&lt;li&gt;Your &lt;em&gt;SupportAddress&lt;/em&gt; will be displayed at your organisation's home page. Give this SupportAddress to your customers for them to email any support queries they may have to it.&lt;/li&gt;
&lt;li&gt;You may create subgroups (from the organisation actions section at the bottom of the organistation home page). Giving a description to your subgroup lets AI attempt to automatically send any matching support tickets to it.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can invite other people to your organisation, and once they've joined, you can add them to subgroups. People who are not part of a subgroup will not be allowed to access its tickets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To create a ticket, email the organisation's SupportAddress, or the SupportAddress of any individual subgroup. The tickets list should update automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Support Agents can assign themselves to a ticket. If they do that, nobody else will be able to reply to or close that ticket, unless that agent unassigns themselves. Replying to a ticket will send an email to the customer with the reply.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once a ticket is closed, it can only be re-opened if the customer who opened the ticket replies to the ticket mail thread.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Screenshots:&lt;/p&gt;

&lt;p&gt;Join/Create organisation page:&lt;/p&gt;

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

&lt;p&gt;Subgroup creation page:&lt;/p&gt;

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

&lt;p&gt;Org Home page:&lt;/p&gt;

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

&lt;p&gt;Sending an email to the SupportAddress:&lt;/p&gt;

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

&lt;p&gt;A new ticket was automatically created and categorised:&lt;/p&gt;

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

&lt;p&gt;Send a reply from the dashboard:&lt;/p&gt;

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

&lt;p&gt;With attachment support:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/arnu515" rel="noopener noreferrer"&gt;
        arnu515
      &lt;/a&gt; / &lt;a href="https://github.com/arnu515/supportaddress" rel="noopener noreferrer"&gt;
        supportaddress
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Email-first customer support
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SupportAddress&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;The email-first support platform that lets your customers reach out for support directly through email.&lt;/p&gt;
&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/postmark" rel="nofollow"&gt;Postmark Challenge: Inbox Innovators&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is hosted live at &lt;a href="https://support.aarnavpai.in" rel="nofollow noopener noreferrer"&gt;https://support.aarnavpai.in&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Read more about this project &lt;a href="https://dev.to/arnu515/supportaddress-an-email-first-support-platform-1lo5" rel="nofollow"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/arnu515/supportaddress" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I used &lt;a href="https://qwik.dev" rel="noopener noreferrer"&gt;QwikJS&lt;/a&gt; for this project. Qwik is a relatively new JavaScript framework that uses Resumability for client-side interactivity rather than Hydration. Resumability means that the client-side bundle can essentially "pick-up" where the server-side rendering stopped, i.e. adding event listeners, running on-mount hooks, etc., instead of having to re-render the entire app from scratch like how traditional SSR frameworks do (Next, SvelteKit, etc.). I used QwikJS before for &lt;a href="https://aarnavpai.in" rel="noopener noreferrer"&gt;my website&lt;/a&gt;, but that website is mostly static content, so to give Qwik a real try, I decided to use it for this project.&lt;/p&gt;

&lt;p&gt;Other components of the tech stack include Supabase for the database, auth, storage, and functions; TailwindCSS for the styling; Cloudflare Workers AI for the AI models; and Vercel for deployment. A full list of used libraries can be found in the &lt;code&gt;package.json&lt;/code&gt; file in the repository.&lt;/p&gt;

&lt;p&gt;Postmark was used for sending and receiving emails. When a customer emails the SupportAddress, Postmark calls a Supabase edge function with the email data. That edge function sanitizes the email, determines whether it belongs to an existing ticket, or creates a new ticket, and uses AI to categorize it into a subcategory. Any attachments received are uploaded to Supabase storage by the function. Finally, if the ticket is closed, the function opens it.&lt;/p&gt;

&lt;p&gt;When a Support Agent replies to, or closes a ticket, an email is sent to the customer using Postmark's API. The API makes it very easy to send emails, including with attachments and custom SMTP headers, without having to set up an entire SMTP connection.&lt;/p&gt;

&lt;p&gt;I'd like to thank Postmark for giving me an opportunity to explore email parsing, QwikJS, and AI models! I wanted to use more AI in this project, but unfortunately due to me starting this challenge much later than I was supposed to, I wasn't able to add more AI features. Thanks for stopping by!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>postmarkchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>I wanna Do ____! — An AI powered habit building application</title>
      <dc:creator>arnu515</dc:creator>
      <pubDate>Mon, 20 Jan 2025 00:07:46 +0000</pubDate>
      <link>https://dev.to/arnu515/i-wanna-do-an-ai-powered-habit-building-application-3kpi</link>
      <guid>https://dev.to/arnu515/i-wanna-do-an-ai-powered-habit-building-application-3kpi</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github"&gt;GitHub Copilot Challenge &lt;/a&gt;: New Beginnings&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A Habit tracking application that uses AI to create tasks and goals for you which help you build good habits!&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The live demo is hosted at: &lt;a href="https://do.aarnavpai.in" rel="noopener noreferrer"&gt;https://do.aarnavpai.in&lt;/a&gt; &lt;br&gt;
The source code is at: &lt;a href="https://github.com/arnu515/i-wanna-do" rel="noopener noreferrer"&gt;https://github.com/arnu515/i-wanna-do&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Screenshots:&lt;/p&gt;

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

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

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

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

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

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

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

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/arnu515" rel="noopener noreferrer"&gt;
        arnu515
      &lt;/a&gt; / &lt;a href="https://github.com/arnu515/i-wanna-do" rel="noopener noreferrer"&gt;
        i-wanna-do
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      AI powered habit building app!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;I wanna Do!&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;An app that lets you build habits with AI!&lt;/p&gt;
&lt;p&gt;This application was built for the &lt;a href="https://dev.to/challenges/github" rel="nofollow"&gt;GitHub Copilot 1-Day Build Challenge&lt;/a&gt; (and it was built in ~19h!)&lt;/p&gt;
&lt;p&gt;It is hosted live at &lt;a href="https://do.aarnavpai.in" rel="nofollow noopener noreferrer"&gt;https://do.aarnavpai.in&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For more information, check &lt;a href="https://dev.to/arnu515/i-wanna-do-an-ai-powered-habit-building-application-3kpi" rel="nofollow"&gt;this post on DEV&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/arnu515/i-wanna-do" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Copilot Experience
&lt;/h2&gt;

&lt;p&gt;Copilot was very useful in this task. I'm sure it would have taken me twice as long if it weren't for Copilot. Copilot does repetitive tasks very well, for example, converting an SQL schema into a typescript interface, or converting JSON to typescript interfaces. It can also do all the boilerplate code, so you focus on what matters. Copilot chat is useful for performing tedious refactors that would require command line scripts to do! Copilot chat was also useful in planning this application.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Models
&lt;/h2&gt;

&lt;p&gt;This application uses GitHub Models to plan out a habit building journey. The LLM converts whatever habit the user wishes to build into a proper roadmap with goals and tasks, breaking down big tasks into smaller ones and making them more approachable.&lt;/p&gt;

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

&lt;p&gt;This was a unique challenge because of the time limit. There were so many things I wanted to add but couldn't because of the time constraints. Nevertheless, it was a lot of fun! I hope GitHub comes back with more challenges in the future!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Easy development environments with Nix and Nix flakes!</title>
      <dc:creator>arnu515</dc:creator>
      <pubDate>Wed, 15 Jan 2025 15:04:37 +0000</pubDate>
      <link>https://dev.to/arnu515/easy-development-environments-with-nix-and-nix-flakes-21mb</link>
      <guid>https://dev.to/arnu515/easy-development-environments-with-nix-and-nix-flakes-21mb</guid>
      <description>&lt;p&gt;In this article, we shall cover declarative development shells with Nix flakes! If you're new to Nix, I recommend checking out the previous two articles in this series to get a better understanding, since this article assumes that you've read the previous two already.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's revise: Creating a Shell
&lt;/h2&gt;

&lt;p&gt;The previous article introduced you to the &lt;code&gt;nix shell&lt;/code&gt; command, which downloads/builds packages and puts you in a shell environment with them in the &lt;code&gt;$PATH&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You need not run the examples given below, they're just provided for illustration purposes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By default, &lt;code&gt;nix shell&lt;/code&gt; drops you into a shell (which is your login shell) with the packages in &lt;code&gt;$PATH&lt;/code&gt;. You can also specify an arbitrary command to run, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Postgres client&lt;/span&gt;
nix shell nixpkgs#postgres_17 &lt;span class="nt"&gt;--command&lt;/span&gt; psql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, you can also use another shell (that should exist in &lt;code&gt;$PATH&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix shell nixpkgs#nushell &lt;span class="nt"&gt;--command&lt;/span&gt; nushell  &lt;span class="c"&gt;# https://www.nushell.sh/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also run multiple commands like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix shell nixpkgs#gnumake &lt;span class="nt"&gt;--command&lt;/span&gt; sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"make &amp;amp;&amp;amp; make install"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to run the package itself, you need not use &lt;code&gt;nix shell&lt;/code&gt;, since the &lt;code&gt;nix run&lt;/code&gt; command also exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix run nixpkgs#vim &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--log&lt;/span&gt; some.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are some more things that &lt;code&gt;nix shell&lt;/code&gt; can do, which I will not be covering here, but they are covered &lt;a href="https://nix.dev/manual/nix/2.24/command-ref/new-cli/nix3-env-shell" rel="noopener noreferrer"&gt;in the reference manual&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;nix shell&lt;/code&gt; to create a development environment
&lt;/h3&gt;

&lt;p&gt;This can be done, but it is tedious. Imagine having to type dozens of dependencies manually by hand every time you want to enter your development environment, or even if you put it in a script, or use the &lt;a href="https://nix.dev/manual/nix/2.24/command-ref/new-cli/nix3-env-shell#opt-stdin" rel="noopener noreferrer"&gt;&lt;code&gt;--stdin&lt;/code&gt; argument&lt;/a&gt;, you'll still have the risk of installing a different version of the package than required. You may also like to customize the shell environment a bit more, like populating environment variables, running commands before the shell starts, etc.&lt;/p&gt;

&lt;p&gt;Instead, there's a better way..&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Development Shell with Flakes
&lt;/h2&gt;

&lt;p&gt;Development shells (or shell environments as they're called in non-flake land) are a feature of Nix that allow you to all the things mentioned above — install specific versions of packages, set environment variables, run commands before the shell starts, etc.&lt;/p&gt;

&lt;p&gt;Let's start with the same &lt;code&gt;flake.nix&lt;/code&gt; as last time. You could copy it from &lt;a href="https://gitlab.com/arnu515-tutorials/nix/-/blob/master/a-simple-flake/flake.nix?ref_type=heads" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but I'd like to digress a bit by introducing Nix templates!&lt;/p&gt;

&lt;p&gt;Templates are a way for you to create, well template folders which are declared by a flake. Nix can then copy those files over from a flake into your current directory with the &lt;a href="https://nix.dev/manual/nix/2.24/command-ref/new-cli/nix3-flake-init" rel="noopener noreferrer"&gt;&lt;code&gt;nix flake init&lt;/code&gt;&lt;/a&gt; command. Nix makes templates declarative and versioned too!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Contrary to the command's reference manual, there's no need for the template itself to be a flake!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A template is pretty easy to create, you just need to add an attribute set called &lt;code&gt;templates&lt;/code&gt; in your flake's &lt;code&gt;outputs&lt;/code&gt;. The keys of the attrset are the template names, and the values are an attribute set with three fields: &lt;code&gt;description&lt;/code&gt;, a description of the template; &lt;code&gt;path&lt;/code&gt;, the path to the template folder; and &lt;code&gt;welcomeText&lt;/code&gt;, some text that is output when someone uses the template.&lt;/p&gt;

&lt;p&gt;I've made the simple flake from last article into a template hosted at this &lt;a href="https://github.com/arnu515-tutorials/dotfiles" rel="noopener noreferrer"&gt;article series' GitLab page&lt;/a&gt;, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="sx"&gt;https://gitlab.com/arnu515-tutorials/nix/-/blob/master/flake.nix&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;ref_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;heads&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&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="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="nv"&gt;templates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;simple&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A simple flake"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;./a-simple-flake&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;welcomeText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        Welcome to Nix!&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;        This flake exports a single package, `hello`, which can be&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        executed by running:&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;        $ nix run #.hello&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;        Check out my article series on Nix for more information!&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        https://dev.to/arnu515/my-new-nix-series-2cc3&lt;/span&gt;&lt;span class="err"&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="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;You can use a template by using the &lt;code&gt;nix flake init&lt;/code&gt; command. Beware that &lt;code&gt;nix flake init&lt;/code&gt; will copy the template's files to the current directory, but it will not overwrite existing files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix flake init &lt;span class="nt"&gt;--template&lt;/span&gt; gitlab:arnu515-tutorials/nix#simple 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note that Nix checks the fragment &lt;code&gt;simple&lt;/code&gt; (called a flake output name) starting from the &lt;code&gt;templates&lt;/code&gt; attrset, then the rest of the flake, unlike in &lt;code&gt;nix shell/run/build&lt;/code&gt;, which started from the &lt;code&gt;packages&lt;/code&gt; attrset, then moved to the &lt;code&gt;legacyPackages&lt;/code&gt; attrset, then the rest of the flake.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Creating a development shell&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now let's create the development shell. This will be created in the &lt;code&gt;devShells&lt;/code&gt; attrset, which looks just like the &lt;code&gt;packages&lt;/code&gt; attrset, i.e. it has another attrset inside it for each system (like &lt;code&gt;x86_64-linux&lt;/code&gt;), and those attrsets declare development shells inside them.&lt;/p&gt;

&lt;p&gt;Just like &lt;code&gt;packages&lt;/code&gt;, a development shell named &lt;code&gt;default&lt;/code&gt; is the default development shell.&lt;/p&gt;

&lt;p&gt;Here's a simple shell that has the &lt;code&gt;hello&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Nix devshells!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:nixos/nixpkgs/nixpkgs-24.11"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;nixpkgs&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="kd"&gt;let&lt;/span&gt;
    &lt;span class="nv"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"x86_64-linux"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;inherit&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;devShells&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;simple&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkShell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nv"&gt;shellHook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;hello&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/hello&lt;/span&gt;&lt;span class="err"&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="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;There's quite a few changes from earlier! First, the &lt;code&gt;hello&lt;/code&gt; package was removed, since we don't need it for this demonstration. Then, two variables called &lt;code&gt;system&lt;/code&gt; and &lt;code&gt;pkgs&lt;/code&gt; were created using the &lt;a href="https://nix.dev/tutorials/nix-language#let-in" rel="noopener noreferrer"&gt;&lt;code&gt;let ... in ...&lt;/code&gt; construct&lt;/a&gt;. Finally, we create a shell using the &lt;a href="https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell" rel="noopener noreferrer"&gt;&lt;code&gt;pkgs.mkShell&lt;/code&gt;&lt;/a&gt; function, specifying &lt;code&gt;packages&lt;/code&gt; to be put in path, and a &lt;code&gt;shellHook&lt;/code&gt; to run before the shell starts. Let's dissect this one-by-one.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;${...}&lt;/code&gt; syntax does string interpolation, replacing itself with the evaluated string inside the braces. &lt;code&gt;pkgs.hello&lt;/code&gt; evaluates to the nix store path of the &lt;code&gt;hello&lt;/code&gt; package. Thus, we need to append &lt;code&gt;/bin/hello&lt;/code&gt; to actually point to the &lt;code&gt;hello&lt;/code&gt; executable. Note that just &lt;code&gt;hello&lt;/code&gt; could also have been specified, since it will be available in &lt;code&gt;$PATH&lt;/code&gt;, but it is better to specify an absolute path like this so you know where your dependencies are coming from.&lt;/p&gt;

&lt;p&gt;Next, let's talk about what &lt;code&gt;pkgs = import nixpkgs { inherit system; };&lt;/code&gt; does. The &lt;a href="https://nix.dev/tutorials/nix-language#import" rel="noopener noreferrer"&gt;&lt;code&gt;import&lt;/code&gt; function&lt;/a&gt; takes in a path, and evaluates it if it is a path to a Nix file, or evaluates the &lt;code&gt;default.nix&lt;/code&gt; file within the directory if it is a path to a directory (which is the case when a flake reference, like &lt;code&gt;nixpkgs&lt;/code&gt; is passed to &lt;code&gt;import&lt;/code&gt;), and returns the evaluation. Since &lt;code&gt;nixpkgs&lt;/code&gt; points to the &lt;a href="https://github.com/nixos/nixpkgs" rel="noopener noreferrer"&gt;nixpkgs&lt;/a&gt; flake, which is a directory, it evaluates the &lt;code&gt;default.nix&lt;/code&gt; file present within the flake, which in the case of nixpkgs returns a function accepting, among others, an argument for the current &lt;code&gt;system&lt;/code&gt;, which is what we have passed to it. It then returns the set of packages for that system, which is what is bound to &lt;code&gt;pkgs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell" rel="noopener noreferrer"&gt;&lt;code&gt;pkgs.mkShell&lt;/code&gt;&lt;/a&gt; function creates a development shell / shell environment for us. A shell environment is actually just another derivation (that's why it is possible to run &lt;code&gt;nix develop&lt;/code&gt; on packages itself). In fact, &lt;code&gt;pkgs.mkShell&lt;/code&gt; is just a wrapper around &lt;code&gt;stdenv.mkDerivation&lt;/code&gt;, with some conveniences like specifying &lt;code&gt;packages&lt;/code&gt; instead of &lt;code&gt;nativeBuildInputs&lt;/code&gt;, and concatenating &lt;code&gt;shellHook&lt;/code&gt;s from all inputs, as visible in its &lt;a href="https://github.com/NixOS/nixpkgs/blob/nixos-24.11/pkgs/build-support/mkshell/default.nix" rel="noopener noreferrer"&gt;source code&lt;/a&gt;. There also exists a &lt;code&gt;pkgs.mkShellNoCC&lt;/code&gt;, which does the same as &lt;code&gt;pkgs.mkShell&lt;/code&gt;, but does not introduce a C compiler in the shell. This is useful if you're developing projects that do not need a C compiler installed.&lt;/p&gt;

&lt;p&gt;Let's enter this shell by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;nix develop .#simple
Hello, world!
&lt;span class="o"&gt;(&lt;/span&gt;nix:nix-shell-env&lt;span class="o"&gt;)&lt;/span&gt; bash-5.2&lt;span class="nv"&gt;$ &lt;/span&gt;which cc  &lt;span class="c"&gt;# a C compiler was added to thes shell by Nix&lt;/span&gt;
/nix/store/888bkaqdpfpx72dd8bdc69qsqlgbhcvf-gcc-wrapper-13.3.0/bin/cc
&lt;span class="o"&gt;(&lt;/span&gt;nix:nix-shell-env&lt;span class="o"&gt;)&lt;/span&gt; bash-5.2&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;which cc  &lt;span class="c"&gt;# different C compiler!&lt;/span&gt;
/usr/bin/cc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;With &lt;code&gt;nix develop&lt;/code&gt;, the flake output name is searched starting from &lt;code&gt;devShells&lt;/code&gt;, then &lt;code&gt;packages&lt;/code&gt;, then &lt;code&gt;legacyPackages&lt;/code&gt;, and finally the whole flake.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;An actual development environment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The earlier examples were really simple! Let's create an actual development environment with a &lt;em&gt;better&lt;/em&gt; shell, a NodeJS install, pnpm, and even a redis database!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Nix devshells!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:nixos/nixpkgs/nixos-24.11"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;nixpkgs&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="kd"&gt;let&lt;/span&gt;
    &lt;span class="nv"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"x86_64-linux"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;inherit&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c"&gt;# A custom config for valkey&lt;/span&gt;
    &lt;span class="c"&gt;# Declative configs!&lt;/span&gt;
    &lt;span class="nv"&gt;valkeyConfStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;requirepass super-strong-password&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;port 12345&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;# This special package writes the valkey configuration to&lt;/span&gt;
    &lt;span class="c"&gt;# a text file. For more such packages, see:&lt;/span&gt;
    &lt;span class="c"&gt;# https://nixos.org/manual/nixpkgs/stable/#trivial-builder-writeTextFile&lt;/span&gt;
    &lt;span class="c"&gt;#&lt;/span&gt;
    &lt;span class="c"&gt;# The package outputs the path to the file&lt;/span&gt;
    &lt;span class="nv"&gt;valkeyConf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;writeTextFile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"valkey.conf"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;valkeyConfStr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;devShells&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;system&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="nv"&gt;simple&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkShell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nv"&gt;shellHook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;hello&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/hello&lt;/span&gt;&lt;span class="err"&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="nv"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkShell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="c"&gt;# newer versions of redis are not packaged in&lt;/span&gt;
          &lt;span class="c"&gt;# nixpkgs, so we're using valkey, an open-source&lt;/span&gt;
          &lt;span class="c"&gt;# fork of redis maintained by the Linux Foundation&lt;/span&gt;
          &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;valkey&lt;/span&gt;

          &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nodejs_22&lt;/span&gt;
          &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nodePackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;pnpm&lt;/span&gt;

          &lt;span class="c"&gt;# the friendly interactive shell!&lt;/span&gt;
          &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fish&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="nv"&gt;shellHook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;valkey&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/valkey-server &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;valkeyConf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          exec fish&lt;/span&gt;&lt;span class="err"&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="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 &lt;code&gt;default&lt;/code&gt; shell in the above flake adds Valkey, NodeJS &lt;code&gt;22&lt;/code&gt;, the PNPM package manager, and the &lt;code&gt;fish&lt;/code&gt; shell to the environment. It also starts Valkey in the background through a shell hook, passing it a custom config (declared via Nix!) and runs &lt;code&gt;fish&lt;/code&gt; so we're dropped in the &lt;a href="https://fishshell.com/" rel="noopener noreferrer"&gt;&lt;code&gt;fish&lt;/code&gt; shell&lt;/a&gt; instead of our login shell.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;exec&lt;/code&gt; command replaces the currently running process with the command specified. If &lt;code&gt;fish&lt;/code&gt; was run without &lt;code&gt;exec&lt;/code&gt;, it would drop you into your login shell with the shell environment (or run any additional commands in the &lt;code&gt;shellHook&lt;/code&gt; if there were any) after you exit fish instead of exiting the devshell as expected. With &lt;code&gt;exec&lt;/code&gt;, when you exit &lt;code&gt;fish&lt;/code&gt;, it'll exit the environment too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's enter the shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;nix develop
155483:C 15 Jan 2025 15:56:27.317 &lt;span class="k"&gt;*&lt;/span&gt; oO0OoO0OoO0Oo Valkey is starting oO0OoO0OoO0Oo
155483:C 15 Jan 2025 15:56:27.317 &lt;span class="k"&gt;*&lt;/span&gt; Valkey &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8.0.1, &lt;span class="nv"&gt;bits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64, &lt;span class="nv"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;00000000, &lt;span class="nv"&gt;modified&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0, &lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;155483, just started
155483:C 15 Jan 2025 15:56:27.317 &lt;span class="k"&gt;*&lt;/span&gt; Configuration loaded
155483:M 15 Jan 2025 15:56:27.318 &lt;span class="k"&gt;*&lt;/span&gt; monotonic clock: POSIX clock_gettime
                .+^+.
            .+#########+.
        .+########+########+.           Valkey 8.0.1 &lt;span class="o"&gt;(&lt;/span&gt;00000000/0&lt;span class="o"&gt;)&lt;/span&gt; 64 bit
    .+########+&lt;span class="s1"&gt;'     '&lt;/span&gt;+########+.
 .########+&lt;span class="s1"&gt;'     .+.     '&lt;/span&gt;+########.    Running &lt;span class="k"&gt;in &lt;/span&gt;standalone mode
 |####+&lt;span class="s1"&gt;'     .+#######+.     '&lt;/span&gt;+####|    Port: 12345
 |###|   .+###############+.   |###|    PID: 155483
 |###|   |#####&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="c"&gt;#####|   |###|&lt;/span&gt;
 |###|   |####&lt;span class="s1"&gt;'  .-.  '&lt;/span&gt;&lt;span class="c"&gt;####|   |###|&lt;/span&gt;
 |###|   |###&lt;span class="o"&gt;(&lt;/span&gt;  &lt;span class="o"&gt;(&lt;/span&gt;@@@&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="c"&gt;###|   |###|          https://valkey.io&lt;/span&gt;
 |###|   |####.  &lt;span class="s1"&gt;'-'&lt;/span&gt;  .####|   |###|
 |###|   |#####&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;   .&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="c"&gt;#####|   |###|&lt;/span&gt;
 |###|   &lt;span class="s1"&gt;'+#####|   |#####+'&lt;/span&gt;   |###|
 |####+.     +##|   |#+&lt;span class="s1"&gt;'     .+####|
 '&lt;/span&gt;&lt;span class="c"&gt;#######+   |##|        .+########'&lt;/span&gt;
    &lt;span class="s1"&gt;'+###|   |##|    .+########+'&lt;/span&gt;
        &lt;span class="s1"&gt;'|   |####+########+'&lt;/span&gt;
             +#########+&lt;span class="s1"&gt;'
                '&lt;/span&gt;+v+&lt;span class="s1"&gt;'

155483:M 15 Jan 2025 15:56:27.320 * Server initialized
155483:M 15 Jan 2025 15:56:27.320 * Loading RDB produced by Valkey version 8.0.1
155483:M 15 Jan 2025 15:56:27.320 * RDB age 2934 seconds
155483:M 15 Jan 2025 15:56:27.320 * RDB memory usage when created 0.91 Mb
155483:M 15 Jan 2025 15:56:27.320 * Done loading RDB, keys loaded: 0, keys expired: 0.
155483:M 15 Jan 2025 15:56:27.320 * DB loaded from disk: 0.001 seconds
155483:M 15 Jan 2025 15:56:27.320 * Ready to accept connections tcp

&amp;gt; ps 
   PID TTY          TIME CMD
 153101 pts/3    00:00:00 fish  # this is login shell
 153205 pts/3    00:00:04 fish  # this is the fish process started by `nix develop`
 155483 pts/3    00:00:00 valkey-server  # valkey is running in the background
 155654 pts/3    00:00:00 ps
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you see that valkey has started in the background! To exit valkey, you need to kill it yourself, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;kill &lt;/span&gt;155483
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If valkey isn't killed, it'll continue running in the background, even &lt;em&gt;after&lt;/em&gt; you exit the devshell!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to kill valkey when you exit the shell, you can change your &lt;code&gt;shellHook&lt;/code&gt; to kill the valkey process on exit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;shellHook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;  &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;valkey&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/valkey-server &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;valkeyConf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;  fish&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;  echo Killing valkey server&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;  # `ps` lists all processes, `grep` searches for a line with `valkey-server`&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;  # in it. `awk` grabs the first column (the PID) from the output, and &lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;  # `xargs` sends stdin as arguments to `kill`.&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;  ps | grep valkey-server | awk "{printf \$1}" | xargs kill&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;  exit&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Improvement needed:&lt;/strong&gt; If you have another way to achieve this, then please let me know in the comments!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We can't use &lt;code&gt;exec&lt;/code&gt; anymore, since we need to execute commands after &lt;code&gt;fish&lt;/code&gt; exits. &lt;/p&gt;

&lt;p&gt;Now running &lt;code&gt;nix develop&lt;/code&gt; starts valkey, drops you into a shell, and when you exit the shell, valkey will automatically be killed!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pinning package versions:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This usually isn't required, since flakes are already pinned to exact commits via. the &lt;code&gt;flake.lock&lt;/code&gt; file, which &lt;em&gt;should&lt;/em&gt; be checked into version control. So even if you enter a development shell ten years from now, you'll have the same version of all packages as the &lt;code&gt;flake.lock&lt;/code&gt; (provided that GitHub still exists :P). &lt;/p&gt;

&lt;p&gt;To update package versions, you can run &lt;code&gt;nix flake update&lt;/code&gt;, which will fetch the latest commit of the branch of all your inputs.&lt;/p&gt;

&lt;p&gt;But if you still want a specific version of a package, down to the patch version, you can use a &lt;code&gt;nixpkgs&lt;/code&gt; input that has exactly that package. Do note that you &lt;em&gt;may have to build that package yourself&lt;/em&gt; if the Nix build cache doesn't have it, as is common with quite old versions of packages.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before doing that, do &lt;a href="https://search.nixos.org/packages" rel="noopener noreferrer"&gt;search nixpkgs&lt;/a&gt; for alternate versions of packages that may exist, for example, nixpkgs has node 18, 20, and 22 in it. Many popular packages are split into different nixpkgs for major versions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the disclaimers out of the way, let's learn how you can pin a specific version of a package. For this example, we'll add Node 16 (an end-of-life version that you totally &lt;strong&gt;shouldn't&lt;/strong&gt; be using) to the environment.&lt;/p&gt;

&lt;p&gt;First, we need to find a commit of the &lt;code&gt;nixpkgs&lt;/code&gt; repo that has Node 16. The latest commit should be enough. There are tools like &lt;a href="//https//nixhub.io"&gt;NixHub&lt;/a&gt; that help with exactly that. If you check the page for &lt;a href="https://www.nixhub.io/packages/nodejs" rel="noopener noreferrer"&gt;&lt;code&gt;nodejs&lt;/code&gt; on NixHub&lt;/a&gt;, you can see all the versions of node that were ever published on &lt;code&gt;nixpkgs&lt;/code&gt;. Copy the &lt;code&gt;nixpkgs&lt;/code&gt; commit for &lt;code&gt;nodejs&lt;/code&gt; &lt;code&gt;16.20.2&lt;/code&gt;, i.e. the part in "Nixpkgs Reference", except the flake output name, but do keep it in mind (&lt;code&gt;#nodejs_16&lt;/code&gt;), which happens to be &lt;code&gt;a71323f68d4377d12c04a5410e214495ec598d4c&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5dxuq4fk6geksz90mmnq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5dxuq4fk6geksz90mmnq.png" alt="A screenshot of NixHub for  raw `nodejs` endraw  version  raw `16.20.2` endraw , and the nixpkgs commit is highlighted with a black outline" width="800" height="103"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, add that to our environment like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Nix devshells!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:nixos/nixpkgs/nixos-24.11"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;# NEW&lt;/span&gt;
    &lt;span class="nv"&gt;nodejs_16_nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:nixos/nixpkgs/a71323f68d4377d12c04a5410e214495ec598d4c"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nodejs_16_nixpkgs&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="kd"&gt;let&lt;/span&gt;
    &lt;span class="nv"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"x86_64-linux"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;inherit&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c"&gt;# NEW&lt;/span&gt;
    &lt;span class="nv"&gt;nodejs_16&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nv"&gt;nodejs_16_nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;inherit&lt;/span&gt; &lt;span class="nv"&gt;system&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="nv"&gt;nodejs_16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;# ...&lt;/span&gt;
  &lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;devShells&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;system&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="c"&gt;# ...&lt;/span&gt;
      &lt;span class="nv"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkShell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;valkey&lt;/span&gt;

          &lt;span class="c"&gt;# NEW: replaced node 22 and pnpm with node 16&lt;/span&gt;
          &lt;span class="nv"&gt;nodejs_16&lt;/span&gt;

          &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fish&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="c"&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;Running &lt;code&gt;nix develop&lt;/code&gt; will actually give us an error claiming that this version of Node is end-of-life, which it is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;nix develop
error:
       … &lt;span class="k"&gt;while &lt;/span&gt;calling the &lt;span class="s1"&gt;'derivationStrict'&lt;/span&gt; &lt;span class="nb"&gt;builtin
         &lt;/span&gt;at &amp;lt;nix/derivation-internal.nix&amp;gt;:34:12:
           33|
           34|   strict &lt;span class="o"&gt;=&lt;/span&gt; derivationStrict drvAttrs&lt;span class="p"&gt;;&lt;/span&gt;
             |            ^
           35|

       … &lt;span class="k"&gt;while &lt;/span&gt;evaluating derivation &lt;span class="s1"&gt;'nix-shell'&lt;/span&gt;
         whose name attribute is located at /nix/store/2csx2kkb2hxyxhhmg2xs9jfyypikwwk6-source/pkgs/stdenv/generic/make-derivation.nix:336:7

       … &lt;span class="k"&gt;while &lt;/span&gt;evaluating attribute &lt;span class="s1"&gt;'nativeBuildInputs'&lt;/span&gt; of derivation &lt;span class="s1"&gt;'nix-shell'&lt;/span&gt;
         at /nix/store/2csx2kkb2hxyxhhmg2xs9jfyypikwwk6-source/pkgs/stdenv/generic/make-derivation.nix:380:7:
          379|       depsBuildBuild              &lt;span class="o"&gt;=&lt;/span&gt; elemAt &lt;span class="o"&gt;(&lt;/span&gt;elemAt dependencies 0&lt;span class="o"&gt;)&lt;/span&gt; 0&lt;span class="p"&gt;;&lt;/span&gt;
          380|       nativeBuildInputs           &lt;span class="o"&gt;=&lt;/span&gt; elemAt &lt;span class="o"&gt;(&lt;/span&gt;elemAt dependencies 0&lt;span class="o"&gt;)&lt;/span&gt; 1&lt;span class="p"&gt;;&lt;/span&gt;
             |       ^
          381|       depsBuildTarget             &lt;span class="o"&gt;=&lt;/span&gt; elemAt &lt;span class="o"&gt;(&lt;/span&gt;elemAt dependencies 0&lt;span class="o"&gt;)&lt;/span&gt; 2&lt;span class="p"&gt;;&lt;/span&gt;

       &lt;span class="o"&gt;(&lt;/span&gt;stack trace truncated&lt;span class="p"&gt;;&lt;/span&gt; use &lt;span class="s1"&gt;'--show-trace'&lt;/span&gt; to show the full, detailed trace&lt;span class="o"&gt;)&lt;/span&gt;

       error: Package ‘nodejs-16.20.2’ &lt;span class="k"&gt;in&lt;/span&gt; /nix/store/bxygxxbgcc7s82wn8a8wdp4gd34hiw4w-source/pkgs/development/web/nodejs/v16.nix:28 is marked as insecure, refusing to evaluate.


       Known issues:
        - This NodeJS release has reached its end of life. See https://nodejs.org/en/about/releases/.

       You can &lt;span class="nb"&gt;install &lt;/span&gt;it anyway by allowing this package, using the
       following methods:

       a&lt;span class="o"&gt;)&lt;/span&gt; To temporarily allow all insecure packages, you can use an environment
          variable &lt;span class="k"&gt;for &lt;/span&gt;a single invocation of the nix tools:

            &lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NIXPKGS_ALLOW_INSECURE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1

          Note: When using &lt;span class="sb"&gt;`&lt;/span&gt;nix shell&lt;span class="sb"&gt;`&lt;/span&gt;, &lt;span class="sb"&gt;`&lt;/span&gt;nix build&lt;span class="sb"&gt;`&lt;/span&gt;, &lt;span class="sb"&gt;`&lt;/span&gt;nix develop&lt;span class="sb"&gt;`&lt;/span&gt;, etc with a flake,
                &lt;span class="k"&gt;then &lt;/span&gt;pass &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nt"&gt;--impure&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;order to allow use of environment variables.

       b&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;nixos-rebuild&lt;span class="sb"&gt;`&lt;/span&gt; you can add ‘nodejs-16.20.2’ to
          &lt;span class="sb"&gt;`&lt;/span&gt;nixpkgs.config.permittedInsecurePackages&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;the configuration.nix,
          like so:

            &lt;span class="o"&gt;{&lt;/span&gt;
              nixpkgs.config.permittedInsecurePackages &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"nodejs-16.20.2"&lt;/span&gt;
              &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

       c&lt;span class="o"&gt;)&lt;/span&gt; For &lt;span class="sb"&gt;`&lt;/span&gt;nix-env&lt;span class="sb"&gt;`&lt;/span&gt;, &lt;span class="sb"&gt;`&lt;/span&gt;nix-build&lt;span class="sb"&gt;`&lt;/span&gt;, &lt;span class="sb"&gt;`&lt;/span&gt;nix-shell&lt;span class="sb"&gt;`&lt;/span&gt; or any other Nix &lt;span class="nb"&gt;command &lt;/span&gt;you can add
          ‘nodejs-16.20.2’ to &lt;span class="sb"&gt;`&lt;/span&gt;permittedInsecurePackages&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
          ~/.config/nixpkgs/config.nix, like so:

            &lt;span class="o"&gt;{&lt;/span&gt;
              permittedInsecurePackages &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"nodejs-16.20.2"&lt;/span&gt;
              &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fortunately, the error also provides an easy fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ NIXPKGS_ALLOW_INSECURE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 nix develop &lt;span class="nt"&gt;--impure&lt;/span&gt;
&lt;span class="c"&gt;# valkey output truncated ...&lt;/span&gt;
&lt;span class="c"&gt;# you can append &amp;gt; /dev/null to the valkey command in shellHook&lt;/span&gt;
&lt;span class="c"&gt;# to supress this output&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt;
v16.20.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success! You're now using an old, unsupported version of node!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Be warned! If you're reading this article in the future, and decide to use this version of NodeJS, then don't be surprised if you have to build NodeJS from scratch! (It's not a fast build, by the way).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Automatic shell environments with &lt;code&gt;direnv&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A devshell discussion with Nix is not complete without introducing &lt;a href="https://direnv.net" rel="noopener noreferrer"&gt;&lt;code&gt;direnv&lt;/code&gt;&lt;/a&gt;. &lt;code&gt;direnv&lt;/code&gt; is a tool that &lt;em&gt;very simply&lt;/em&gt; runs commands based on the current directory. It is tedious to run &lt;code&gt;nix develop&lt;/code&gt; to get into a development shell every time. It's also tedious to remember to exit the dev shell when you're done. &lt;code&gt;direnv&lt;/code&gt; automatically does this for you, so it's a valuable addition! It also allows you to use these shells within non-terminal editors like VSCode and JetBrains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Just a small fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's a small thing we need to change in &lt;code&gt;flake.nix&lt;/code&gt;. It's quite a bad idea to start Valkey every time the devshell is opened, since more than one instance of a dev shell can exist at the same time. Instead, wrap the valkey command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;# ...&lt;/span&gt;
  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nodejs_16_nixpkgs&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="kd"&gt;let&lt;/span&gt;
    &lt;span class="c"&gt;# ...&lt;/span&gt;

    &lt;span class="c"&gt;# NEW&lt;/span&gt;
    &lt;span class="nv"&gt;valkeyScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;writeShellScriptBin&lt;/span&gt;
      &lt;span class="s2"&gt;"start-valkey"&lt;/span&gt;
      &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;valkey&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/valkey-server &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;valkeyConf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;devShells&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;system&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="c"&gt;# ...&lt;/span&gt;
      &lt;span class="nv"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkShell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;valkey&lt;/span&gt;

          &lt;span class="nv"&gt;nodejs_16&lt;/span&gt;

          &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fish&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="nv"&gt;shellHook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          # NEW: Replace valkey startup and shutdown code with this&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          export PATH="&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;valkeyScript&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin:$PATH"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          # NEW: Add exec back&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          exec fish&lt;/span&gt;&lt;span class="err"&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="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;This change adds a shell script called &lt;code&gt;start-valkey&lt;/code&gt; to the environment, which runs valkey with the specified configuration. Now, to start valkey, you just run &lt;code&gt;start-valkey&lt;/code&gt;, and press &lt;code&gt;Ctrl-C&lt;/code&gt; to stop it. This allows us to have multiple shells in that environment now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install &lt;code&gt;direnv&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;direnv&lt;/code&gt; is packaged in most linux distros, but since this is a Nix guide after all, let's install it via. nix by running this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix profile &lt;span class="nb"&gt;install &lt;/span&gt;nixpkgs#direnv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You also need to &lt;a href="https://direnv.net/docs/hook.html" rel="noopener noreferrer"&gt;hook &lt;code&gt;direnv&lt;/code&gt; into your shell&lt;/a&gt;, since &lt;code&gt;direnv&lt;/code&gt; needs to know when you've changed directories. Follow the instructions on &lt;a href="https://direnv.net/docs/hook.html" rel="noopener noreferrer"&gt;this page&lt;/a&gt; for your shell. For &lt;code&gt;bash&lt;/code&gt;, you need to add &lt;code&gt;eval "$(direnv hook bash)"&lt;/code&gt; to the end of your &lt;code&gt;~/.bashrc&lt;/code&gt;, and restart your terminal after that.&lt;/p&gt;

&lt;p&gt;To tell &lt;code&gt;direnv&lt;/code&gt; we want it to start the development shell when we &lt;code&gt;cd&lt;/code&gt; into this folder, add an &lt;code&gt;.envrc&lt;/code&gt; in the folder, with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Need to set `stty sane` for fish&lt;/span&gt;
&lt;span class="c"&gt;# https://github.com/direnv/direnv/issues/967#issuecomment-1987134113&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$SHELL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ &lt;span class="s2"&gt;"fish"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;stty &lt;/span&gt;sane
&lt;span class="k"&gt;fi

&lt;/span&gt;use flake
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will make &lt;code&gt;direnv&lt;/code&gt; automatically enter your development environment. Just run &lt;code&gt;direnv allow&lt;/code&gt;, to allow executing this &lt;code&gt;.envrc&lt;/code&gt;, and you're automatically dropped into the development environment, with glorious Node 16 available!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're using &lt;code&gt;fish&lt;/code&gt; as your login shell, remove &lt;code&gt;exec fish&lt;/code&gt; from the &lt;code&gt;shellHook&lt;/code&gt; in the flake, otherwise &lt;code&gt;direnv&lt;/code&gt; will go on an infinite loop, since the &lt;code&gt;fish&lt;/code&gt; spawned by the devshell spawns &lt;code&gt;direnv&lt;/code&gt; which sees that it should enter the devshell, which spawns a &lt;code&gt;fish&lt;/code&gt;, which spawns a &lt;code&gt;direnv&lt;/code&gt;, ... and so on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Other alternatives to development shells built with Nix
&lt;/h2&gt;

&lt;p&gt;If writing a devshell on your own seems more complicated than necessary, you can use tools like &lt;a href="https://devenv.sh" rel="noopener noreferrer"&gt;Devenv&lt;/a&gt; or &lt;a href="https://jetify.com/devbox" rel="noopener noreferrer"&gt;Devbox&lt;/a&gt; (by the same team that built &lt;a href="https://nixhub.io" rel="noopener noreferrer"&gt;NixHub&lt;/a&gt;), which are both built on Nix. Devenv provides nice wrappers to automatically add languages, services (like postgres or redis), etc. on top of your flake, without having to do the shenanigans we had to do with Valkey. Devbox on the other hand, lets you skip writing Nix entirely, since they have their own CLI and lock file that pull packages from nixpkgs.&lt;/p&gt;

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

&lt;p&gt;You've now learned one of the most powerful features of nix! In the next few articles, we'll cover packaging for Nix!&lt;/p&gt;

&lt;p&gt;If you really liked this article and would like to support me, here are some ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sponsors/arnu515" rel="noopener noreferrer"&gt;GitHub Sponsors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://m.do.co/c/371591aa3027" rel="noopener noreferrer"&gt;Use my DigitalOcean referral link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.prisma.io/?via=arnu515" rel="noopener noreferrer"&gt;Use my Prisma referral link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you so much!&lt;/p&gt;

</description>
      <category>nix</category>
      <category>tutorial</category>
      <category>linux</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Getting started with Nix and Nix Flakes</title>
      <dc:creator>arnu515</dc:creator>
      <pubDate>Fri, 03 Jan 2025 12:26:17 +0000</pubDate>
      <link>https://dev.to/arnu515/getting-started-with-nix-and-nix-flakes-mml</link>
      <guid>https://dev.to/arnu515/getting-started-with-nix-and-nix-flakes-mml</guid>
      <description>&lt;p&gt;Let's get started with &lt;a href="https://nixos.org" rel="noopener noreferrer"&gt;Nix&lt;/a&gt;! This article guides you into setting up the Nix package manager, along with &lt;em&gt;flakes&lt;/em&gt;, and demonstrates some cool things it can do.&lt;/p&gt;

&lt;p&gt;For a quick intro to what Nix is, check out &lt;a href="https://dev.to/arnu515/my-new-nix-series-2cc3"&gt;this article I posted&lt;/a&gt;, which introduces Nix, and this series of articles to you. This article demonstrates an overview of the Nix package manager, more Nix concepts will be covered in future articles of this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Nix
&lt;/h2&gt;

&lt;p&gt;The Nix package manager can be installed on both Linux and Mac, and is also available as a &lt;a href="https://hub.docker.com/r/nixos/nix" rel="noopener noreferrer"&gt;Docker image&lt;/a&gt; for you to try out without installing it on bare metal. Windows users will have to use WSL2 to install Nix on their systems (if they're not using Docker).&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://nixos.org" rel="noopener noreferrer"&gt;Nix website&lt;/a&gt;'s &lt;a href="https://nixos.org/download" rel="noopener noreferrer"&gt;download page&lt;/a&gt; guides you into using their installer to install Nix on your system. However, this article will use [Determinate Systems' Nix installer] instead, since it lets you easily &lt;a href="https://zero-to-nix.com/start/uninstall" rel="noopener noreferrer"&gt;undo all changes&lt;/a&gt; their Nix installer makes (i.e. uninstall) with one command, and it also enables &lt;em&gt;Nix flakes&lt;/em&gt; by default, which you'd have to enable on your own if you were using the official Nix installer instead.&lt;/p&gt;

&lt;p&gt;Their guide, &lt;a href="https://zero-to-nix.com" rel="noopener noreferrer"&gt;Zero-to-Nix&lt;/a&gt; has &lt;a href="https://zero-to-nix.com/start/install/" rel="noopener noreferrer"&gt;detailed installation instructions&lt;/a&gt; using the Determinate installer, but in essence, it just boils down to running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;-sSf&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; https://install.determinate.systems/nix | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in a terminal and following the prompts the installer asks. You can check &lt;a href="https://zero-to-nix.com/start/install/" rel="noopener noreferrer"&gt;Zero-to-Nix's page&lt;/a&gt; for more instructions, or the &lt;a href="https://github.com/DeterminateSystems/nix-installer" rel="noopener noreferrer"&gt;Determinate installer's README&lt;/a&gt; for detailed options. This method will work both on Linux and Mac.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Obligatory disclaimer:&lt;/strong&gt; Nix flakes, and nix experimental commands (like &lt;code&gt;nix shell&lt;/code&gt; and &lt;code&gt;nix profile&lt;/code&gt;, which will be covered later) are experimental and may have breaking changes anytime in the future, hence, they are gated by a config option when using the default installer. But these two features have stayed experimental for many years with little-to-no breaking changes, and are thus considered &lt;em&gt;de facto&lt;/em&gt; stable by much of the Nix community, and &lt;em&gt;not&lt;/em&gt; using them is just giving yourself a handicap for no reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the official installer?
&lt;/h3&gt;

&lt;p&gt;If you've opted to use the &lt;a href="https://nixos.org/download" rel="noopener noreferrer"&gt;official Nix installer&lt;/a&gt; instead, you'll have to manually enable flakes for your user by editing &lt;code&gt;~/.config/nix/nix.conf&lt;/code&gt;, or for all users by editing &lt;code&gt;/etc/nix/nix.conf&lt;/code&gt; and adding the following line to the end of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;-&lt;span class="n"&gt;features&lt;/span&gt; = &lt;span class="n"&gt;nix&lt;/span&gt;-&lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="n"&gt;flakes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You'll have to create the file if it doesn't already exist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  No Systemd?
&lt;/h3&gt;

&lt;p&gt;If you're using a systemd-less distro, like &lt;a href="https://artixlinux.org" rel="noopener noreferrer"&gt;Artix&lt;/a&gt; or &lt;a href="https://voidlinux.org" rel="noopener noreferrer"&gt;Void&lt;/a&gt;, you can install Nix from their repositories, since it will come preconfigured with whatever init system you'd be using on those distros. The Determinate installer only works on distros with systemd (for a multi-user installation, which is what you want).&lt;/p&gt;

&lt;p&gt;If you wish, you can install Nix from your distro's repositories too, instead of using the Determinate installer. I've done this on Alpine, Artix, and Arch. Just make sure the Nix package is not extremely out of date, like in Void's case. You can check if the &lt;code&gt;nix&lt;/code&gt; package is out of date by comparing the version in your repos to &lt;a href="https://search.nixos.org/packages?channel=unstable&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=nix" rel="noopener noreferrer"&gt;the version on NixOS's repos&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Void linux's Nix package is very out-of-date (by 2-ish years!) as of the time of writing. You'll have to use the &lt;a href="https://nixos.org/manual/nix/stable/installation/single-user" rel="noopener noreferrer"&gt;Single-user installation&lt;/a&gt; mode of the &lt;a href="https://nixos.org/download/#nix-install-linux" rel="noopener noreferrer"&gt;official Nix installer&lt;/a&gt; to install Nix on void.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Using Nix packages
&lt;/h2&gt;

&lt;p&gt;Now that you have the Nix package manager installed, you can use it to install &lt;em&gt;any&lt;/em&gt; package from the &lt;a href="https://search.nixos.org/packages" rel="noopener noreferrer"&gt;vast library of 120k+ Nix packages&lt;/a&gt; on your system, while ensuring that none of your other packages, even those installed by Nix itself, will break due to dependency conflicts.&lt;/p&gt;

&lt;p&gt;Let's install the &lt;a href="https://github.com/busyloop/lolcat" rel="noopener noreferrer"&gt;lolcat&lt;/a&gt; package. Well, Nix actually allows us to try out the package &lt;em&gt;without installing it&lt;/em&gt; first! It downloads the package (or compiles it if the binary isn't availabe in the &lt;a href="https://cache.nixos.org" rel="noopener noreferrer"&gt;build cache&lt;/a&gt;) and drops you into a shell session containing the requested package in your &lt;code&gt;$PATH&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The below demonstrations will only have their intended effect if you &lt;em&gt;don't&lt;/em&gt; already have &lt;code&gt;lolcat&lt;/code&gt; installed!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's try it out! Run &lt;code&gt;nix shell nixpkgs#lolcat&lt;/code&gt; and Nix will download the latest commit of &lt;a href="https://github.com/nixos/nixpkgs" rel="noopener noreferrer"&gt;nixpkgs&lt;/a&gt;, a collection of lots of Nix packages, find the &lt;code&gt;lolcat&lt;/code&gt; package, get its binary from NixOS's &lt;a href="https://cache.nixos.org" rel="noopener noreferrer"&gt;binary cache&lt;/a&gt;, download it and put it in your &lt;code&gt;$PATH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nix will create you a new shell session where &lt;code&gt;lolcat&lt;/code&gt; will be available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcfvv0voppgar8qlkoktd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcfvv0voppgar8qlkoktd.png" alt="A screenshot of a shell session with two commands:  raw `nix shell nixpkgs#lolcat` endraw , which has no output, and  raw `echo " width="390" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨🪄 Magic! Open a new terminal window, try running &lt;code&gt;lolcat&lt;/code&gt;, and see that the command doesn't exist! Let's see what Nix added to our &lt;code&gt;$PATH&lt;/code&gt; to make &lt;code&gt;lolcat&lt;/code&gt; available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;nix shell nixpkgs#lolcat

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PATH&lt;/span&gt;
/nix/store/3mbkj2nlzf87aapwp1ckqrid21p9lb3j-lolcat-100.0.1/bin:... &lt;span class="c"&gt;# (truncated)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nix downloaded &lt;code&gt;lolcat&lt;/code&gt; and placed it in a folder in the Nix store (&lt;code&gt;/nix/store&lt;/code&gt; by default). The Nix store contains all packages, even multiple versions of the same package that were ever fetched by Nix. My system, about a month old, has close to 34k items in the store! Some of these are packages, some are built derivations (more on those later!). This can be cleaned using the &lt;code&gt;nix store gc&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;We'll learn more about the nix store in further articles, but for a quick rundown, the Nix store is a read-only filesystem which stores things like downloaded and built packages, any packages you create yourself, and anything else included in a nix package like downloaded or locally available source code. All packages are treated as a &lt;em&gt;pure function&lt;/em&gt;, and their built output is stored in the store.&lt;/p&gt;

&lt;p&gt;Notice the name of the directory where &lt;code&gt;lolcat&lt;/code&gt; was downloaded. It contains the hash of the derivation, ensuring integrity of the package, then the name of the package itself, and finally, the version, which in this case is &lt;code&gt;100.0.1&lt;/code&gt;. When you're trying out these commands for yourself, you may have a different hash and/or version of the package.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;lolcat&lt;/code&gt; is specified to the &lt;code&gt;nix shell&lt;/code&gt; command as &lt;code&gt;nixpkgs#lolcat&lt;/code&gt;. This is termed as an &lt;strong&gt;installable&lt;/strong&gt;, and in this case, is a &lt;strong&gt;flake reference&lt;/strong&gt; (more about that later). &lt;code&gt;nixpkgs#lolcat&lt;/code&gt; is actually a URL with path &lt;code&gt;nixpkgs&lt;/code&gt;, and fragment, i.e. the part after the &lt;code&gt;#&lt;/code&gt;, &lt;code&gt;lolcat&lt;/code&gt;. &lt;code&gt;nixpkgs&lt;/code&gt; is an alias which resolves to the &lt;a href="https://github.com/nixos/nixpkgs/tree/nixpkgs-unstable" rel="noopener noreferrer"&gt;&lt;code&gt;nixpkgs-unstable&lt;/code&gt;&lt;/a&gt; branch of the &lt;a href="https://github.com/nixos/nixpkgs" rel="noopener noreferrer"&gt;Nixpkgs GitHub repository&lt;/a&gt;, a vast collection of Nix packages. There are other stable branches of nixpkgs, like &lt;code&gt;nixpkgs-24.11&lt;/code&gt;, the latest one as of writing. There are quite a few other aliases too, Nix downloads the list from &lt;a href="https://channels.nixos.org/flake-registry.json" rel="noopener noreferrer"&gt;this JSON file&lt;/a&gt;. If you omit the URL host, it defaults to the current directory (&lt;code&gt;.&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;From the JSON file, the alias for &lt;code&gt;nixpkgs&lt;/code&gt; appears to be:&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;"from"&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="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nixpkgs"&lt;/span&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;"indirect"&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="nl"&gt;"to"&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="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NixOS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nixpkgs-unstable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"repo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nixpkgs"&lt;/span&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;"github"&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;The object at &lt;code&gt;"to"&lt;/code&gt; is one type of flake reference. It can also be written in a URL form like so: &lt;code&gt;github:NixOS/nixpkgs/nixpkgs-unstable&lt;/code&gt;. We'll take a look at various types of flake references later.&lt;/p&gt;

&lt;p&gt;The fragment of the URL, &lt;code&gt;lolcat&lt;/code&gt;, refers to one of the packages exported by the &lt;code&gt;nixpkgs&lt;/code&gt; flake. Omitting it defaults to the &lt;code&gt;default&lt;/code&gt; package, i.e., literally a package called &lt;code&gt;default&lt;/code&gt;. We'll talk more about flakes later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Actually installing &lt;code&gt;lolcat&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Having &lt;code&gt;lolcat&lt;/code&gt; in a local shell isn't really useful. Let's install &lt;code&gt;lolcat&lt;/code&gt; using Nix so that it can be accessed from any shell (that has the Nix profile in &lt;code&gt;$PATH&lt;/code&gt;!).&lt;/p&gt;

&lt;p&gt;A nix profile is a set of packages that are installed independently from each other. Nix profiles are versioned, so you can roll back to a previous state of your profile at any time!&lt;/p&gt;

&lt;p&gt;To install &lt;code&gt;lolcat&lt;/code&gt; into your profile, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix profile &lt;span class="nb"&gt;install &lt;/span&gt;nixpkgs#lolcat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;lolcat&lt;/code&gt; will be available in any shell! To remove it, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix profile remove lolcat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;lolcat&lt;/code&gt; will no longer be available in path, but it &lt;em&gt;will&lt;/em&gt; still remain in the Nix store. If you wish to use &lt;code&gt;lolcat&lt;/code&gt; again, maybe in a temporary shell, it will not have to be downloaded/built again, since it'll already be available in the Nix store.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice that this time we pass the package name only, and not a flake reference. To view a list of installed packages in your current profile, run &lt;code&gt;nix profile list&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, let's demonstrate profile versioning. Run &lt;code&gt;nix profile history&lt;/code&gt; to see the versions of your profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;nix profile &lt;span class="nb"&gt;history
&lt;/span&gt;Version 1 &lt;span class="o"&gt;(&lt;/span&gt;2025-01-03&lt;span class="o"&gt;)&lt;/span&gt;:
  flake:nixpkgs#legacyPackages.x86_64-linux.lolcat: ∅ -&amp;gt; 100.0.1

Version 2 &lt;span class="o"&gt;(&lt;/span&gt;2025-01-03&lt;span class="o"&gt;)&lt;/span&gt; &amp;lt;- 1:
  flake:nixpkgs#legacyPackages.x86_64-linux.lolcat: 100.0.1 -&amp;gt; ∅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;2 will be green, since it is the current version of the profile.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Don't worry about &lt;code&gt;flake:nixpkgs#legacyPackages.x86_64-linux.lolcat&lt;/code&gt; for now, that'll be covered later. Just notice that the first history version installed &lt;code&gt;lolcat&lt;/code&gt; &lt;code&gt;100.0.1&lt;/code&gt;, and the second history version removed it. Let's roll back to the first version with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix profile rollback &lt;span class="nt"&gt;--to&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;lolcat&lt;/code&gt; is back! If we make a change while we're in this version, Nix'll create a new version for us, without deleting version &lt;code&gt;2&lt;/code&gt;, so you can roll back to any point! For now, let's stick to the same slate, and go back to version &lt;code&gt;2&lt;/code&gt;. I'd leave it as an exercise for you to do the same.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you wish to learn more about these commands, you can append &lt;code&gt;--help&lt;/code&gt; to see a nicely formatted colored manual.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Nix language basics
&lt;/h2&gt;

&lt;p&gt;This article will not teach you the Nix language, since there are much better places to learn that from, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learnxinyminutes.com/nix/" rel="noopener noreferrer"&gt;https://learnxinyminutes.com/nix/&lt;/a&gt; (concise)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nix.dev/tutorials/nix-language" rel="noopener noreferrer"&gt;https://nix.dev/tutorials/nix-language&lt;/a&gt; (official)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nixcloud.io/tour/?id=introduction/nix" rel="noopener noreferrer"&gt;https://nixcloud.io/tour/?id=introduction/nix&lt;/a&gt; (interactive)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nix.dev/manual/nix/2.24/language/" rel="noopener noreferrer"&gt;https://nix.dev/manual/nix/2.24/language/&lt;/a&gt; (the full reference)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is &lt;strong&gt;highly recommended&lt;/strong&gt; to learn this language before proceeding to the next section. If you know the language, you'll not be troubled with the syntax when you make your own flakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  An introduction to flakes
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nix.dev/concepts/flakes" rel="noopener noreferrer"&gt;Nix Flakes&lt;/a&gt; are an opinionated way to structure a nix expression made up of packages, OS configurations, development shells, modules, images, overlays, etc. If you've read the &lt;a href="https://dev.to/arnu515/my-new-nix-series-2cc3"&gt;series introduction&lt;/a&gt;, you'll know that every &lt;code&gt;.nix&lt;/code&gt; file is just a Nix expression.&lt;/p&gt;

&lt;p&gt;Before flakes were a thing, you'd have to create separate nix files for development shells (&lt;code&gt;shell.nix&lt;/code&gt;), packages (&lt;code&gt;default.nix&lt;/code&gt;), OS configurations (&lt;code&gt;configuration.nix&lt;/code&gt;), etc., which may get annoying, but is just a small hindrance. The real advantage of flakes is flake inputs, which let you easily fetch nix packages from anywhere using many methods ("fetchers"), and the &lt;code&gt;nix&lt;/code&gt; experimental commands, which are built to work with flakes.&lt;/p&gt;

&lt;p&gt;When you create a &lt;code&gt;flake.nix&lt;/code&gt; file in a directory, that directory becomes a flake, so it can be used as a path in the URL passed to &lt;code&gt;nix&lt;/code&gt; commands. Open an empty directory on your computer, and create a blank file called &lt;code&gt;flake.nix&lt;/code&gt; in it. That directory has now become a flake!&lt;/p&gt;

&lt;p&gt;We can run the simplest &lt;code&gt;nix&lt;/code&gt; command to verify that, &lt;code&gt;nix eval&lt;/code&gt; evaluates a Nix expression and prints it to stdout. Without any arguments, it &lt;em&gt;evaluates&lt;/em&gt; the &lt;strong&gt;default package&lt;/strong&gt; exported by the flake. Notice the emphasis on &lt;em&gt;evaluates&lt;/em&gt;, not &lt;em&gt;runs&lt;/em&gt; or &lt;em&gt;installs&lt;/em&gt;, i.e. the command just prints the package &lt;a href="https://nix.dev/tutorials/nix-language.html#derivations" rel="noopener noreferrer"&gt;derivation&lt;/a&gt; out to the screen. Some things are &lt;em&gt;lazily evaluated&lt;/em&gt;, meaning they aren't evaluated until they're required. A package in a flake is not evaluated when you're just using the flake's development shell. It is only evaluated when you use the package, i.e. when you install it, or use it in another flake.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;nix eval&lt;/code&gt; on an empty &lt;code&gt;flake.nix&lt;/code&gt; however, will give you a syntax error, well obviously, since an empty file is not a valid nix expression. A flake is an &lt;a href="https://nix.dev/tutorials/nix-language.html#attribute-set" rel="noopener noreferrer"&gt;attrset&lt;/a&gt; which has a few fields as described &lt;a href="https://nix.dev/manual/nix/2.24/command-ref/new-cli/nix3-flake.html#flake-format" rel="noopener noreferrer"&gt;here&lt;/a&gt;, notably &lt;code&gt;inputs&lt;/code&gt;, a set of &lt;em&gt;other flakes&lt;/em&gt; your flake uses, and &lt;code&gt;outputs&lt;/code&gt;, a function returning a set of things your flake exposes (which can be used by other flakes!). The declared flakes in &lt;code&gt;inputs&lt;/code&gt; will be fetched by Nix, evaluated (lazily), and passed to the &lt;code&gt;outputs&lt;/code&gt; function, along with a special parameter &lt;code&gt;self&lt;/code&gt;, which is just a reference to the set returned by &lt;code&gt;outputs&lt;/code&gt;, so you can reference your own flake in itself. The power of lazy evaluation!&lt;/p&gt;

&lt;p&gt;Let's start with a simple &lt;code&gt;"Hello, world!"&lt;/code&gt; flake. Create &lt;code&gt;flake.nix&lt;/code&gt; in an empty directory on the machine you installed Nix in, and write the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"My first flake!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:nixos/nixpkgs/nixpkgs-24.11"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;nixpkgs&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;x86_64-linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;legacyPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;x86_64-linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;hello&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;This is how a nix flake looks like. It looks quite a lot like JSON, but with semicolons instead of commas, but don't be fooled, Nix is a proper functional language with programming constructs and everything! The &lt;code&gt;inputs&lt;/code&gt; attribute set (attrset) declares a set of flakes your flake depends on. In this case, this flake depends on the &lt;code&gt;nixpkgs&lt;/code&gt; flake, a collection of 120k+ nix packages. The &lt;code&gt;nixpkgs&lt;/code&gt; flake's source code is hosted on GitHub at &lt;a href="https://github.com/nixos/nixpkgs" rel="noopener noreferrer"&gt;nixos/nixpkgs&lt;/a&gt;, which is what is specified in the flake input. The &lt;code&gt;nixpkgs-24.11&lt;/code&gt; part after the last &lt;code&gt;/&lt;/code&gt; is the branch of the repository. Nixpkgs releases a stable branch every six months, in May (&lt;code&gt;05&lt;/code&gt;), and November (&lt;code&gt;11&lt;/code&gt;, which is what we're using) every year. You can use &lt;code&gt;nixpkgs-unstable&lt;/code&gt; if you want the latest packages instead. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;outputs&lt;/code&gt; function shows one such construct. Nix attrsets can be condensed with dots, so &lt;code&gt;nixpkgs.url = "foo";&lt;/code&gt; is actually &lt;code&gt;nixpkgs = { url = "foo"; };&lt;/code&gt;, similarly with &lt;code&gt;packages.aarch64_linux.hello&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The function syntax of Nix is: &lt;code&gt;param: returned_expr&lt;/code&gt;. Nix functions can take in only one parameter, so multiple parameter functions are actually multiple functions with one parameter each, like so: &lt;code&gt;param1: param2: ...: returned_expr&lt;/code&gt;. Nix has support for attrset destructuring, so &lt;code&gt;{nixpkgs, ...}&lt;/code&gt; means that the parameter to &lt;code&gt;outputs&lt;/code&gt; is actually an attrset, and we extract the property &lt;code&gt;nixpkgs&lt;/code&gt;, which matches the input property from it. The &lt;code&gt;...&lt;/code&gt; means to ignore any extra properties passed. If it isn't specified, nix will error if properties other than &lt;code&gt;nixpkgs&lt;/code&gt; are passed! We know that &lt;code&gt;self&lt;/code&gt; is another property passed to output, hence &lt;code&gt;...&lt;/code&gt; is needed.&lt;/p&gt;

&lt;p&gt;Finally, in the outputs, we define a single package named &lt;code&gt;hello&lt;/code&gt; for 64-bit linux systems (which is what I have), which is just set to the &lt;code&gt;hello&lt;/code&gt; package declared by &lt;code&gt;nixpkgs&lt;/code&gt; for 64-bit linux systems. Note that the &lt;code&gt;legacy&lt;/code&gt; in &lt;code&gt;legacyPackages&lt;/code&gt; doesn't actually mean that these packages are legacy (see &lt;a href="https://github.com/NixOS/nixpkgs/blob/b2e41a5bd20d4114f27fe8d96e84db06b841d035/flake.nix#L47-L55" rel="noopener noreferrer"&gt;this&lt;/a&gt; for an explanation [TLDR; &lt;code&gt;legacyPackages&lt;/code&gt; makes the &lt;code&gt;nix flake show&lt;/code&gt; command not evaluate the package, apart from that, they're functionally identical]). You should change &lt;code&gt;x86_64&lt;/code&gt; and &lt;code&gt;linux&lt;/code&gt; to your own system's architecture and OS, if they differ from these.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For example, an M-series Mac would be &lt;code&gt;aarch64-darwin&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now running &lt;code&gt;nix eval&lt;/code&gt; tells us that there's no default package, which is true, since we created a package named &lt;code&gt;hello&lt;/code&gt;, not one named &lt;code&gt;default&lt;/code&gt;. We could rename &lt;code&gt;hello&lt;/code&gt; to &lt;code&gt;default&lt;/code&gt;, or we could also ask &lt;code&gt;nix eval&lt;/code&gt; to evaluate the &lt;code&gt;hello&lt;/code&gt; program, since it takes an &lt;code&gt;installable&lt;/code&gt; as an argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;nix &lt;span class="nb"&gt;eval&lt;/span&gt; .#hello
«derivation /nix/store/83p9zy4d8lh5fnipz7d1hl7g3rryw6mx-hello-2.12.1.drv»
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;.&lt;/code&gt; is technically optional, but if it is omitted, bash treats &lt;code&gt;#hello&lt;/code&gt; as a comment, so you'll have to quote it. I prefer putting a leading &lt;code&gt;.&lt;/code&gt; instead of quoting the whole string.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We get a derivation! Nix saw that our flake exports the &lt;code&gt;hello&lt;/code&gt; package or legacyPackage, saw that it is supposed to fetch the &lt;code&gt;hello&lt;/code&gt; package from the &lt;code&gt;nixpkgs&lt;/code&gt; flakes, fetches that flake if reqiured, and returns the derivation to us.&lt;/p&gt;

&lt;p&gt;We can also build and run this package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;nix build .#hello
&lt;span class="c"&gt;# no output&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;./result/bin/hello
Hello, world!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;hello&lt;/code&gt; executable is actually GNU hello, that in true GNU fashion is an overly complicated program with a &lt;a href="https://www.gnu.org/software/hello/manual/hello.pdf" rel="noopener noreferrer"&gt;seventeen page manual&lt;/a&gt; that prints something to the screen, defaulting to &lt;code&gt;Hello, world!&lt;/code&gt;. Nix would build this program using its derivation from scratch, if it wasn't available in the &lt;a href="https://cache.nixos.org/" rel="noopener noreferrer"&gt;build cache&lt;/a&gt;, which most packages usually are, so it downloads the program from there instead. The derivation and built (or downloaded) output is stored in the Nix store, as we saw earlier. The &lt;code&gt;result&lt;/code&gt; symlink also links to a folder in the store. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;result&lt;/code&gt; folder is actually a symlink, which links to the built folder in the nix store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;readlink &lt;/span&gt;result
/nix/store/ccs8597k5ji5h7ad94wfr329xcxydbla-hello-2.12.1
&lt;span class="nv"&gt;$ &lt;/span&gt;tree result/
result/
|-- bin/
    |-- hello
|-- share/
    ...
|-- man/
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This was a super quick introduction to the Nix package manager and nix flakes. Stay tuned for more articles in the series, the next one lined up is about development shells with flakes. You wouldn't want to miss this one!&lt;/p&gt;

&lt;p&gt;If you really liked this article and would like to support me, here are some ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sponsors/arnu515" rel="noopener noreferrer"&gt;GitHub Sponsors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://m.do.co/c/371591aa3027" rel="noopener noreferrer"&gt;Use my DigitalOcean referral link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.prisma.io/?via=arnu515" rel="noopener noreferrer"&gt;Use my Prisma referral link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you so much!&lt;/p&gt;

</description>
      <category>nix</category>
      <category>linux</category>
      <category>raspberrypi</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>My new Nix series!</title>
      <dc:creator>arnu515</dc:creator>
      <pubDate>Fri, 27 Dec 2024 09:11:34 +0000</pubDate>
      <link>https://dev.to/arnu515/my-new-nix-series-2cc3</link>
      <guid>https://dev.to/arnu515/my-new-nix-series-2cc3</guid>
      <description>&lt;p&gt;This is a (work-in-progress) series of articles on the &lt;a href="https://nix.dev" rel="noopener noreferrer"&gt;Nix ecosystem&lt;/a&gt;. Some things planned for the near future are development environments with Nix and &lt;a href="https://direnv.net" rel="noopener noreferrer"&gt;direnv&lt;/a&gt;, many &lt;a href="https://nixos.org" rel="noopener noreferrer"&gt;NixOS&lt;/a&gt; related things, and much more. Stay tuned by giving me a follow for when those come out!&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick introduction to Nix
&lt;/h2&gt;

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

&lt;p&gt;From the &lt;a href="https://nixos.org" rel="noopener noreferrer"&gt;NixOS website&lt;/a&gt;, Nix is a tool that takes a unique approach to package management and system configuration. This "unique approach" refers to building packages in isolation from each other, ensuring that if packages work on one machine, they will work on another. This is done in a &lt;em&gt;declarative&lt;/em&gt; fashion, essentially meaning that you tell Nix what to do, and it does it for you, without you having to tell Nix &lt;em&gt;how&lt;/em&gt; to do it.&lt;/p&gt;

&lt;p&gt;This is done using the &lt;em&gt;nix&lt;/em&gt; language, which is a &lt;em&gt;purely&lt;/em&gt; functional programming language purpose-built to configure the Nix package manager. The language is simple, dynamically typed, and purely functional. You'll pick it up quite easily, but if you want some formal learning, you can check out the &lt;a href="https://nix.dev/tutorials/nix-language" rel="noopener noreferrer"&gt;Nix Language Basics&lt;/a&gt; chapter of the &lt;a href="https://nix.dev" rel="noopener noreferrer"&gt;Nix documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's a very simple &lt;code&gt;"Hello, world!"&lt;/code&gt; in Nix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save it as &lt;code&gt;hello.nix&lt;/code&gt; and run it with the command (if you have Nix installed) &lt;code&gt;nix eval --file hello.nix&lt;/code&gt; to see &lt;code&gt;Hello, world!&lt;/code&gt; output to your terminal!&lt;/p&gt;

&lt;p&gt;This works because every &lt;code&gt;.nix&lt;/code&gt; file contains a nix expression, which are functions, lists, attribute sets (dictionaries), numbers, paths, strings, etc. The &lt;code&gt;nix eval&lt;/code&gt; command evaluates the expression and prints it to the screen.&lt;/p&gt;

&lt;p&gt;The Nix language is used to create packages for the nix ecosystem. Nix packages, as described earlier, are built in isolation from each other, ensuring that there is no &lt;a href="https://en.wikipedia.org/wiki/Dependency_hell" rel="noopener noreferrer"&gt;dependency hell&lt;/a&gt; and allows multiple versions of a package to seamlessly coexist at the same time. Nix packages are &lt;a href="https://nix.dev/manual/nix/2.24/language/derivations" rel="noopener noreferrer"&gt;derivations&lt;/a&gt;: "a specification for running an executable on precisely defined input  files to repeatably produce output files at uniquely determined file  system paths". Nix also provides useful &lt;a href="https://nix.dev/manual/nix/stable/language/builtins.html" rel="noopener noreferrer"&gt;builtin functions&lt;/a&gt; and a &lt;a href="https://nixos.org/manual/nixpkgs/stable/#part-stdenv" rel="noopener noreferrer"&gt;standard environment&lt;/a&gt; to make packaging easier. These packages can then be shared, like any other file, and built by other people without any hassle, since Nix guarantees reproducibility. A collection of 120,000+ packages is available in the &lt;a href="https://github.com/nixos/nixpkgs" rel="noopener noreferrer"&gt;nixpkgs&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;Atop all this is the Nix Operating System, or NixOS. NixOS brings all of the declarative goodness of Nix into a Linux distribution. This means that (almost) everything about your system is declarative, including the packages (obviously), the users, the desktop, the login manager, systemd units, containers, and among other things, even the bootloader! Through some extensions, you can also &lt;a href="https://github.com/nix-community/disko" rel="noopener noreferrer"&gt;partition disks&lt;/a&gt;, &lt;a href="https://github.com/nix-community/nixos-generators" rel="noopener noreferrer"&gt;build images&lt;/a&gt;, and even &lt;a href="https://github.com/nix-community/home-manager" rel="noopener noreferrer"&gt;configure your home folder&lt;/a&gt;. A single NixOS configuration is built into multiple pre-configured operating systems using just a single command!&lt;/p&gt;

&lt;h2&gt;
  
  
  Planned Articles
&lt;/h2&gt;

&lt;p&gt;You can see a very crude list of planned articles for this series on &lt;a href="https://gitlab.com/arnu515-tutorials/nix/-/boards/9016066" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt;. The GitLab repository will also contain any source code and large code snippets (not inlined) used in the series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support me
&lt;/h2&gt;

&lt;p&gt;Here are some ways you can support me on this journey:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sponsors/arnu515" rel="noopener noreferrer"&gt;GitHub Sponsors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://m.do.co/c/371591aa3027" rel="noopener noreferrer"&gt;Use my DigitalOcean referral link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.prisma.io/?via=arnu515" rel="noopener noreferrer"&gt;Use my Prisma referral link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you so much!&lt;/p&gt;

&lt;p&gt;This upcoming series will explore a lot of what Nix can do, and what its &lt;a href="https://github.com/nix-community" rel="noopener noreferrer"&gt;amazing community&lt;/a&gt; has enabled it to do, so stay tuned for more articles in the near future!&lt;/p&gt;

</description>
      <category>nix</category>
      <category>linux</category>
      <category>devops</category>
    </item>
    <item>
      <title>The one thing I do not like about the Nix package manager (and a fix for it)</title>
      <dc:creator>arnu515</dc:creator>
      <pubDate>Tue, 16 Jan 2024 16:51:41 +0000</pubDate>
      <link>https://dev.to/arnu515/the-one-thing-i-do-not-like-about-the-nix-package-manager-and-a-fix-for-it-33ln</link>
      <guid>https://dev.to/arnu515/the-one-thing-i-do-not-like-about-the-nix-package-manager-and-a-fix-for-it-33ln</guid>
      <description>&lt;p&gt;The &lt;a href="https://nixos.org"&gt;nix&lt;/a&gt; package manager is an awesome package manager for linux and macos, which focuses on declarative packages. This means that you can dump out all the packages you want into a file, and nix will go out and fetch them for you.&lt;/p&gt;

&lt;p&gt;This package manager builds itself on the concept of reproducibility, and it boasts a collection of over 80,000 packages, second only to the AUR! But this blog post is not about why nix-os is great, you'll find many other &lt;a href="https://serokell.io/blog/what-is-nix"&gt;blog posts&lt;/a&gt; and &lt;a href="https://www.youtube.com/channel/UC_zBdZ0_H_jn41FDRG7q4Tw"&gt;videos&lt;/a&gt; if you want to learn why.&lt;/p&gt;

&lt;p&gt;Many nix package definitions include compiling from source. This can be tedious, since compiling takes a long time and wastes more bandwidth downloading build dependencies. Hence, the nix team has a binary cache, in which they build binaries for all the systems that nix supports for most of the common packages out there, like web browsers, desktop environments, and many more.&lt;/p&gt;

&lt;p&gt;But what if your desired package is not in the binary cache? And what if it takes a long time to compile? This is what happened to me, and this is the one thing I don't like about nix.&lt;/p&gt;

&lt;p&gt;The AUR provides binary packages, alongside source packages, for example, &lt;a href="https://aur.archlinux.org/packages/yay"&gt;yay&lt;/a&gt; and &lt;a href="https://aur.archlinux.org/packages/yay-bin"&gt;yay-bin&lt;/a&gt;, but nix only provides source packages for most of its packages. Granted you'll see some exceptions, like &lt;a href="https://search.nixos.org/packages?channel=23.11&amp;amp;show=firefox&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages"&gt;firefox&lt;/a&gt; and &lt;a href="https://search.nixos.org/packages?channel=23.11&amp;amp;show=firefox-bin&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages"&gt;firefox-bin&lt;/a&gt;, but those are pretty rare.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;In this article, I'll show you how you can create a binary package for your desired program. I wanted to download the &lt;a href="https://surrealdb.com/"&gt;SurrealDB&lt;/a&gt; package, but the &lt;a href="https://search.nixos.org/packages?channel=unstable&amp;amp;show=surrealdb&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=surrealdb"&gt;package on nix&lt;/a&gt; was a source package, meaning that I had to spend over 50 minutes waiting for a stupid package to compile.&lt;/p&gt;

&lt;p&gt;Surreal provides binaries over at its &lt;a href="https://github.com/surrealdb/surrealdb/releases"&gt;GitHub releases&lt;/a&gt;, which I could've downloaded and ran, but I'd have to manually update the package, and as a 10x developer (I'm not one), I'd never manually do anything, but spend 10x the time trying to automate it. Doing this also defeats the reproducibility of nix, hence it's better to create a nix package so that anyone would be able to download your dependencies without having to do anything extra.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting set up
&lt;/h2&gt;

&lt;p&gt;First, make sure you have in hand a name for your package, and its version. Generally, binary packages end with &lt;code&gt;-bin&lt;/code&gt;, so I'll call my package &lt;code&gt;surrealdb-bin&lt;/code&gt;, and I'll be downloading the binary for the latest version as of the time of writing, which is &lt;code&gt;v1.1.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since I use NixOS, and since I want to install &lt;code&gt;surrealdb&lt;/code&gt; globally, I'll create my &lt;code&gt;surrealdb&lt;/code&gt; package in &lt;code&gt;/etc/nixos&lt;/code&gt;, which is the folder which holds my &lt;code&gt;configuration.nix&lt;/code&gt;. You may choose to co-locate your package in the directory with your &lt;code&gt;flake.nix&lt;/code&gt;, for example, or even push it to GitHub/Lab so you can make your nix config truly reproducible.&lt;/p&gt;

&lt;p&gt;This approach will contain two &lt;code&gt;nix&lt;/code&gt; files, one for the package itself, and another for an overlay. An &lt;a href="https://nixos.wiki/wiki/Overlays"&gt;overlay&lt;/a&gt; gives you the ability to modify &lt;code&gt;nixpkgs&lt;/code&gt; without having to publish your package to the &lt;code&gt;nixpkgs&lt;/code&gt; repository, and without having to mess around with &lt;code&gt;inputs&lt;/code&gt;. Overlays make it very easy to use your package.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the package
&lt;/h3&gt;

&lt;p&gt;Generally, I put my custom packages in the &lt;code&gt;packages/&lt;/code&gt; subfolder, and custom overlays in the &lt;code&gt;overlays/&lt;/code&gt; subfolder, and I name both these files with the name of the package, in this case &lt;code&gt;surrealdb-bin.nix&lt;/code&gt;. I'd recommend you follow the same strategy to avoid clutter, but if you're only going to create one package, you can just keep everything in one directory.&lt;/p&gt;

&lt;p&gt;I shall refer to &lt;code&gt;packages/surrealdb-bin.nix&lt;/code&gt; as the package declaration. Now, put this code in your package declaration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;fetchzip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;autoPatchelfHook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;glibc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;gcc-unwrapped&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="kr"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PACKAGE_NAME"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PACKAGE_VERSION"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;fetchzip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PATH_TO_YOUR_PACKAGE'S_TARBALL_HERE"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A_HASH_OF_YOUR_PACKAGE'S_CONTENTS"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;autoPatchelfHook&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nv"&gt;glibc&lt;/span&gt; &lt;span class="nv"&gt;gcc-unwrapped&lt;/span&gt;
    &lt;span class="c"&gt;# Any other system binaries your app may need&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nv"&gt;installPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    runHook preInstall&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    mkdir -p $out/bin&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    install -m755 PACKAGE_BINARY_FILE_NAME $out/bin&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    runHook postInstall&lt;/span&gt;&lt;span class="err"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go over this code step-by-step. If you're familiar with nix syntax, you'll recognise that we're creating a function which destructures its first argument to accept some fields, and returns the output of the function &lt;code&gt;stdenv.mkDerivation&lt;/code&gt;. &lt;code&gt;mkDerivation&lt;/code&gt; is the helper function that you use to define &lt;code&gt;nix&lt;/code&gt; packages. We give it an argument which defines the basic metadata of our package. Here are the fields it defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;pname&lt;/code&gt;:&lt;/strong&gt; This is the name of the package. You could also use &lt;code&gt;name&lt;/code&gt;, but you'll have to also specify the package's version in the &lt;code&gt;name&lt;/code&gt;. Using &lt;code&gt;pname&lt;/code&gt; makes nix automatically generate the &lt;code&gt;name&lt;/code&gt; for us.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;version&lt;/code&gt;:&lt;/strong&gt; This is the version of your package. Make sure you're fetching the correct binary!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;src&lt;/code&gt;:&lt;/strong&gt; Now here's the meat of the package. We use nix's &lt;a href="https://ryantm.github.io/nixpkgs/builders/fetchers/"&gt;fetchers&lt;/a&gt; to fetch our package's binary from the internet. There are two mainly used fetchers for binary packages: &lt;code&gt;fetchurl&lt;/code&gt; and &lt;code&gt;fetchzip&lt;/code&gt;. Let's take a closer look at them. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;fetchurl&lt;/code&gt;:&lt;/strong&gt; This fetcher directly downloads the file provided to it by the &lt;code&gt;url&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;fetchzip&lt;/code&gt;:&lt;/strong&gt; This file downloads the archive provided to it by the &lt;code&gt;url&lt;/code&gt; field, and unarchives it. The fetcher may be named &lt;code&gt;fetchzip&lt;/code&gt;, but it also works for other archives like &lt;code&gt;.tar.gz&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I'll be using the &lt;code&gt;fetchzip&lt;/code&gt; fetcher to download the tarball of the correct SurrealDB version using a direct link pointing to its GitHub release. You can use nix's string interpolation to generate a proper link. This is what mine would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/surrealdb/surrealdb/releases/download/v${version}/surreal-v${version}.linux-amd64.tgz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll replace &lt;code&gt;PATH_TO_YOUR_PACKAGE'S_TARBALL_HERE&lt;/code&gt; with the above link.&lt;/p&gt;

&lt;p&gt;Now for the &lt;code&gt;hash&lt;/code&gt; field. You must specify a checksum hash for the binary that you're downloading so that nix can verify that the correct file is being downloaded. The easiest way to set this value is to set &lt;code&gt;hash&lt;/code&gt; to something random, install the package, and set the hash to whatever it says in the error that gets thrown.&lt;/p&gt;

&lt;p&gt;If you'd like to write &lt;code&gt;hash&lt;/code&gt; yourself, the syntax for it will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hashingAlgorithm-base64encodedhash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many hashing algorithms are supported, but the most commonly used ones are &lt;code&gt;md5&lt;/code&gt; and &lt;code&gt;sha256&lt;/code&gt;. Make sure to &lt;code&gt;base64&lt;/code&gt; encode your hash value, since &lt;code&gt;nix&lt;/code&gt; only accepts that! Don't give it a &lt;code&gt;hex&lt;/code&gt; string.&lt;/p&gt;

&lt;p&gt;Now let's get back to our package derivation. The rest of the code is instructing &lt;code&gt;patchelf&lt;/code&gt; to automatically patch the binary to make it run with nix. Since nix doesn't work like other package managers, binaries which expect shared libraries to be at one particular location will not work, hence we need to use &lt;code&gt;patchelf&lt;/code&gt; to update these locations. Thankfully we won't have to manually run &lt;code&gt;patchelf&lt;/code&gt;, since nix provides us with the &lt;code&gt;autoPatchelf&lt;/code&gt; package. This package is defined in the &lt;code&gt;nativeBuildInputs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The additional libraries which the package depends on must be specified in the &lt;code&gt;buildInputs&lt;/code&gt; array. The best way to find this out, would again be to install the package and add the dependencies it lists out in the error, but generally the two packages I've included should suffice.&lt;/p&gt;

&lt;p&gt;Finally, in the &lt;code&gt;installPhase&lt;/code&gt;, we define a shell script that runs. The &lt;code&gt;hooks&lt;/code&gt; that are called are all related to &lt;code&gt;patchelf&lt;/code&gt;, so just make sure they're present in your code. In between the two hook calls, we write code to create a &lt;code&gt;bin/&lt;/code&gt; directory in our package's nix-store path, and we transfer any binaries the package provides to the &lt;code&gt;bin/&lt;/code&gt; folder. Do update the &lt;code&gt;PACKAGE_BINARY_FILE_NAME&lt;/code&gt; variable with the name of the binary that gets downloaded by the fetcher. In my case, that'd be &lt;code&gt;surreal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;install&lt;/code&gt; command is actually just some syntactic sugar for the &lt;code&gt;cp&lt;/code&gt; command, they both have the same function. The only extra thing is the &lt;code&gt;-m&lt;/code&gt; flag, which sets &lt;code&gt;chmod&lt;/code&gt; permissions on the binary to make it readable, writable and executable by the user (the &lt;code&gt;7&lt;/code&gt;), and only readable and executable by the user's group and everyone else (the two &lt;code&gt;5&lt;/code&gt;s).&lt;/p&gt;

&lt;p&gt;Finally, this is how your package derivation should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/nixos/packages/surrealdb-bin.nix &lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;fetchzip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;autoPatchelfHook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;glibc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;gcc-unwrapped&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="kr"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"surrealdb-bin"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.1.0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;fetchzip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/surrealdb/surrealdb/releases/download/v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/surreal-v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.linux-amd64.tgz"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2611de5eb7779dfe3b32bb47833fee2e3e168e39e43d76b47ea649b2f8c407fa"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;autoPatchelfHook&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;glibc&lt;/span&gt; &lt;span class="nv"&gt;gcc-unwrapped&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nv"&gt;installPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    runHook preInstall&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    mkdir -p $out/bin&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    install -m755 surreal $out/bin&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    runHook postInstall&lt;/span&gt;&lt;span class="err"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating the overlay
&lt;/h3&gt;

&lt;p&gt;Now we need to create an overlay to be able to add our package to the existing list of &lt;code&gt;nixpkgs&lt;/code&gt;. I'll call my overlay derivation &lt;code&gt;surrealdb-bin.nix&lt;/code&gt;, and place it in the &lt;code&gt;overlays/&lt;/code&gt; folder in the &lt;code&gt;/ext/nixos&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;If you're using a different path than me, be sure to update the relevant imports.&lt;/p&gt;

&lt;p&gt;Add these lines to your overlay definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;super&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;surrealdb-bin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;callPackage&lt;/span&gt; &lt;span class="sx"&gt;../packages/surrealdb-bin.nix&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;Now this may seem a little scarier, if you're new to the &lt;code&gt;nix&lt;/code&gt; language. Since nix functions can only accept one argument, we use nested functions to declare multiple arguments. &lt;code&gt;self&lt;/code&gt; and &lt;code&gt;super&lt;/code&gt;, as they're commonly called, or more recently &lt;code&gt;final&lt;/code&gt; and &lt;code&gt;prev&lt;/code&gt;, are both instances of &lt;code&gt;nixpkgs&lt;/code&gt;. It's just that &lt;code&gt;super&lt;/code&gt;/&lt;code&gt;prev&lt;/code&gt; is the version of &lt;code&gt;nixpkgs&lt;/code&gt; before the overlay is applied, and &lt;code&gt;self&lt;/code&gt;/&lt;code&gt;final&lt;/code&gt; is the version of &lt;code&gt;nixpkgs&lt;/code&gt; after all overlays are applied.&lt;/p&gt;

&lt;p&gt;You should generally only use the &lt;code&gt;super&lt;/code&gt; argument. Read &lt;a href="https://nixos.wiki/wiki/Overlays"&gt;the wiki&lt;/a&gt; if you want to learn more about overlays.&lt;/p&gt;

&lt;p&gt;The overlay functions return type is a set of packages which will be merged into &lt;code&gt;nixpkgs&lt;/code&gt;. Here you can see that I'm defining one package called &lt;code&gt;surrealdb-bin&lt;/code&gt;, and I'm calling the &lt;code&gt;super.callPackage&lt;/code&gt; function and giving it the location of my &lt;code&gt;surrealdb-bin&lt;/code&gt; package derivation as the argument. The &lt;code&gt;callPackage&lt;/code&gt; function ensures that the package derivation is called with the proper arguments supplied. The blank argument set at the end is just to specify that I don't want to extend the list of arguments passed to the package derivation any further.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the overlay
&lt;/h3&gt;

&lt;p&gt;The only thing left to do now, is to actually use the overlay and extend the &lt;code&gt;nixpkgs&lt;/code&gt; definition. Now this depends on where you plan to use the overlay, i.e. in a &lt;code&gt;shell.nix&lt;/code&gt;, &lt;code&gt;flake.nix&lt;/code&gt;, the nixos &lt;code&gt;configuration.nix&lt;/code&gt;, or in a home-manager configuration. The steps are different for all of those.&lt;/p&gt;

&lt;p&gt;Check &lt;a href="https://nixos.wiki/wiki/Overlays#Applying_overlays_manually"&gt;the wiki&lt;/a&gt; for instructions pertaining to your specific use case. Since I want to install &lt;code&gt;surreal&lt;/code&gt; globally, I'll add the overlay to my &lt;code&gt;configuration.nix&lt;/code&gt;. All I have to do is add the below line somewhere, and then add &lt;code&gt;surrealdb-bin&lt;/code&gt; to the list of environment/user packages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;overlays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="sx"&gt;./overlays/surrealdb-bin.nix&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;blockquote&gt;
&lt;p&gt;Don't forget to update the path to the overlay if you're using a different path!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And with a small &lt;code&gt;nixos-rebuild switch&lt;/code&gt;, I have access to the &lt;code&gt;surreal&lt;/code&gt; command in my environment! Great success! Now if there's a new surreal version published, all I have to do is change the &lt;code&gt;version&lt;/code&gt; in my package derivation and rebuild!&lt;/p&gt;

&lt;p&gt;You should push your package derivation and overlay to a centralised place like GitHub, GitLab, or any other place that provides direct download links (GitHub Gist / GitLab snippets would be a great place), so that you can download this overlay in any nix configuration and maintain reproducibility, instead of possibly having to duplicate your overlay and maintain two sources of truths.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if I don't have access to binaries
&lt;/h2&gt;

&lt;p&gt;If your package only has the source available, with no binaries, and still takes a long time to compile, then you should probably leave the compilation to a GitHub action.&lt;/p&gt;

&lt;p&gt;Just create a GitHub repository, create an action to build the package, and then upload it as an artifact/release once done.&lt;/p&gt;

&lt;p&gt;Then use Nix to fetch that artifact in your package.&lt;/p&gt;

&lt;p&gt;Do note that GitHub-provided hosted action runners have a maximum runtime of 6 hours, so if you have a package that takes longer than that (e.g. a web browser), then you need to use self-hosted runners, which have a max runtime of 35 days!&lt;/p&gt;

&lt;p&gt;It is risky to host a self-hosted runner on your own machine, since your machine may go down, and you'll have to restart the build process again&lt;/p&gt;

&lt;p&gt;Instead it'd be best to use a cloud VPS service like &lt;a href="https://m.do.co/c/371591aa3027"&gt;Digital Ocean&lt;/a&gt;. If you use that link, or &lt;a href="https://m.do.co/c/371591aa3027"&gt;this one&lt;/a&gt;, you can get &lt;code&gt;$200&lt;/code&gt; in credit for upto 60 days, that's literally giving you free access to a 16GiB ram + 8 vCPU instance to build all your hopes and dreams on! (You will have to request access to these machines first though).&lt;/p&gt;

&lt;p&gt;If you use &lt;a href="https://m.do.co/c/371591aa3027"&gt;my link&lt;/a&gt;, I also get some credits, so it helps me out too! Thanks for using &lt;a href="https://m.do.co/c/371591aa3027"&gt;my link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Anyway, custom GitHub actions are out of scope of this article, but do let me know if you want me to create another article on that topic in the comments below!&lt;/p&gt;

</description>
      <category>nix</category>
      <category>linux</category>
      <category>tutorial</category>
      <category>packages</category>
    </item>
    <item>
      <title>Create a Reddit clone with RedwoodJS</title>
      <dc:creator>arnu515</dc:creator>
      <pubDate>Sun, 20 Feb 2022 14:41:46 +0000</pubDate>
      <link>https://dev.to/arnu515/create-a-reddit-clone-with-redwoodjs-pcp</link>
      <guid>https://dev.to/arnu515/create-a-reddit-clone-with-redwoodjs-pcp</guid>
      <description>&lt;p&gt;Redwood is an opinionated full-stack javascript web application framework. It is also serverless-ready, meaning it can be deployed &lt;em&gt;on the edge&lt;/em&gt; with services like AWS Lambda and Cloudflare Workers. Redwood is &lt;em&gt;super&lt;/em&gt; opinionated. It decides your project/directory structure, it decides the frameworks and libraries you use, and it configures everything for you. Some may see a downside to this, but if you're experienced with Redwood's choosing of frameworks, you will have a pleasant time using Redwood.&lt;/p&gt;

&lt;p&gt;Redwood was created by &lt;a href="https://github.com/mojombo" rel="noopener noreferrer"&gt;Tom Preston-Werner&lt;/a&gt;. You may have heard of him before, because he is the guy behind&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com" rel="noopener noreferrer"&gt;Github&lt;/a&gt;, which is the most popular code host&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jekyllrb.com" rel="noopener noreferrer"&gt;Jekyll&lt;/a&gt;, a ruby-based static-site generator&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gravatar.com" rel="noopener noreferrer"&gt;Gravatar&lt;/a&gt;, a very popular avatar service&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://semver.org" rel="noopener noreferrer"&gt;Semver&lt;/a&gt;, the semantic versioning system&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://toml.io" rel="noopener noreferrer"&gt;TOML&lt;/a&gt;, a configuration language, like JSON or YAML, and much more.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Redwood uses &lt;a href="https://reactjs.org" rel="noopener noreferrer"&gt;React&lt;/a&gt; for the frontend framework, so you'll need to know React.&lt;/li&gt;
&lt;li&gt;Redwood uses &lt;a href="https://graphql.org" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt; instead of REST APIs, so knowledge of that is &lt;strong&gt;required&lt;/strong&gt;. You can learn it on the &lt;a href="https://graphql.org/learn/" rel="noopener noreferrer"&gt;official website&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Redwood uses &lt;a href="https://prisma.io" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt; for interacting with databases, but it's very easy to use, and you can pick it up from this tutorial itself. Prisma works with SQL databases.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://postgresql.org" rel="noopener noreferrer"&gt;Postgres&lt;/a&gt; database running. You can either have the Postgres server installed, or use &lt;a href="https://docker.com" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;. I'll be doing the latter in this tutorial.&lt;/li&gt;
&lt;li&gt;There are various other libraries used like &lt;a href="https://jestjs.io" rel="noopener noreferrer"&gt;Jest&lt;/a&gt; and &lt;a href="https://storybook.js.org" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt;, but these are not needed to follow this tutorial.&lt;/li&gt;
&lt;li&gt;I'll be using &lt;a href="https://typescriptlang.org" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt; in this tutorial, but feel free to use plain JavaScript. Just be sure to remove any code that is TypeScript-specific.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are also a few things you'll need installed on your computer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;NodeJS&lt;/a&gt; v14 or higher. I'll be using v16. (Psst: For an easy way to manage versions of NodeJS and many others, try &lt;a href="https://asdf-vm.com" rel="noopener noreferrer"&gt;https://asdf-vm.com&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://yarnpkg.com" rel="noopener noreferrer"&gt;Yarn&lt;/a&gt; Package Manager installed. Redwood leverages yarn workspaces, so yarn is needed. You can install it using &lt;code&gt;npm i -g yarn&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A powerful code editor like &lt;a href="https://code.visualstudio.com" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt; or (Neo)Vim. If you're using VSCode, be sure to install the Redwood extension.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 0 — Creating your Redwood app
&lt;/h2&gt;

&lt;p&gt;Open an empty folder in your favorite IDE and run the below command in the terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn create redwood-app &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're like me however, and you've fallen in love with &lt;a href="https://typescriptlang.org" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt;, you can create a Redwood typescript app by adding the &lt;code&gt;--typescript&lt;/code&gt; flag to the above command like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn create &lt;span class="nt"&gt;--typescript&lt;/span&gt; redwood-app &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you want to convert an existing Redwood project to TypeScript, you can run &lt;code&gt;yarn rw setup tsconfig&lt;/code&gt; and change your &lt;code&gt;.js&lt;/code&gt; files to &lt;code&gt;.ts&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now open the project in your favorite IDE. I'll use VSCode in this tutorial, since Redwood has first-class support for it. Launch the editor and open the folder, or just run &lt;code&gt;code .&lt;/code&gt; in the terminal.&lt;/p&gt;

&lt;p&gt;You may be prompted to install recommended extensions, so feel free to install them all, or just some if you don't need certain extensions (like Gitlens, in my case).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Getting to know your project
&lt;/h2&gt;

&lt;p&gt;Let's take a look at the project structure.&lt;/p&gt;

&lt;p&gt;There are a few files in the root project. Most of them are configuration files, like &lt;code&gt;jest.config.js&lt;/code&gt;. Let's take a look at a specific file called &lt;code&gt;redwood.toml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[web]&lt;/span&gt;
  &lt;span class="py"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Redwood App"&lt;/span&gt;
  &lt;span class="py"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8910&lt;/span&gt;
  &lt;span class="py"&gt;apiUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/.redwood/functions"&lt;/span&gt; &lt;span class="c"&gt;# you can customise graphql and dbauth urls individually too: see https://redwoodjs.com/docs/app-configuration-redwood-toml#api-paths&lt;/span&gt;
  &lt;span class="py"&gt;includeEnvironmentVariables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="c"&gt;# any ENV vars that should be available to the web side, see https://redwoodjs.com/docs/environment-variables#web&lt;/span&gt;
&lt;span class="nn"&gt;[api]&lt;/span&gt;
  &lt;span class="py"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8911&lt;/span&gt;
&lt;span class="nn"&gt;[browser]&lt;/span&gt;
  &lt;span class="py"&gt;open&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Redwood recommends so many extensions, but not a TOML one! Install &lt;a href="https://marketplace.visualstudio.com/items?itemName=bungcip.better-toml" rel="noopener noreferrer"&gt;this extension&lt;/a&gt; for VSCode for TOML highlighting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're unfamiliar with TOML syntax, don't worry, I'll guide you through the config. For now, let's change the &lt;code&gt;port&lt;/code&gt; of both the &lt;code&gt;[web]&lt;/code&gt; and &lt;code&gt;[api]&lt;/code&gt; projects to &lt;code&gt;3000&lt;/code&gt; and &lt;code&gt;5000&lt;/code&gt; respectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where your code lives
&lt;/h3&gt;

&lt;p&gt;There are many directories, but the main two directories are &lt;code&gt;web&lt;/code&gt;, and &lt;code&gt;api&lt;/code&gt;. This is where your Redwood code lives. As the name suggests, &lt;code&gt;web&lt;/code&gt; is the frontend project, and &lt;code&gt;api&lt;/code&gt; is the backend project.&lt;/p&gt;

&lt;p&gt;These two directories are actually their own projects, and Redwood uses Yarn Workspaces to link these two folders together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inside the &lt;code&gt;web&lt;/code&gt; folder&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FAS7I7Lc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FAS7I7Lc.png" alt="Screenshot of the subfolders inside the web folder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;web&lt;/code&gt; folder is a regular ol' React application. If you know react, you should be able to read through the structure of this directory. There are just a few things that are different. In the &lt;code&gt;src&lt;/code&gt; folder, you can see three more subfolders, &lt;code&gt;components&lt;/code&gt;, &lt;code&gt;layouts&lt;/code&gt; and &lt;code&gt;pages&lt;/code&gt;. The &lt;code&gt;components&lt;/code&gt; folder holds any re-usable React components. The &lt;code&gt;layouts&lt;/code&gt; folder holds page layouts, which are also React components, and the &lt;code&gt;pages&lt;/code&gt; folder, which contains React components mapped to routes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is a &lt;code&gt;.keep&lt;/code&gt; file?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;.keep&lt;/code&gt; files are just files that are placed in empty directories so they get committed to a git project. Git doesn't commit empty folders, so &lt;code&gt;.keep&lt;/code&gt; files are created to make the folder &lt;em&gt;not empty&lt;/em&gt;, and hence, get it committed. These &lt;code&gt;.keep&lt;/code&gt; files don't even have to be called &lt;code&gt;.keep&lt;/code&gt;, they can be called anything else, but by convention, they're called &lt;code&gt;.keep&lt;/code&gt; and &lt;code&gt;.gitkeep&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Inside the &lt;code&gt;api&lt;/code&gt; folder&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F1h7HAko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F1h7HAko.png" alt="Screenshot of the folders inside the api folder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;api&lt;/code&gt; folder is the backend server. This is running &lt;code&gt;fastify&lt;/code&gt; under the hood, which is just a faster backend server than express. There are a few config files, and there are three subdirectories.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;db&lt;/code&gt; folder contains the &lt;code&gt;schema.prisma&lt;/code&gt; file, which is the schema for your database models that is used by Prisma ORM.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;src&lt;/code&gt; folder contains all of your source code for the backend.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;From the &lt;a href="https://learn.redwoodjs.com/docs/tutorial/redwood-file-structure" rel="noopener noreferrer"&gt;redwood documentation&lt;/a&gt;: &lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;directives&lt;/code&gt; will contain GraphQL &lt;a href="https://www.graphql-tools.com/docs/schema-directives" rel="noopener noreferrer"&gt;schema directives&lt;/a&gt; for controlling access to queries and transforming values.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;functions&lt;/code&gt; will contain any &lt;a href="https://docs.netlify.com/functions/overview/" rel="noopener noreferrer"&gt;lambda functions&lt;/a&gt; your app needs in addition to the &lt;code&gt;graphql.js&lt;/code&gt; file auto-generated by Redwood. This file is required to use the GraphQL API.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;graphql&lt;/code&gt; contains your GraphQL schema written in a Schema Definition Language (the files will end in &lt;code&gt;.sdl.js&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib&lt;/code&gt; contains a few files:&lt;code&gt;auth.js&lt;/code&gt; starts as a placeholder for adding auth functionality and has a couple of bare-bones functions in it to start, &lt;code&gt;db.js&lt;/code&gt; instantiates the Prisma database client so we can talk to a database and &lt;code&gt;logger.js&lt;/code&gt; which configures, well, logging. You can use this directory for other code related to the API side that doesn't really belong anywhere else.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;services&lt;/code&gt; contains business logic related to your data. When you're querying or mutating data for GraphQL (known as &lt;strong&gt;resolvers&lt;/strong&gt;), that code ends up here, but in a format that's reusable in other places in your application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Start the server&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Let's start the server by running the below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn rw dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see your application running on &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;, or &lt;a href="http://localhost:8911" rel="noopener noreferrer"&gt;http://localhost:8911&lt;/a&gt;, if you didn't change the port in the config. The backend will run on port &lt;code&gt;5000&lt;/code&gt;, or &lt;code&gt;8910&lt;/code&gt; if you didn't change the port in the config.&lt;/p&gt;

&lt;p&gt;If this is what you see, you've successfully created your redwood project!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F1iBwiDg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F1iBwiDg.png" alt="Screenshot of localhost:3000"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Replacing SQLLite with Postgres&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;SQLLite is the default database used by Redwood, since it doesn't expect everyone to have a fully-fledged database installed and running on their computer. But SQLLite is a file-system based database, and it also lacks in features when compared to Postgres. A file-system based database isn't the best for production, so let's switch over to Postgres.&lt;/p&gt;

&lt;p&gt;Postgres needs to be installed on your computer. You can download it and install it, and have a system-wide install of postgres, or you can use &lt;a href="https://docker.com" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; to &lt;em&gt;containerize&lt;/em&gt; it, which is easier to do. You'll need docker installed, however, and you can get it from &lt;a href="https://www.docker.com/get-started" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once docker is running, you can create a &lt;code&gt;postgres&lt;/code&gt; container using the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5432:5432 &lt;span class="nt"&gt;--name&lt;/span&gt; postgres &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_STRONG_PASSWORD postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Be sure to replace &lt;code&gt;YOUR_STRONG_PASSWORD&lt;/code&gt; to a strong password, since that will be the password of your root account in postgres.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The above command will run the &lt;a href="https://hub.docker.com/_/postgres" rel="noopener noreferrer"&gt;&lt;code&gt;postgres&lt;/code&gt;&lt;/a&gt; image as a container, with the name &lt;code&gt;postgres&lt;/code&gt; (with the &lt;code&gt;--name&lt;/code&gt; flag), adds the environment variable &lt;code&gt;POSTGRES_PASSWORD&lt;/code&gt; to it (with the &lt;code&gt;-e&lt;/code&gt; flag), exposes port &lt;code&gt;5432&lt;/code&gt; (postgres' default port) back to the host (with the &lt;code&gt;-p&lt;/code&gt; flag) and finally, it runs it in the background with the &lt;code&gt;-d&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;Now, create a new database in the fresh postgres container you just created. Run the below command to get &lt;em&gt;shell&lt;/em&gt; access to the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; postgres bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your &lt;em&gt;shell prompt&lt;/em&gt; changed, you now have the ability to run commands directly in the postgres container! Now run the below command to create a new database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;createdb &lt;span class="nt"&gt;-U&lt;/span&gt; postgres NAME_OF_YOUR_DATABASE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-U postgres&lt;/code&gt; flag makes it run as the &lt;code&gt;postgres&lt;/code&gt; user, which is the default root user. Change &lt;code&gt;NAME_OF_YOUR_DATABASE&lt;/code&gt; to anything you want. In my case, I changed it to &lt;code&gt;reddit&lt;/code&gt;, which means that a new database with the name &lt;code&gt;reddit&lt;/code&gt; has been created for me. Once that's done, exit out of the shell by typing &lt;code&gt;exit&lt;/code&gt; and hitting Enter.&lt;/p&gt;

&lt;p&gt;Now that you have a postgres database, you just need to tell Prisma to use it. Open the &lt;code&gt;.env&lt;/code&gt; file in the project root and add the below code to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL=postgres://postgres:YOUR_STRONG_PASSWORD@localhost:5432/YOUR_DATABASE_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be sure to replace &lt;code&gt;YOUR_STRONG_PASSWORD&lt;/code&gt; and &lt;code&gt;YOUR_DATABASE_NAME&lt;/code&gt; with the relevant values. And finally, change the line that says &lt;code&gt;provider = "sqlite"&lt;/code&gt; to &lt;code&gt;provider = "postgresql"&lt;/code&gt; in the &lt;code&gt;api/db/schema.prisma&lt;/code&gt;.&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="err"&gt;datasource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;db&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;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="err"&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="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="err"&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;h2&gt;
  
  
  Step 2 — Creating Prisma models
&lt;/h2&gt;

&lt;p&gt;Prisma models are definitions for how your database tables will look like. They are written in prisma's own model language in the &lt;code&gt;schema.prisma&lt;/code&gt; file. If you're not familiar with this syntax, don't fear, since it looks similar to GraphQL syntax, and I'll guide you with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the User model
&lt;/h3&gt;

&lt;p&gt;Open the &lt;code&gt;src/db/schema.prisma&lt;/code&gt; file in the &lt;code&gt;api&lt;/code&gt; project. Let's delete the example &lt;code&gt;UserExample&lt;/code&gt; project, and replace it with our own User model.&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="err"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;User&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;id&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cuid&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@unique&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@unique&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;avatarUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;avatar_url&lt;/span&gt;&lt;span class="err"&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;roles&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="err"&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;bio&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;isBanned&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;is_banned&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="err"&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;blockquote&gt;
&lt;p&gt;If you can't see any syntax highlighting, be sure to install the Prisma extension for VSCode.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What the above Prisma code does, is it creates a model named &lt;code&gt;User&lt;/code&gt;. A Prisma model is mapped to a table in the database, which in this case will be &lt;code&gt;users&lt;/code&gt;, because of the &lt;code&gt;@@map("users")&lt;/code&gt;. These are the fields that will be created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;code&gt;id&lt;/code&gt; filed, which will be the primary key (denoted by &lt;code&gt;@id&lt;/code&gt;). It will be a String with the &lt;code&gt;VarChar&lt;/code&gt; datatype in Postgres. Since &lt;code&gt;VarChar&lt;/code&gt; isn't supported by all databases Prisma supports (like MongoDB), we have to use &lt;code&gt;@db.VarChar&lt;/code&gt; instead of directly declaring it as a &lt;code&gt;VarChar&lt;/code&gt; type. The &lt;code&gt;id&lt;/code&gt; will also be a generated &lt;code&gt;CUID&lt;/code&gt; by default. A CUID is a randomly-generated string, like a UUID.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;username&lt;/code&gt; and an &lt;code&gt;email&lt;/code&gt; field, both of which are &lt;code&gt;String&lt;/code&gt;s and are &lt;code&gt;unique&lt;/code&gt;, meaning no two users can have the same &lt;code&gt;email&lt;/code&gt; or &lt;code&gt;username&lt;/code&gt;. By default, a &lt;code&gt;String&lt;/code&gt; will be mapped to Postgres' &lt;code&gt;Text&lt;/code&gt; datatype.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;password&lt;/code&gt;, which is a &lt;code&gt;String&lt;/code&gt; in Prisma, but a &lt;code&gt;VarChar&lt;/code&gt; in Postgres&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;avatarUrl&lt;/code&gt;, which is a &lt;code&gt;String&lt;/code&gt;. This will be accessed in JavaScript with &lt;code&gt;avatarUrl&lt;/code&gt;, but will be stored in the database as &lt;code&gt;avatar_url&lt;/code&gt;, because of &lt;code&gt;@map&lt;/code&gt;. I did this because Postgres follows &lt;code&gt;snake_casing&lt;/code&gt;, while JavaScript follows &lt;code&gt;camelCasing&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;roles&lt;/code&gt;, which is a &lt;code&gt;String&lt;/code&gt;, which will contain a comma-separated string of roles. You could use an array here, but I feel like that would be overkill for a field that would usually only have one role. Also &lt;code&gt;member&lt;/code&gt; is the default.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;bio&lt;/code&gt;, which is an &lt;em&gt;optional&lt;/em&gt; string (&lt;code&gt;nullable&lt;/code&gt;, in database lingo). This is indicated by the &lt;code&gt;?&lt;/code&gt; after &lt;code&gt;String&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;And finally, &lt;code&gt;isBanned&lt;/code&gt;, which is a &lt;code&gt;Boolean&lt;/code&gt; that defaults to &lt;code&gt;false&lt;/code&gt;, and is stored as &lt;code&gt;is_banned&lt;/code&gt; in the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you need to sync the models to your database. Currently, they're only present in the &lt;code&gt;schema.prisma&lt;/code&gt; file. To create the tables in the database, run the below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn redwood prisma migrate dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can use the alias &lt;code&gt;rw&lt;/code&gt; instead of &lt;code&gt;redwood&lt;/code&gt;, and that's what I'll be doing from now on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Be sure to give it a meaningful name. Treat it like a git commit – the name should reflect the changes you've made. In this case, we've created a &lt;code&gt;User&lt;/code&gt; model, so I named it &lt;code&gt;add-user-model&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now your database will have a table called &lt;code&gt;users&lt;/code&gt; with all these fields that you just defined.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Post model
&lt;/h3&gt;

&lt;p&gt;Now it's time to create a model for holding our posts.&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="err"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Post&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;id&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cuid&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&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;String&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;hasMedia&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;has_media&lt;/span&gt;&lt;span class="err"&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;mediaUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;authorId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="err"&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;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;@relation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&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;authorId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;references&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;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="err"&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;blockquote&gt;
&lt;p&gt;You may see a squiggly line under the &lt;code&gt;author&lt;/code&gt; field. Don't worry, we'll solve that soon.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The fields in this model are pretty similar to the ones in the &lt;code&gt;User&lt;/code&gt; model, except they have different names. There is one odd one out however, and that is &lt;code&gt;authorId&lt;/code&gt;. This &lt;code&gt;authorId&lt;/code&gt; field will point to the &lt;code&gt;id&lt;/code&gt; of the &lt;code&gt;User&lt;/code&gt; that created this post, and this is denoted by the &lt;code&gt;author User&lt;/code&gt; line. It has an &lt;code&gt;@relation&lt;/code&gt; directive that relates the &lt;code&gt;id&lt;/code&gt; field of &lt;code&gt;User&lt;/code&gt; to the &lt;code&gt;authorId&lt;/code&gt; field of &lt;code&gt;Post&lt;/code&gt;. Prisma also requires that we include a backref – a field on the other table that points back to this one indicating the relation. Since this will be a one-to-many (O2M) relation, i.e. one user can have many posts, the post backref in the User model should be an array. You can denote that by putting square brackets (&lt;code&gt;[]&lt;/code&gt;) after the type, just like in regular TypeScript.&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="err"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;User&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;id&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cuid&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@unique&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@unique&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;avatarUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;avatar_url&lt;/span&gt;&lt;span class="err"&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;roles&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="err"&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;bio&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;isBanned&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;is_banned&lt;/span&gt;&lt;span class="err"&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;post&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="err"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;backref&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="err"&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="err"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Post&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;id&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cuid&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&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;String&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;hasMedia&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;has_media&lt;/span&gt;&lt;span class="err"&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;mediaUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;authorId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="err"&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;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;@relation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&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;authorId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;references&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;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="err"&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;While we're at it, let's also add a &lt;code&gt;Comment&lt;/code&gt; model, which will store comments on a post. This model will have two relations — both O2M — one with the &lt;code&gt;User&lt;/code&gt; model, and the other with the &lt;code&gt;Post&lt;/code&gt; model.&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="err"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;User&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;id&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cuid&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@unique&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@unique&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;avatarUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;avatar_url&lt;/span&gt;&lt;span class="err"&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;roles&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="err"&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;bio&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;isBanned&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;is_banned&lt;/span&gt;&lt;span class="err"&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;post&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="err"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;backref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="err"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;backref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="err"&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="err"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Post&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;id&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cuid&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&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;String&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;hasMedia&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;has_media&lt;/span&gt;&lt;span class="err"&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;mediaUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;authorId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="err"&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;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;@relation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&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;authorId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;references&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;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="err"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;backref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="err"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Our&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;comment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;model&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Comment&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;id&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cuid&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;authorId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="err"&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;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;@relation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&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;authorId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;references&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;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;postId&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="err"&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;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;@relation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&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;postId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;references&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;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="err"&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;You should notice that the fields that are in a relation have the same type as the field they're in a relationship with. This is important, since they'll be storing the same type of data.&lt;/p&gt;

&lt;p&gt;Let's migrate our database! Run the same command as before, and this time, you can give it a name directly in the command line with the &lt;code&gt;--name&lt;/code&gt; argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn rw prisma migrate dev &lt;span class="nt"&gt;--name&lt;/span&gt; add-post-and-comment-models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, our three basic models have been created. Let's now use them in the Redwood project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Adding authentication to your app
&lt;/h2&gt;

&lt;p&gt;Redwood makes it really easy to add authentication to your application. It handles almost everything that is boring, like sessions and stuff like that.&lt;/p&gt;

&lt;p&gt;Let's use the Redwood CLI and sets up authentication for you. Run the below command to do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn rw setup auth dbAuth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will setup a local authentication provider that saves your users' credentials on the database. Redwood also supports some authentication-as-a-service providers out-of-the-box, like &lt;a href="https://auth0.com" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt; and &lt;a href="https://magic.link" rel="noopener noreferrer"&gt;Magic&lt;/a&gt;. Read more about that &lt;a href="https://redwoodjs.com/docs/authentication" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A few new files have been created. You need to edit these files to make it work with your schema. First, let's edit &lt;code&gt;src/lib/auth.ts&lt;/code&gt;. This file contains methods that are used by Redwood under-the-hood to determine if a user is authenticated and authorized to access a resource.&lt;/p&gt;

&lt;p&gt;You only need to do one small edit – make Redwood read the roles stored in the &lt;code&gt;users&lt;/code&gt; table in the &lt;code&gt;hasRole&lt;/code&gt; function. But first. let's make the &lt;code&gt;getCurrentUser&lt;/code&gt; function return the whole user, instead of just the user's &lt;code&gt;id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getCurrentUser&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;session&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;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&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="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above snippet, I just removed the &lt;code&gt;select {...}&lt;/code&gt; from the query so it returns all fields of the user. We can now use this in the &lt;code&gt;hasRole&lt;/code&gt; function. Change out the &lt;code&gt;hasRole&lt;/code&gt; function to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;roles&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AllowedRoles&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isAuthenticated&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="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// If your User model includes roles, uncomment the role checks on currentUser&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;roles&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// the line below has changed&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;roles&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&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="c1"&gt;// the line below has changed&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// roles not found&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code should now check the roles in the database instead of returning false by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding some fields to the &lt;code&gt;User&lt;/code&gt; model&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Redwood gives you a &lt;code&gt;hashedPassword&lt;/code&gt;, a &lt;code&gt;salt&lt;/code&gt;, a &lt;code&gt;resetToken&lt;/code&gt; and a &lt;code&gt;resetTokenExpiresAt&lt;/code&gt; to store in your database, but the current &lt;code&gt;User&lt;/code&gt; model can only store the password. Let's change that by adding three new fields to the &lt;code&gt;User&lt;/code&gt; model by changing the &lt;code&gt;User&lt;/code&gt; model in &lt;code&gt;schema.prisma&lt;/code&gt; to this:&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="err"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;User&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;id&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;@id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cuid&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;@unique&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;@unique&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;added&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;below&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;three&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;resetToken&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VarChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;resetTokenExp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@db&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timestamptz&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;avatarUrl&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;avatar_url&lt;/span&gt;&lt;span class="err"&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;roles&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="err"&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;bio&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;isBanned&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;@default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;is_banned&lt;/span&gt;&lt;span class="err"&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;post&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="err"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="err"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;@map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="err"&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;blockquote&gt;
&lt;p&gt;If you were messing around and created a few users, your migration will fail because &lt;code&gt;salt&lt;/code&gt; is empty, and &lt;code&gt;salt&lt;/code&gt; is not allowed to be empty. So, just add &lt;code&gt;@default("")&lt;/code&gt; to the &lt;code&gt;salt&lt;/code&gt; field in the schema to ensure that already-existing users won't have &lt;code&gt;null&lt;/code&gt; values for required fields.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, migrate with the below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn rw prisma migrate dev &lt;span class="nt"&gt;--name&lt;/span&gt; add-fields-to-user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Of course, you can use your own migration name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, you'll need to generate types so Redwood knows about the new User.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn rw generate types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, you need to restart the dev server. Press &lt;code&gt;Ctrl+C&lt;/code&gt; (maybe twice) to stop the current running dev server and run &lt;code&gt;yarn rw dev&lt;/code&gt; to start it again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuring authentication&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;There are just a few final changes you need to make to the &lt;code&gt;src/functions/auth.ts&lt;/code&gt; file, such as setting an &lt;code&gt;avatarUrl&lt;/code&gt;. For the &lt;code&gt;avatarUrl&lt;/code&gt;, we'll use &lt;a href="https://gravatar.com" rel="noopener noreferrer"&gt;Gravatar&lt;/a&gt;, which is a popular avatar service. For that, you just need to use the below URL as the &lt;code&gt;avatarUrl&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://gravatar.com/avatar/EMAIL_HASH?d=mp&amp;amp;s=64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;EMAIL_HASH&lt;/code&gt; should be an &lt;code&gt;md5&lt;/code&gt; hash of the user's email. For generating an &lt;code&gt;md5&lt;/code&gt; hash, let's install the &lt;code&gt;md5&lt;/code&gt; package (along with its typescript definitions) with the below commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn workspace api add md5 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; yarn workspace api add &lt;span class="nt"&gt;-D&lt;/span&gt; @types/md5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We're using &lt;code&gt;workspace api add&lt;/code&gt; instead of just &lt;code&gt;add&lt;/code&gt; because there are two workspaces here, and we just want to add &lt;code&gt;md5&lt;/code&gt; to the &lt;code&gt;api&lt;/code&gt; folder, not the &lt;code&gt;web&lt;/code&gt; folder.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, let's change the &lt;code&gt;src/functions/auth.ts&lt;/code&gt; file to make sure it works with our requirements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;db&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="s1"&gt;src/lib/db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DbAuthHandler&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="s1"&gt;@redwoodjs/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;md5&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;md5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&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;event&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="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;forgotPasswordOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// handler() is invoked after verifying that a user was found with the given&lt;/span&gt;
    &lt;span class="c1"&gt;// username. This is where you can send the user an email with a link to&lt;/span&gt;
    &lt;span class="c1"&gt;// reset their password. With the default dbAuth routes and field names, the&lt;/span&gt;
    &lt;span class="c1"&gt;// URL to reset the password will be:&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// https://example.com/reset-password?resetToken=${user.resetToken}&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// Whatever is returned from this function will be returned from&lt;/span&gt;
    &lt;span class="c1"&gt;// the `forgotPassword()` function that is destructured from `useAuth()`&lt;/span&gt;
    &lt;span class="c1"&gt;// You could use this return value to, for example, show the email&lt;/span&gt;
    &lt;span class="c1"&gt;// address in a toast message so the user will know it worked and where&lt;/span&gt;
    &lt;span class="c1"&gt;// to look for the email.&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&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="o"&gt;=&amp;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;user&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// How long the resetToken is valid for, in seconds (default is 24 hours)&lt;/span&gt;
    &lt;span class="na"&gt;expires&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// for security reasons you may want to be vague here rather than expose&lt;/span&gt;
      &lt;span class="c1"&gt;// the fact that the email address wasn't found (prevents fishing for&lt;/span&gt;
      &lt;span class="c1"&gt;// valid email addresses)&lt;/span&gt;
      &lt;span class="na"&gt;usernameNotFound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// if the user somehow gets around client validation&lt;/span&gt;
      &lt;span class="na"&gt;usernameRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email is required&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loginOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// handler() is called after finding the user that matches the&lt;/span&gt;
    &lt;span class="c1"&gt;// username/password provided at login, but before actually considering them&lt;/span&gt;
    &lt;span class="c1"&gt;// logged in. The `user` argument will be the user in the database that&lt;/span&gt;
    &lt;span class="c1"&gt;// matched the username/password.&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// If you want to allow this user to log in simply return the user.&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// If you want to prevent someone logging in for another reason (maybe they&lt;/span&gt;
    &lt;span class="c1"&gt;// didn't validate their email yet), throw an error and it will be returned&lt;/span&gt;
    &lt;span class="c1"&gt;// by the `logIn()` function from `useAuth()` in the form of:&lt;/span&gt;
    &lt;span class="c1"&gt;// `{ message: 'Error message' }`&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&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="o"&gt;=&amp;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;user&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;usernameOrPasswordMissing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Both email and password are required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;usernameNotFound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email ${username} not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// For security reasons you may want to make this the same as the&lt;/span&gt;
      &lt;span class="c1"&gt;// usernameNotFound error so that a malicious user can't use the error&lt;/span&gt;
      &lt;span class="c1"&gt;// to narrow down if it's the username or password that's incorrect&lt;/span&gt;
      &lt;span class="na"&gt;incorrectPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Incorrect password for ${username}&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="c1"&gt;// How long a user will remain logged in, in seconds&lt;/span&gt;
    &lt;span class="na"&gt;expires&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;365&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&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;resetPasswordOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// handler() is invoked after the password has been successfully updated in&lt;/span&gt;
    &lt;span class="c1"&gt;// the database. Returning anything truthy will automatically logs the user&lt;/span&gt;
    &lt;span class="c1"&gt;// in. Return `false` otherwise, and in the Reset Password page redirect the&lt;/span&gt;
    &lt;span class="c1"&gt;// user to the login page.&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&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="o"&gt;=&amp;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;user&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// If `false` then the new password MUST be different than the current one&lt;/span&gt;
    &lt;span class="na"&gt;allowReusedPassword&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;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// the resetToken is valid, but expired&lt;/span&gt;
      &lt;span class="na"&gt;resetTokenExpired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resetToken is expired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// no user was found with the given resetToken&lt;/span&gt;
      &lt;span class="na"&gt;resetTokenInvalid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resetToken is invalid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// the resetToken was not present in the URL&lt;/span&gt;
      &lt;span class="na"&gt;resetTokenRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resetToken is required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// new password is the same as the old password (apparently they did not forget it)&lt;/span&gt;
      &lt;span class="na"&gt;reusedPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Must choose a new password&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signupOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Whatever you want to happen to your data on new user signup. Redwood will&lt;/span&gt;
    &lt;span class="c1"&gt;// check for duplicate usernames before calling this handler. At a minimum&lt;/span&gt;
    &lt;span class="c1"&gt;// you need to save the `username`, `hashedPassword` and `salt` to your&lt;/span&gt;
    &lt;span class="c1"&gt;// user table. `userAttributes` contains any additional object members that&lt;/span&gt;
    &lt;span class="c1"&gt;// were included in the object given to the `signUp()` function you got&lt;/span&gt;
    &lt;span class="c1"&gt;// from `useAuth()`.&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// If you want the user to be immediately logged in, return the user that&lt;/span&gt;
    &lt;span class="c1"&gt;// was created.&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// If this handler throws an error, it will be returned by the `signUp()`&lt;/span&gt;
    &lt;span class="c1"&gt;// function in the form of: `{ error: 'Error message' }`.&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// If this returns anything else, it will be returned by the&lt;/span&gt;
    &lt;span class="c1"&gt;// `signUp()` function in the form of: `{ message: 'String here' }`.&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&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="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hashedPassword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userAttributes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&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="nf"&gt;create&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="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hashedPassword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;avatarUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://gravatar.com/avatar/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;?d=mp&amp;amp;s=64`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userAttributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// `field` will be either "username" or "password"&lt;/span&gt;
      &lt;span class="na"&gt;fieldMissing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${field} is required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;usernameTaken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email `${username}` already in use&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DbAuthHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="c1"&gt;// Provide prisma db client&lt;/span&gt;
    &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// The name of the property you'd call on `db` to access your user table.&lt;/span&gt;
    &lt;span class="c1"&gt;// ie. if your Prisma model is named `User` this value would be `user`, as in `db.user`&lt;/span&gt;
    &lt;span class="na"&gt;authModelAccessor&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;// A map of what dbAuth calls a field to what your database calls it.&lt;/span&gt;
    &lt;span class="c1"&gt;// `id` is whatever column you use to uniquely identify a user (probably&lt;/span&gt;
    &lt;span class="c1"&gt;// something like `id` or `userId` or even `email`)&lt;/span&gt;
    &lt;span class="na"&gt;authFields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;hashedPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;salt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resetToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resetToken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resetTokenExpiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resetTokenExp&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;forgotPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;forgotPasswordOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;loginOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resetPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resetPasswordOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;signup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signupOptions&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;authHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&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;All I did above was change the &lt;code&gt;hashedPassword&lt;/code&gt; field to &lt;code&gt;password&lt;/code&gt;, and the &lt;code&gt;username&lt;/code&gt; field to &lt;code&gt;email&lt;/code&gt;. I also replaced instances of &lt;code&gt;Username&lt;/code&gt; in messages to &lt;code&gt;Email&lt;/code&gt;, and I added the &lt;code&gt;avatarUrl&lt;/code&gt; field.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding login and signup pages
&lt;/h3&gt;

&lt;p&gt;Let's add login and signup pages to the frontend. Redwood makes this really easy by providing a generator for us. Run the below command to create a login page, a signup page, and a forgot and reset password page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn rw g dbAuth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will delete the &lt;code&gt;ForgotPassword&lt;/code&gt; and &lt;code&gt;ResetPassword&lt;/code&gt; pages, since I won't be adding that functionality to this project.&lt;/p&gt;

&lt;p&gt;Next, you need to replace the &lt;code&gt;username&lt;/code&gt; field in both Login and SignUp to &lt;code&gt;email&lt;/code&gt;, and in SignUp, add a new field called username. I've done it below and here's how your code should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Routes.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Route&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="s1"&gt;@redwoodjs/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="o"&gt;=&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;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;Router&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;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;LoginPage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"login"&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;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/signup"&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;SignupPage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"signup"&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;Route&lt;/span&gt; &lt;span class="na"&gt;notfound&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;NotFoundPage&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;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Router&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;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// LoginPage.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routes&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="s1"&gt;@redwoodjs/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRef&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="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PasswordField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Submit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FieldError&lt;/span&gt;&lt;span class="p"&gt;,&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="s1"&gt;@redwoodjs/forms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&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="s1"&gt;@redwoodjs/auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MetaTags&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="s1"&gt;@redwoodjs/web&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Toaster&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="s1"&gt;@redwoodjs/web/toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LoginPage&lt;/span&gt; &lt;span class="o"&gt;=&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;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logIn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nf"&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;home&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="nx"&gt;isAuthenticated&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;emailRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&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="nx"&gt;emailRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&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;onSubmit&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;data&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;logIn&lt;/span&gt;&lt;span class="p"&gt;({&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="k"&gt;if &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;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;toast&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;message&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="k"&gt;if &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;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;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;error&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="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome back!&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;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MetaTags&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;"Login"&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;main&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;"rw-main"&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;Toaster&lt;/span&gt; &lt;span class="na"&gt;toastOptions&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rw-toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6000&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;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;"rw-scaffold rw-login-container"&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;"rw-segment"&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;header&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;"rw-segment-header"&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;h2&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;"rw-heading rw-heading-secondary"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Login&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&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;header&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;"rw-segment-main"&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;"rw-form-wrapper"&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;Form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="si"&gt;}&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;"rw-form-wrapper"&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;Label&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"username"&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;"rw-label"&lt;/span&gt;
                    &lt;span class="na"&gt;errorClassName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rw-label rw-label-error"&lt;/span&gt;
                  &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    Email
                  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&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;TextField&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"username"&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;"rw-input"&lt;/span&gt;
                    &lt;span class="na"&gt;errorClassName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rw-input rw-input-error"&lt;/span&gt;
                    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emailRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                    &lt;span class="na"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;value&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email is required&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="si"&gt;}&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;FieldError&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"username"&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;"rw-field-error"&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;Label&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&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;"rw-label"&lt;/span&gt;
                    &lt;span class="na"&gt;errorClassName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rw-label rw-label-error"&lt;/span&gt;
                  &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    Password
                  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&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;PasswordField&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&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;"rw-input"&lt;/span&gt;
                    &lt;span class="na"&gt;errorClassName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rw-input rw-input-error"&lt;/span&gt;
                    &lt;span class="na"&gt;autoComplete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"current-password"&lt;/span&gt;
                    &lt;span class="na"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;value&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password is required&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="si"&gt;}&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;FieldError&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&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;"rw-field-error"&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;"rw-button-group"&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;Submit&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;"rw-button rw-button-blue"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Login&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Submit&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="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Form&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="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="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="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;"rw-login-link"&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;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Don&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;t have an account?&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&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="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&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;"rw-link"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              Sign up!
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&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="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="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;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;LoginPage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routes&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="s1"&gt;@redwoodjs/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRef&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="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PasswordField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FieldError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Submit&lt;/span&gt;&lt;span class="p"&gt;,&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="s1"&gt;@redwoodjs/forms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&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="s1"&gt;@redwoodjs/auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MetaTags&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="s1"&gt;@redwoodjs/web&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Toaster&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="s1"&gt;@redwoodjs/web/toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SignupPage&lt;/span&gt; &lt;span class="o"&gt;=&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;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signUp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nf"&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;home&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="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="c1"&gt;// focus on email box on page load&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&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="nx"&gt;emailRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&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;onSubmit&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;data&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signUp&lt;/span&gt;&lt;span class="p"&gt;({&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="k"&gt;if &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;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;toast&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;message&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="k"&gt;if &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;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;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;error&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="c1"&gt;// user is signed in automatically&lt;/span&gt;
      &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome!&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;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MetaTags&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;"Signup"&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;main&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;"rw-main"&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;Toaster&lt;/span&gt; &lt;span class="na"&gt;toastOptions&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rw-toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6000&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;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;"rw-scaffold rw-login-container"&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;"rw-segment"&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;header&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;"rw-segment-header"&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;h2&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;"rw-heading rw-heading-secondary"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Signup&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&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;header&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;"rw-segment-main"&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;"rw-form-wrapper"&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;Form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="si"&gt;}&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;"rw-form-wrapper"&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;Label&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"username"&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;"rw-label"&lt;/span&gt;
                    &lt;span class="na"&gt;errorClassName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rw-label rw-label-error"&lt;/span&gt;
                  &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    Email
                  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&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;TextField&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"username"&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;"rw-input"&lt;/span&gt;
                    &lt;span class="na"&gt;errorClassName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rw-input rw-input-error"&lt;/span&gt;
                    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emailRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                    &lt;span class="na"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;value&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email is required&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="si"&gt;}&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;FieldError&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"username"&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;"rw-field-error"&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;Label&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&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;"rw-label"&lt;/span&gt;
                    &lt;span class="na"&gt;errorClassName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rw-label rw-label-error"&lt;/span&gt;
                  &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    Username
                  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&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;TextField&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&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;"rw-input"&lt;/span&gt;
                    &lt;span class="na"&gt;errorClassName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rw-input rw-input-error"&lt;/span&gt;
                    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emailRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                    &lt;span class="na"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;value&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Username is required&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="si"&gt;}&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;FieldError&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&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;"rw-field-error"&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;Label&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&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;"rw-label"&lt;/span&gt;
                    &lt;span class="na"&gt;errorClassName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rw-label rw-label-error"&lt;/span&gt;
                  &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    Password
                  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&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;PasswordField&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&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;"rw-input"&lt;/span&gt;
                    &lt;span class="na"&gt;errorClassName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rw-input rw-input-error"&lt;/span&gt;
                    &lt;span class="na"&gt;autoComplete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"current-password"&lt;/span&gt;
                    &lt;span class="na"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;value&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password is required&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="si"&gt;}&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;FieldError&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&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;"rw-field-error"&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;"rw-button-group"&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;Submit&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;"rw-button rw-button-blue"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                      Sign Up
                    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Submit&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="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Form&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="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="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="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;"rw-login-link"&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;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Already have an account?&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&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="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&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;"rw-link"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              Log in!
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&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="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="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;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;SignupPage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Notice how I've only changed the text but not the &lt;code&gt;name&lt;/code&gt; of the inputs? This is because under-the-hood, Redwood still expects your &lt;code&gt;email&lt;/code&gt; to be called &lt;code&gt;username&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For one final step, let's add a home page so we don't have to see the Redwood logo anymore. Use the below command to generate an index page at &lt;code&gt;/&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn rw g page home /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will generate a page called &lt;code&gt;home&lt;/code&gt;, but map it to &lt;code&gt;/&lt;/code&gt;, instead of &lt;code&gt;/home&lt;/code&gt;. Change the code of the newly created &lt;code&gt;HomePage.tsx&lt;/code&gt; to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;MetaTags&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="s1"&gt;@redwoodjs/web&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HomePage&lt;/span&gt; &lt;span class="o"&gt;=&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;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MetaTags&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;"Redwoodit"&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"A clone of Reddit using RedwoodJS"&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;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Redwoodit&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;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;HomePage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with that, you've added authentication to your Redwood application.&lt;/p&gt;

&lt;p&gt;If you visit &lt;a href="http://localhost:3000/signup" rel="noopener noreferrer"&gt;http://localhost:3000/signup&lt;/a&gt;, you can create an account and if you visit &lt;a href="http://localhost:3000/login" rel="noopener noreferrer"&gt;http://localhost:3000/login&lt;/a&gt;, you can log in to an account.&lt;/p&gt;

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

&lt;p&gt;You've successfully created a Redwood application and added authentication to it. In the next part of this tutorial, you will add support for fetching, creating, updating and deleting posts and comments. If you've gotten stuck anywhere, be sure to check out the &lt;a href="https://gitlab.com/arnu515-tutorials/reddit-clone-with-redwoodjs" rel="noopener noreferrer"&gt;source code&lt;/a&gt;, the &lt;a href="https://redwoodjs.com/docs" rel="noopener noreferrer"&gt;Redwood documentation&lt;/a&gt;, or ask in the &lt;a href="https://discord.gg/jjSYEQd" rel="noopener noreferrer"&gt;Redwood Discord&lt;/a&gt;/&lt;a href="https://community.redwoodjs.com/" rel="noopener noreferrer"&gt;Discourse Forums&lt;/a&gt; for help.&lt;/p&gt;

&lt;p&gt;Stay tuned for Part 2!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Simple Authenticator - The Simplest TOTP Application</title>
      <dc:creator>arnu515</dc:creator>
      <pubDate>Fri, 14 Jan 2022 07:30:51 +0000</pubDate>
      <link>https://dev.to/arnu515/simple-authenticator-the-simplest-totp-application-94f</link>
      <guid>https://dev.to/arnu515/simple-authenticator-the-simplest-totp-application-94f</guid>
      <description>&lt;h3&gt;
  
  
  Overview of My Submission
&lt;/h3&gt;

&lt;p&gt;Simple Authenticator is an application that generates TOTP codes for two-factor authentication - like Google Authenticator. It can optionally store your configured applications &lt;strong&gt;encrypted&lt;/strong&gt; in the cloud with MongoDB Atlas.&lt;/p&gt;

&lt;p&gt;Github Repository: &lt;a href="https://github.com/arnu515/simple-authenticator" rel="noopener noreferrer"&gt;https://github.com/arnu515/simple-authenticator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Link to download (Android only): &lt;a href="https://github.com/arnu515/simpleauthenticator/releases" rel="noopener noreferrer"&gt;https://github.com/arnu515/simpleauthenticator/releases&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;[Note]: Choose your own Adventure&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Code
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/arnu515" rel="noopener noreferrer"&gt;
        arnu515
      &lt;/a&gt; / &lt;a href="https://github.com/arnu515/simpleauthenticator" rel="noopener noreferrer"&gt;
        simpleauthenticator
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Simple TOTP Authenticator App built with Flutter
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;simpleauthenticator&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Simple TOTP Authenticator&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Download&lt;/h2&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;I recommend running the application with &lt;code&gt;flutter run&lt;/code&gt;, because the APK is not signed&lt;/strong&gt;. View instructions below.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Get the Android APK file from &lt;a href="https://github.com/arnu515/simpleauthenticator/releases" rel="noopener noreferrer"&gt;Releases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You may get a play-protect warning, and that's because I haven't yet signed the application. This warning can be ignored.&lt;/p&gt;
&lt;p&gt;I will publish it soon on F-Droid and Play store.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run locally&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;You can run the application on Windows, Mac or Linux by using the below command. Make sure to have the &lt;a href="https://flutter.dev" rel="nofollow noopener noreferrer"&gt;flutter SDK&lt;/a&gt; installed.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;flutter run --dart-define &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;API_URL=URL_TO_BACKEND&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;If you haven't hosted the &lt;a href="https://github.com/arnu515/simpleauthenticator/tree/master/backend" rel="noopener noreferrer"&gt;backend&lt;/a&gt; yourself, you can use &lt;a href="https://d13c320db282.up.railway.app" rel="nofollow noopener noreferrer"&gt;https://d13c320db282.up.railway.app&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can also run the app on Android if you have the Android SDK installed by connecting your phone to your computer and turning on USB debugging, and running the above command.&lt;/p&gt;
&lt;p&gt;You can also build the app by replacing &lt;code&gt;flutter run&lt;/code&gt; with &lt;code&gt;flutter build&lt;/code&gt;…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/arnu515/simpleauthenticator" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>atlashackathon</category>
      <category>mongodb</category>
      <category>flutter</category>
      <category>dart</category>
    </item>
    <item>
      <title>Connect VSCode to a Docker container</title>
      <dc:creator>arnu515</dc:creator>
      <pubDate>Sat, 18 Sep 2021 02:40:24 +0000</pubDate>
      <link>https://dev.to/arnu515/connect-vscode-to-a-docker-container-50o7</link>
      <guid>https://dev.to/arnu515/connect-vscode-to-a-docker-container-50o7</guid>
      <description>&lt;p&gt;In the video below, you'll learn how you can connect VSCode to a Docker container using the Remote Containers extension.&lt;/p&gt;

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

</description>
      <category>devops</category>
      <category>vscode</category>
      <category>docker</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Automate your workflow with Microsoft Power Automate</title>
      <dc:creator>arnu515</dc:creator>
      <pubDate>Mon, 12 Jul 2021 16:50:41 +0000</pubDate>
      <link>https://dev.to/arnu515/automate-your-workflow-with-microsoft-power-automate-3m7i</link>
      <guid>https://dev.to/arnu515/automate-your-workflow-with-microsoft-power-automate-3m7i</guid>
      <description>&lt;p&gt;The most tedious part of development is doing the same tasks over and over again. Don't you feel bored having to mention someone on Slack when they get assigned a Github Issue, or having to send an email to your newsletter when you post a tweet.&lt;/p&gt;

&lt;p&gt;Sure, you can setup integrations, webhooks and CI jobs to do these tasks, but they're often tedious, require signups to hundreds of services, and all of them might not have the same steps.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://flow.microsoft.com" rel="noopener noreferrer"&gt;Microsoft Power Automate&lt;/a&gt;, also called Microsoft Flow, which allows you to automate almost everything in your tech stack. Want to send a message on Slack or Teams, sure, want to SMS a number with Twilio, you can do that too. It also has many listeners, like when a new Tweet is posted, or when a Github Issue is assigned to you.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll take a look at how we can automate a few common tasks with Microsoft Flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An Office365 Microsoft Account&lt;/li&gt;
&lt;li&gt;Accounts for the services we'll be automating (Github, Slack, etc).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Flow 1 - Mail me when a Github Issue is assigned to me
&lt;/h2&gt;

&lt;p&gt;First, create a new Automated Cloud flow and give it a name.&lt;/p&gt;

&lt;p&gt;Add the &lt;code&gt;When an issue is assigned to me&lt;/code&gt; trigger from Github. You will now be asked to sign in to Github, which you should do.&lt;/p&gt;

&lt;p&gt;Next, add the &lt;code&gt;Mail&lt;/code&gt; action, which uses Sendgrid under the hood.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.ibb.co%2FXbgBN01%2FScreenshot-from-2021-07-09-14-48-42.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.ibb.co%2FXbgBN01%2FScreenshot-from-2021-07-09-14-48-42.png" alt="How your flow looks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can notice that I've added some dynamic elements that were provided to me by the Github Trigger.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can add more things to your flow, for example, add an item on Microsoft Todo, create a Trello card, or message yourself on Slack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.ibb.co%2Fg4DGNVs%2FScreenshot-from-2021-07-09-14-53-15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.ibb.co%2Fg4DGNVs%2FScreenshot-from-2021-07-09-14-53-15.png" alt="Added a Todo action"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Save&lt;/code&gt; when done, and you will now be emailed whenever an issue's been assigned to you!&lt;/p&gt;

&lt;h2&gt;
  
  
  Flow 2 - Post your website's status to your Twitter everyday at 6 AM
&lt;/h2&gt;

&lt;p&gt;Create a Scheduled Cloud Flow, since this event is based on time.&lt;/p&gt;

&lt;p&gt;Make sure the date is set to this date, and the time is set to &lt;code&gt;6:00 AM&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also set the task to repeat every 1 day. See the below image if you get confused.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.ibb.co%2F0Bf8qqL%2FScreenshot-from-2021-07-09-14-58-28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.ibb.co%2F0Bf8qqL%2FScreenshot-from-2021-07-09-14-58-28.png" alt="Flow creation dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a HTTP action that'll query your website's status page. My website uses Cachet to display its status, so I'll tailor my flow to Cachet's API response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FLbm4ZML.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FLbm4ZML.png" alt="HTTP action"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, you need to parse the data down to access the &lt;code&gt;status_name&lt;/code&gt; field in the JSON response. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FPwNrtPK.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FPwNrtPK.png" alt="Data actions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, you can tweet out the status of the website. You'll need to sign in to Twitter for this flow to work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FHujqhpF.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FHujqhpF.png" alt="Tweet action"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Save&lt;/code&gt; when done, and now, at 6 AM daily, your followers will be notified of your website's status.&lt;/p&gt;

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

&lt;p&gt;See how easy it is to automate the small things? The best part is that Microsoft Flow is included with the Office365 subscription, so along with Excel, Word and PowerPoint, you also get this monster of an automation tool that really, nobody cares about! So go ahead and share this article with your friends, so they can also use this wonderful tool.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>azure</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Everything new in Flask 2.0</title>
      <dc:creator>arnu515</dc:creator>
      <pubDate>Tue, 22 Jun 2021 15:49:25 +0000</pubDate>
      <link>https://dev.to/arnu515/everything-new-in-flask-2-0-gnl</link>
      <guid>https://dev.to/arnu515/everything-new-in-flask-2-0-gnl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@markusspiske" rel="noopener noreferrer"&gt;Markus Spiske&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/code" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Flask is one of the most used Python Frameworks in Web Development. It recently came out with a new release - &lt;code&gt;2.0.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this post, we'll go through the major changes. If you want the full change log, click &lt;a href="https://flask.palletsprojects.com/en/2.0.x/changes/#version-2-0-0" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changes
&lt;/h2&gt;

&lt;p&gt;Let's go over the major changes introduced in Flask &lt;code&gt;2.0.0&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dropped support for Python 2 and 3.5
&lt;/h3&gt;

&lt;p&gt;The most breaking change is the dropped support for Python &lt;code&gt;2&lt;/code&gt; and &lt;code&gt;3.5&lt;/code&gt;. Well, it's about time we move from those versions anyway, since Python &lt;code&gt;2&lt;/code&gt; is &lt;em&gt;really old&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;Also, dropping &lt;code&gt;3.5&lt;/code&gt; and &lt;code&gt;2&lt;/code&gt; support is required for the next change:&lt;/p&gt;

&lt;h3&gt;
  
  
  Type hints
&lt;/h3&gt;

&lt;p&gt;Python &lt;code&gt;3.7&lt;/code&gt; added support for type annotations. This is where you can add type hints to variables to tell others what type it will be. This is a God send for people with linters and autocompletors, since you no longer have to view docs to find the return type of a certain function.&lt;/p&gt;

&lt;p&gt;Keep in mind however, that type hints aren't enforced, meaning that this is completely valid code, as far as the &lt;code&gt;python&lt;/code&gt; interpreter goes&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;


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

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;To check your types, use an external linter like &lt;a href="http://mypy-lang.org/" rel="noopener noreferrer"&gt;mypy&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Flask &lt;code&gt;2.0.0&lt;/code&gt; is now fully typed, so no more awkward moments when you import &lt;code&gt;request&lt;/code&gt; from &lt;code&gt;flask&lt;/code&gt; and your IDE won't autocomplete its methods.&lt;/p&gt;

&lt;p&gt;You can see the difference between the type hints in Flask &lt;code&gt;2.0.0&lt;/code&gt; and its earlier versions with the &lt;code&gt;help()&lt;/code&gt; function:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2Fsoc5ohb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2Fsoc5ohb.png" alt="Flask 1.1.4's type hints"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above image shows Flask &lt;code&gt;1.1.4&lt;/code&gt;'s docstring and the below image shows Flask &lt;code&gt;2.0.1&lt;/code&gt;'s docstring. Notice the type hints&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FpK5B9yK.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FpK5B9yK.png" alt="Flask 2.0.1's type hints"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  New &lt;code&gt;Config.from_file()&lt;/code&gt; method
&lt;/h3&gt;

&lt;p&gt;You may remember the &lt;code&gt;Config.from_json()&lt;/code&gt;, or the &lt;code&gt;app.config.from_json()&lt;/code&gt; method that you use to configure Flask with a JSON File. In Flask &lt;code&gt;2.0.0&lt;/code&gt;, it has been deprecated in favour of &lt;code&gt;Config.from_file&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The syntax of this method is as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loads_function&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Now, if we wanted to implement the old &lt;code&gt;from_json&lt;/code&gt; behaviour, look at the code below:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This opens us to do the same to parse &lt;code&gt;TOML&lt;/code&gt; files as well:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;toml&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;You need to install the &lt;code&gt;toml&lt;/code&gt; package for this!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  New route decorators
&lt;/h3&gt;

&lt;p&gt;Flask has new decorators for defining routes now. Before we used &lt;code&gt;@app.route(path: str)&lt;/code&gt; to define a route in our app, and for methods other than &lt;code&gt;GET&lt;/code&gt; we add the &lt;code&gt;methods&lt;/code&gt; parameter to our decorator.&lt;/p&gt;

&lt;p&gt;Now, Flask has followed ExpressJS's routes and added decorators for defining routes specific to HTTP Methods.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# You can now use these instead of app.route
&lt;/span&gt;&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@app.put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@app.delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@app.patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Smaller changes
&lt;/h3&gt;

&lt;p&gt;Here's a list of smaller changes that may affect your projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some &lt;code&gt;send_file&lt;/code&gt; parameters have been renamed, the old names are deprecated. &lt;code&gt;attachment_filename&lt;/code&gt; is renamed to &lt;code&gt;download_name&lt;/code&gt;. &lt;code&gt;cache_timeout&lt;/code&gt; is renamed to &lt;code&gt;max_age&lt;/code&gt;. &lt;code&gt;add_etags&lt;/code&gt; is renamed to &lt;code&gt;etag&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When loading a &lt;code&gt;.env&lt;/code&gt; or &lt;code&gt;.flaskenv&lt;/code&gt; file, the current working directory is no longer changed to the location of the file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;helpers.total_seconds()&lt;/code&gt; is deprecated. Use &lt;code&gt;timedelta.total_seconds()&lt;/code&gt; instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And my favorite feature of this list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flask shell&lt;/code&gt; sets up tab and history completion like the default python shell if &lt;code&gt;readline&lt;/code&gt; is installed&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How to upgrade
&lt;/h2&gt;

&lt;p&gt;Change your &lt;code&gt;Flask&lt;/code&gt; dependency in &lt;code&gt;requirements.txt&lt;/code&gt;, &lt;code&gt;Pipfile&lt;/code&gt; or &lt;code&gt;pyproject.toml&lt;/code&gt; to this:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Flask==2.0.0


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

&lt;/div&gt;

&lt;p&gt;And run:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

pip3 &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt


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

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;You can no longer use &lt;code&gt;python2&lt;/code&gt;, so beware of that!&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;This is of course not the full change log. If you want the full change log, click &lt;a href="https://flask.palletsprojects.com/en/2.0.x/changes/#version-2-0-0" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>webdev</category>
      <category>changelog</category>
    </item>
    <item>
      <title>Implement password-less authentication in your apps (magic sign in)</title>
      <dc:creator>arnu515</dc:creator>
      <pubDate>Fri, 30 Apr 2021 04:35:38 +0000</pubDate>
      <link>https://dev.to/arnu515/implement-password-less-authentication-in-your-apps-magic-sign-in-14cn</link>
      <guid>https://dev.to/arnu515/implement-password-less-authentication-in-your-apps-magic-sign-in-14cn</guid>
      <description>&lt;p&gt;In this post, I'll show you how you can implement passwordless sign in, or "magic-link" sign in to your web app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://svelte.dev"&gt;SvelteJS&lt;/a&gt; with &lt;a href="https://vitejs.dev"&gt;Vite&lt;/a&gt; and &lt;a href="https://typescriptlang.org"&gt;Typescript&lt;/a&gt; for the frontend with:

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://w3schools.com/w3css"&gt;W3.CSS&lt;/a&gt; for the styling.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;NodeJS typescript for the backend with:

&lt;ul&gt;
&lt;li&gt;ExpressJS&lt;/li&gt;
&lt;li&gt;MongoDB&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you don't know typescript, feel free to use plain javascript. Just remember, you can't use some features I can.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Create the project
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;p&gt;Creating a svelte + vite = svite app is pretty easy! All you have to do is enter this command into your terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init @vitejs/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I'll choose &lt;code&gt;frontend&lt;/code&gt; for the project name, &lt;code&gt;svelte&lt;/code&gt; for the framework and the &lt;code&gt;Typescript&lt;/code&gt; variant.&lt;/p&gt;

&lt;p&gt;Next, you can &lt;code&gt;cd&lt;/code&gt; into your project and run&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn &lt;span class="c"&gt;# installs packages&lt;/span&gt;
yarn dev &lt;span class="c"&gt;# starts the DEV server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can now access your frontend app at &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Feel free to use &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;pnpm&lt;/code&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;p&gt;The backend setup has more steps, however.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create our project
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create backend folder and cd into it&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;backend &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;backend
&lt;span class="c"&gt;# Create a package.json&lt;/span&gt;
yarn init &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="c"&gt;# or npm init -y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;I removed the &lt;code&gt;main&lt;/code&gt; field in &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Install packages
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# feel free to use npm/pnpm instead&lt;/span&gt;
yarn add express &lt;span class="se"&gt;\&lt;/span&gt;
  cors &lt;span class="se"&gt;\&lt;/span&gt;
  morgan &lt;span class="se"&gt;\&lt;/span&gt;
  mongoose &lt;span class="se"&gt;\&lt;/span&gt;
  jsonwebtoken &lt;span class="se"&gt;\&lt;/span&gt;
  nodemailer &lt;span class="se"&gt;\&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;(TYPESCRIPT ONLY)&lt;/strong&gt; Install typedefs of packages, and other dev dependencies
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# feel free to use npm/pnpm instead&lt;/span&gt;
yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;
  @types/express @types/cors @types/morgan @types/mongoose @types/jsonwebtoken @types/nodemailer &lt;span class="se"&gt;\&lt;/span&gt;
  @types/node &lt;span class="se"&gt;\&lt;/span&gt;
  ts-node &lt;span class="se"&gt;\&lt;/span&gt;
  typescript &lt;span class="se"&gt;\&lt;/span&gt;
  nodemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Add scripts in &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&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="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc -p ."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build:watch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc -p . -w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node dist/index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start:watch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nodemon dist/index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm-run-all -p build:watch start:watch"&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;(TYPESCRIPT ONLY)&lt;/strong&gt; Add a &lt;code&gt;tsconfig.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsconfig.json
&lt;span class="c"&gt;# select "node" from the options&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Add this to &lt;code&gt;compilerOptions&lt;/code&gt; in your tsconfig:&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="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;I removed the &lt;code&gt;baseUrl&lt;/code&gt; field in the config&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Create folders and files
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; src/controllers src/models src/middlewares src/util
&lt;span class="nb"&gt;touch &lt;/span&gt;src/index.ts &lt;span class="c"&gt;# use js for javascript&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Before we start with actual coding, let's take a look at how passwordless authentication, or "magic-link" authentication works.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, we ask the user for their email. This happens on the &lt;strong&gt;frontend&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Next, the &lt;strong&gt;frontend&lt;/strong&gt; sends the email to the &lt;strong&gt;backend&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;backend&lt;/strong&gt; searches the &lt;strong&gt;database&lt;/strong&gt; for a user with the provided email. If a user is found, the user is &lt;strong&gt;logging in&lt;/strong&gt;. Otherwise, the user is &lt;strong&gt;registering&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;backend&lt;/strong&gt; generates a code for the user, and stores it in the database. It sends the code to the user via &lt;strong&gt;email&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The email contains a &lt;strong&gt;link&lt;/strong&gt; to get the user authenticated. This link may/maynot need a code. Hence, the term &lt;strong&gt;magic link&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The user enters the code, the backend checks it, and if the code is valid, the user is successfully authenticated.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Backend coding
&lt;/h2&gt;

&lt;p&gt;Let's start with the backend, so we know what to do in the frontend.&lt;/p&gt;
&lt;h3&gt;
  
  
  Main app
&lt;/h3&gt;

&lt;p&gt;Start with the main file, &lt;code&gt;src/index.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&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;express&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;cors&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;cors&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;morgan&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;morgan&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;mongoose&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;mongoose&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServer&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;http&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;mongoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&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;MONGODB_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;mongodb://localhost:27017/db&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;useNewUrlParser&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;useUnifiedTopology&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;useCreateIndex&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;morgan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dev&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Server started on port &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;PORT&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;h4&gt;
  
  
  Read environment variables from a file
&lt;/h4&gt;

&lt;p&gt;Using the NPM package &lt;code&gt;dotenv&lt;/code&gt;, we can read environment variables like &lt;code&gt;MONGODB_URL&lt;/code&gt; from a file. &lt;/p&gt;

&lt;p&gt;First, install the package:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; dotenv &lt;span class="c"&gt;# or use npm/pnpm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next, add this to the TOP (even before all imports) of &lt;code&gt;src/index.ts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&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="o"&gt;||&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="o"&gt;===&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="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;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&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;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;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  I don't have mongodb!
&lt;/h4&gt;

&lt;p&gt;If you don't have mongodb/can't install it, you can either use the &lt;a href="https://hub.docker.com/_/mongo"&gt;Docker image&lt;/a&gt;, or use &lt;a href="https://cloud.mongodb.com"&gt;MongoDB Atlas&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure to add your MongoDB connection URI to your environment:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MONGODB_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOUR URL&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Or add the same thing to a file named &lt;code&gt;.env&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Models
&lt;/h3&gt;

&lt;p&gt;Let's work on our Mongoose models. For this project, we'll have two models - the User model and the Code model&lt;/p&gt;
&lt;h4&gt;
  
  
  User model
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/models/User.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&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;mongoose&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;member&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&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;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Code model
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/models/Code.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&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;mongoose&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CodeSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Exists only if the user is logging in.&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;nullable&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CodeSchema&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;Code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Auth routes
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/controllers/auth.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Router&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;express&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;router&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;/send_magic_link&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;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="c1"&gt;// Code to send the email&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/token&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;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="c1"&gt;// Code to generate a token from the code in the email&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/user&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;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="c1"&gt;// Code to fetch the user from the token&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;router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's register this controller:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/controllers/index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;auth&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;./auth&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ExpressRouter&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;express&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExpressRouter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;path&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&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="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;auth&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;/api/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// --------------------------&lt;/span&gt;

&lt;span class="c1"&gt;// src/index.ts&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;controllers&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;./controllers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&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;controllers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&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="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this controller, we'll have two API routes. One to generate the code and send it by email, and the other to validate the code and return a token.&lt;/p&gt;

&lt;p&gt;First, let's focus on the route to generate the code. We'll be working with the &lt;code&gt;POST&lt;/code&gt; method for &lt;code&gt;/send_magic_link&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add some code to get the email from the request body
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&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="nx"&gt;body&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&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;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;400&lt;/span&gt;&lt;span class="p"&gt;)&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;error_description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please provide a valid email&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;return&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;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Add some code to check if there's a user with that email
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&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;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;email&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Be sure to import &lt;code&gt;User&lt;/code&gt; and make the function &lt;code&gt;async&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Add code to generate a random 6-digit code
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;899999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Add code to add the generated code to the database
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Expire after 15 minutes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;code&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="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;If we test our code, you'll notice that we now have a new entry in our database
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// codes collection&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;608&lt;/span&gt;&lt;span class="nx"&gt;a5e125f5f267eccf58bd4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;504837&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@email.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1619682057847&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;__v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Add code to send email
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;smtp.mailtrap.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2525&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xxxxxxx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xxxxxxx&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="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;e&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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@example.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;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Enter this code: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&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="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;p&amp;gt;Enter this code: &amp;lt;b&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/b&amp;gt;&amp;lt;/p&amp;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;transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMail&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="nx"&gt;err&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occured while sending email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;else&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mail sent&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;I'm using &lt;a href="https://mailtrap.io"&gt;MailTrap&lt;/a&gt; for a free mail server, but you can use any other service.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You should now have a working mail sender. Test the endpoint to make sure that mails do get sent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you followed all steps correctly, you should get an email with this text:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enter this code: &amp;lt;SOME CODE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, we can work on generating a token from the code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Import &lt;code&gt;sign&lt;/code&gt; and &lt;code&gt;verify&lt;/code&gt; from &lt;code&gt;jsonwebtoken&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;sign&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;verify&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;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Add code to check the validity of the generated code
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/token&lt;/span&gt;&lt;span class="dl"&gt;"&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="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="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;codeFromQs&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="nx"&gt;query&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;codeFromQs&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;codeFromQs&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;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;400&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error_description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please send a valid code in the querystring&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;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;codeFromQs&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;c&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;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;code&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;c&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;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;400&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error_description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please send a valid code in the querystring&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;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;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;ok&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Add code to add user to database and generate a token
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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;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;user&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;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findById&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="nx"&gt;exec&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;user&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;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;400&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;error_description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please send a valid code in the querystring&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&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;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Exp in 1 week&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&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;_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&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;SECRET&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret&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;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;604800&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="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;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&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="nx"&gt;token&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Now you should be able to send a request to the endpoint, providing the code in the query. This will return you a token and with the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, let's add an endpoint to get the user from the token:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/user&lt;/span&gt;&lt;span class="dl"&gt;"&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="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;authHeader&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="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorization&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;authHeader&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&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="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bearer&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;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;401&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid auth header&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;identity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&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="mi"&gt;1&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;SECRET&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;identity&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&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;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;401&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid token&lt;/span&gt;&lt;span class="dl"&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&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;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;401&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid token&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;user&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;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;identity&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="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;user&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;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;401&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid token&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;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;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&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="nx"&gt;user&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;This is what your final &lt;code&gt;auth.ts&lt;/code&gt; controller should look like:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;

&lt;p&gt;With the backend all done and complete, we can start work on the frontend.&lt;/p&gt;

&lt;p&gt;Let's add a CSS library to make our lives easier. In the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; tag of &lt;code&gt;index.html&lt;/code&gt;, add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://www.w3schools.com/w3css/4/w3.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I'll add an &lt;code&gt;Auth.svelte&lt;/code&gt; component which will contain the auth form&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/lib/components/Auth.svelte --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&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;requestCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w3-border w3-border-gray w3-padding w3-rounded"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w3-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Authenticate&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w3-margin"&lt;/span&gt; &lt;span class="na"&gt;on:submit=&lt;/span&gt;&lt;span class="s"&gt;"{requestCode}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w3-input w3-border w3-border-gray"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w3-button w3-black w3-hover-black"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"width: 100%"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Get magic link&lt;span class="nt"&gt;&amp;lt;/button&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now it's time to add some functionality to our app. I'll add a &lt;code&gt;submit&lt;/code&gt; handler to the form which will ask our backend for the code.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// &amp;lt;script&amp;gt; tag&lt;/span&gt;

  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createEventDispatcher&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;svelte&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;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createEventDispatcher&lt;/span&gt;&lt;span class="p"&gt;();&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;requestCode&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;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&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;email&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;trim&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="k"&gt;try&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;res&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;http://localhost:5000/api/auth/send_magic_link&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;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="s2"&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="s2"&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="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;email&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;res&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;if&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;ok&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prompt-code&lt;/span&gt;&lt;span class="dl"&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="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;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;alert&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;error&lt;/span&gt; &lt;span class="o"&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;statusText&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;e&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;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An unknown error occured&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here's our &lt;code&gt;Auth.svelte file&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;After we receive an email, we need to be able to enter the code in it. I'll create a new &lt;code&gt;Code.svelte&lt;/code&gt; component, which will contain the following code:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Notice how these two files emit events? We need to handle these events in &lt;code&gt;App.svelte&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/App.svelte --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Auth&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/components/Auth.svelte&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;Code&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/components/Code.svelte&lt;/span&gt;&lt;span class="dl"&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;sentLink&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w3-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Welcome&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
{#if !token}
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w3-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {#if !sentLink}
      &lt;span class="nt"&gt;&amp;lt;Auth&lt;/span&gt; &lt;span class="na"&gt;on:prompt-code=&lt;/span&gt;&lt;span class="s"&gt;"{() =&amp;gt; (sentLink = true)}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    {:else}
      &lt;span class="nt"&gt;&amp;lt;Code&lt;/span&gt;
        &lt;span class="na"&gt;on:authenticated=&lt;/span&gt;&lt;span class="s"&gt;"{({ detail: token }) =&amp;gt; {
          localStorage.setItem('token', token);
          window.location.reload();
        }}"&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    {/if}
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
{:else}
&lt;span class="c"&gt;&amp;lt;!-- Add code to show user information --&amp;gt;&lt;/span&gt;
{/if}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We should now have a working auth page. But we're not done yet! We still need to fetch the user from the server!&lt;/p&gt;

&lt;p&gt;Here's the final code for that:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



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

&lt;p&gt;And that's it! We're done with a basic, simple, magic-link sign in. But do note that this method here isn't optimised for production or anything, it is just an introduction to email sign in.&lt;/p&gt;

&lt;p&gt;In a realworld app, you should &lt;strong&gt;NEVER&lt;/strong&gt; store the JWT in &lt;code&gt;localStorage&lt;/code&gt;. Always use cookies, or use &lt;code&gt;express-session&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you got stuck somewhere, checkout the &lt;a href="https://github.com/arnu515/passwordless-auth"&gt;Github&lt;/a&gt; repo, and feel free to give your thoughts in the comments!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>svelte</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
