<?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: Kiran Krishnan</title>
    <description>The latest articles on DEV Community by Kiran Krishnan (@devkiran).</description>
    <link>https://dev.to/devkiran</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%2F97387%2F5ab06c08-0d00-456a-b33f-5213327fa116.jpg</url>
      <title>DEV Community: Kiran Krishnan</title>
      <link>https://dev.to/devkiran</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devkiran"/>
    <language>en</language>
    <item>
      <title>Adding SAML Single Sign-On to an Express App: A Step-by-Step Guide 🚀🚀</title>
      <dc:creator>Kiran Krishnan</dc:creator>
      <pubDate>Mon, 04 Sep 2023 06:45:50 +0000</pubDate>
      <link>https://dev.to/devkiran/adding-saml-single-sign-on-to-an-express-app-a-step-by-step-guide-2la4</link>
      <guid>https://dev.to/devkiran/adding-saml-single-sign-on-to-an-express-app-a-step-by-step-guide-2la4</guid>
      <description>&lt;p&gt;In this article, you'll learn how add SAML SSO login to an Express.js app. You'll use &lt;a href="https://github.com/boxyhq/jackson" rel="noopener noreferrer"&gt;SAML Jackson&lt;/a&gt; with &lt;a href="https://auth0.com/single-sign-on" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt; to authenticate users and protect routes.&lt;/p&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/boxyhq" rel="noopener noreferrer"&gt;
        boxyhq
      &lt;/a&gt; / &lt;a href="https://github.com/boxyhq/jackson" rel="noopener noreferrer"&gt;
        jackson
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🔥 Streamline your web application's authentication with Jackson, an SSO service supporting SAML and OpenID Connect protocols. Beyond enterprise-grade Single Sign-On, it also supports Directory Sync via the SCIM 2.0 protocol for automatic user and group provisioning/de-provisioning. 🤩
    &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;SAML Jackson: Open Source Enterprise SSO And Directory Sync&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://bestpractices.coreinfrastructure.org/projects/7493" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3edfd6a63b93427a8b61cf54425dad9f98c5820f8ff2b3cfa9b1f57c4b8ac876/68747470733a2f2f626573747072616374696365732e636f7265696e6672617374727563747572652e6f72672f70726f6a656374732f373439332f6261646765" alt="OpenSSF Best Practices Badge"&gt;&lt;/a&gt;
&lt;a href="https://www.npmjs.com/package/@boxyhq/saml-jackson" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/8ec58b9b815dd8f3e5ef976fb5441757beb1e3b4e22b1ca371f4e33810a722c8/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64742f40626f787968712f73616d6c2d6a61636b736f6e" alt="NPM downloads badge"&gt;&lt;/a&gt;
&lt;a href="https://hub.docker.com/r/boxyhq/jackson" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a4e44d8ab9a1a3aa1da716a95c7e41bc803315b19631afe301021c417c921656/68747470733a2f2f696d672e736869656c64732e696f2f646f636b65722f70756c6c732f626f787968712f6a61636b736f6e" alt="Docker pull statistics badge"&gt;&lt;/a&gt;
&lt;a href="https://github.com/boxyhq/jackson/blob/main/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/6ff0c3e58dbb0437ba71a92c418dddd20aec82b64f259f6abd2691b0d25b1ba1/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f626f787968712f6a61636b736f6e" alt="Apache 2.0 license badge"&gt;&lt;/a&gt;
&lt;a href="https://github.com/boxyhq/jackson/issues" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/991d409e68ba02dec61ad2f341df5fcebd7a4010f9f4673f5d90f748501574ef/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732f626f787968712f6a61636b736f6e" alt="Open Github issues badge"&gt;&lt;/a&gt;
&lt;a href="https://github.com/boxyhq/jackson/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/15549a398b9d29bae35a3876b171447f8933e7a4b070460f1d16b2664a61a219/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f626f787968712f6a61636b736f6e" alt="Github stargazers"&gt;&lt;/a&gt;
&lt;a href="https://www.npmjs.com/package/@boxyhq/saml-jackson" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e0583d7d459e91813d672b07e0be3dfd1f8e27c138784f6cb2c373573ad9ea7e/68747470733a2f2f696d672e736869656c64732e696f2f6e6f64652f762f40626f787968712f73616d6c2d6a61636b736f6e" alt="Nodejs version support badge"&gt;&lt;/a&gt;
&lt;a href="https://raw.githubusercontent.com/boxyhq/jackson/main/swagger/swagger.json" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e2a3acac954e001cb770e33b7afd1bee1aa4b3876d04a30074e667e8740096ec/68747470733a2f2f696d672e736869656c64732e696f2f737761676765722f76616c69642f332e303f7370656355726c3d68747470732533412532462532467261772e67697468756275736572636f6e74656e742e636f6d253246626f787968712532466a61636b736f6e2532466d61696e25324673776167676572253246737761676765722e6a736f6e" alt="Swagger Validator badge"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;SAML Jackson bridges or proxies a SAML login flow to OAuth 2.0 or OpenID Connect, abstracting away all the complexities of the SAML protocol. It also supports Directory Sync via the SCIM 2.0 protocol for automatic user and group provisioning/de-provisioning.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We now also support OpenID Connect providers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/boxyhq/jacksonsamljackson480.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fboxyhq%2Fjacksonsamljackson480.gif" alt="A quick demo of the admin portal without sound to show an overview of what to expect. It shows features such as SSO, the ability to set up SSO connections, Setup Links, Directory sync, and more"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Directory Sync&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;SAML Jackson also supports Directory Sync based on the SCIM 2.0 protocol.&lt;/p&gt;
&lt;p&gt;Directory sync helps organizations automate the provisioning and de-provisioning of their users. As a result, it streamlines the user lifecycle management process by saving valuable organizational hours, creating a single truth source of the user identity data, and facilitating them to keep the data secure.&lt;/p&gt;
&lt;p&gt;For complete documentation, visit &lt;a href="https://boxyhq.com/docs/directory-sync/overview" rel="nofollow noopener noreferrer"&gt;boxyhq.com/docs/directory-sync/overview&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🌟 Why star this repository?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;If you find this project helpful, please consider supporting us by starring &lt;a href="https://github.com/boxyhq/jackson" rel="noopener noreferrer"&gt;the repository&lt;/a&gt; and sharing it with others. This helps others find the project…&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/boxyhq/jackson" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;You can also access the full code at the &lt;a href="https://github.com/boxyhq/express-jackson-auth0-demo" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s get started!&lt;/p&gt;

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

&lt;p&gt;To follow along with this article, you’ll need the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js installed on your computer&lt;/li&gt;
&lt;li&gt;Basic knowledge about Node.js and Express.js&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting up the database
&lt;/h2&gt;

&lt;p&gt;Make sure you have a PostgreSQL database ready. You can use any PostgreSQL database you want.&lt;/p&gt;

&lt;p&gt;If you're on macOS, you can use &lt;code&gt;DBngin&lt;/code&gt; to create a PostgreSQL database. It's free and easy to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure the Identity Provider
&lt;/h2&gt;

&lt;p&gt;We'll use the Auth0 as our identity provider. An Identity Provider (IdP) is a service that manage user accounts for your app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, go to the &lt;a href="https://auth0.com/signup" rel="noopener noreferrer"&gt;Auth0 signup page&lt;/a&gt;, then create an account.&lt;/li&gt;
&lt;li&gt;Go to &lt;a href="https://manage.auth0.com/dashboard/" rel="noopener noreferrer"&gt;Dashboard &amp;gt; Applications &amp;gt; Applications&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Create Application&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;Give your new application a name.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Regular Web Applications&lt;/strong&gt; as an application type and the click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Go to the app you created, then click the &lt;strong&gt;Addons&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;SAML2 Web App&lt;/strong&gt; box, click the slider to enable the Addon.&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;Usage&lt;/strong&gt; tab and download the &lt;strong&gt;Identity Provider Metadata&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;Settings&lt;/strong&gt; tab and make below changes.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;http://localhost:3000/sso/acs&lt;/code&gt; as your &lt;strong&gt;Application Callback URL&lt;/strong&gt; that receives the SAML response.&lt;/li&gt;
&lt;li&gt;Paste the following JSON for &lt;strong&gt;Settings&lt;/strong&gt;, then click &lt;strong&gt;Enable&lt;/strong&gt; button.
&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"audience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://saml.boxyhq.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mappings"&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;"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"firstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"&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;&lt;code&gt;audience&lt;/code&gt; is just an identifier to validate the SAML audience. &lt;a href="https://boxyhq.com/docs/jackson/deploy/env-variables#saml_audience" rel="noopener noreferrer"&gt;More info&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Auth0 provides database connections to authenticate users with an email/username and password. These credentials are securely stored in the Auth0 user store.&lt;/p&gt;

&lt;p&gt;Let's create one so that our users can register or login.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;a href="https://manage.auth0.com/dashboard/" rel="noopener noreferrer"&gt;Auth0 Dashboard &amp;gt; Authentication &amp;gt; Database&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create DB Connection&lt;/strong&gt; - &lt;a href="https://auth0.com/docs/authenticate/database-connections/custom-db/create-db-connection" rel="noopener noreferrer"&gt;Auth0 Create DB Document&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Give your connection a name, then click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;Applications&lt;/strong&gt; tab and enable the application you just created.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we've everything ready, let's move to the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Launch a terminal and clone the GitHub repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/devkiran/express-saml.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;express-saml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, install the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the environment variables:&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;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the &lt;code&gt;DATABASE_URL&lt;/code&gt; variable with your Postgres database connection URI.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Express app
&lt;/h2&gt;

&lt;p&gt;This is a simple express.js app created using &lt;code&gt;express-generator&lt;/code&gt;. You can use any express.js app if you want.&lt;/p&gt;

&lt;p&gt;Our express.js app has only 2 routes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /&lt;/code&gt; render a home page&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /dashboard&lt;/code&gt; render a dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, what's the plan? We'll add SAML SSO login (via Auth0) to our express.js app so that only authenticated users can access the &lt;code&gt;/dashboard&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install SAML Jackson
&lt;/h2&gt;

&lt;p&gt;Run the following command to install the latest version of the SAML Jackson.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;--save&lt;/span&gt; @boxyhq/saml-jackson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you installed Jackson, let's initialize it.&lt;/p&gt;

&lt;p&gt;Add the following code to the &lt;code&gt;routes/index.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/index.js&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;apiController&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;oauthController&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;jacksonOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;externalUrl&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;APP_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;samlAudience&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;SAML_AUDIENCE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;samlPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/sso/acs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sql&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;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;DATABASE_URL&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;init&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;jackson&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;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@boxyhq/saml-jackson&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;controllers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jacksonOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;apiController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jackson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;oauthController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jackson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauthController&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;h2&gt;
  
  
  Setting up Express.js routes
&lt;/h2&gt;

&lt;p&gt;Now let's add the routes to our express.js app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add SAML Metadata
&lt;/h3&gt;

&lt;p&gt;The first route you'll create is the &lt;code&gt;GET /connection&lt;/code&gt; one. This route will display a form with following fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Metadata&lt;/code&gt;: Enter the XML Metadata content you've downloaded from IdP.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Tenant&lt;/code&gt;: Jackson supports a multi-tenant architecture, this is a unique identifier you set from your side that relates back to your customer's tenant. This is normally an email, domain, an account id, or user-id.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Product&lt;/code&gt;: Jackson support multiple products, this is a unique identifier you set from your side that relates back to the product your customer is using.
&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;// routes/index.js&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/connection&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&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;Add a view to display the 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;!-- views/connection.ejs --&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;SAML Connection&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&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;"/stylesheets/style.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&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;"max-w-md mx-auto mt-6 p-6 bg-white rounded-lg shadow-md"&lt;/span&gt;&lt;span class="nt"&gt;&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;"text-2xl font-semibold text-center mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;SAML Connection&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-gray-600 text-center mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add SAML Metadata.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/connection"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&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;"mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&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;"tenant"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block text-gray-700"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tenant&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;"text"&lt;/span&gt;
            &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tenant"&lt;/span&gt;
            &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tenant"&lt;/span&gt;
            &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-input mt-1 block w-full"&lt;/span&gt;
            &lt;span class="na"&gt;required=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt;
          &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&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;"mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&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;"product"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block text-gray-700"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Product&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;"text"&lt;/span&gt;
            &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"product"&lt;/span&gt;
            &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"product"&lt;/span&gt;
            &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-input mt-1 block w-full"&lt;/span&gt;
            &lt;span class="na"&gt;required=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt;
          &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&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;"mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&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;"rawMetadata"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block text-gray-700"&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Metadata (Raw XML)&lt;span class="nt"&gt;&amp;lt;/label&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt;
            &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"rawMetadata"&lt;/span&gt;
            &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"rawMetadata"&lt;/span&gt;
            &lt;span class="na"&gt;cols=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt;
            &lt;span class="na"&gt;rows=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;
            &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-textarea mt-1 block w-full"&lt;/span&gt;
            &lt;span class="na"&gt;required=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&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;"mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
            &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;
            &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Submit
          &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&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;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Now let's add another route &lt;code&gt;POST /connection&lt;/code&gt; that will store the form data by calling the SAML Jackson config API.&lt;/p&gt;

&lt;p&gt;This step is the equivalent of setting an OAuth 2.0 app and generating a client ID and client secret that will be used in the login flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/index.js&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/connection&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="nx"&gt;next&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;rawMetadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;product&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultRedirectUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:3000/sso/callback&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;redirectUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;["http://localhost:3000/*"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apiController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;rawMetadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;defaultRedirectUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;redirectUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;next&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="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 are a few important things to note in the code above.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;defaultRedirectUrl&lt;/code&gt; holds the redirect URL to use in the IdP login flow. Jackson will call this URL after completing an IdP login flow.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;redirectUrl&lt;/code&gt; holds an array containing a list of allowed redirect URLs. Jackson will disallow any redirects that are not on this list.&lt;/p&gt;

&lt;p&gt;Next, let's start the express app. The app starts a server and listens on port 3000 (by default) for connections.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Now, let's visit &lt;a href="http://localhost:3000/connection" rel="noopener noreferrer"&gt;http://localhost:3000/connection&lt;/a&gt;, you should see the page with a form.&lt;/p&gt;

&lt;p&gt;Here you can add the metadata you've downloaded from Auth0. Fill out the form with a Tenant, Product, and paste the metadata XML content as it is.&lt;/p&gt;

&lt;p&gt;I'll use &lt;code&gt;boxyhq.com&lt;/code&gt; as tenant and &lt;code&gt;crm&lt;/code&gt; as product.&lt;/p&gt;

&lt;p&gt;The response returns a JSON with &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; that can be stored against your tenant and product for a more secure OAuth 2.0 flow.&lt;/p&gt;

&lt;p&gt;If you do not want to store the &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; you can alternatively use &lt;code&gt;client_id=tenant=&amp;lt;tenantID&amp;gt;&amp;amp;product=&amp;lt;productID&amp;gt;&lt;/code&gt; and any arbitrary value for &lt;code&gt;client_secret&lt;/code&gt; when setting up the OAuth 2.0 flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redirect the users to IdP
&lt;/h3&gt;

&lt;p&gt;Now you have added the SAML metadata, you'll need a route to redirect the users to IdP to start the SAML authentication.&lt;/p&gt;

&lt;p&gt;Let's add a new route &lt;code&gt;GET /sso/authorize&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Don't forget to change the values of the tenant and product in the code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/index.js&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/sso/authorize&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="nx"&gt;next&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;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;tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boxyhq.com&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;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crm&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;response_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;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;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tenant=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;product=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;product&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;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:3000/sso/callback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a-random-state-value&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;redirect_url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;oauthController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redirect_url&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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;next&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;oauthController.authorize()&lt;/code&gt; will returns a &lt;code&gt;redirect_url&lt;/code&gt;. You should redirect the users to this &lt;code&gt;redirect_url&lt;/code&gt; to start the IdP authentication flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handle the SAML Response from IdP
&lt;/h3&gt;

&lt;p&gt;This route becomes the Assertion Consumer Service (ACS) URL of your app. The ACS URL tells your IdP where to POST its SAML Response after authenticating a user.&lt;/p&gt;

&lt;p&gt;The SAML Response contains 2 fields: &lt;code&gt;SAMLResponse&lt;/code&gt; and &lt;code&gt;RelayState&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/index.js&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/sso/acs&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="nx"&gt;next&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;try&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;SAMLResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RelayState&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;SAMLResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;RelayState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;redirect_url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;oauthController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;samlResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redirect_url&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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;next&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="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;Call to the method &lt;code&gt;oauthController.samlResponse()&lt;/code&gt; will returns a &lt;code&gt;redirect_url&lt;/code&gt;. You should redirect the users to this &lt;code&gt;redirect_url&lt;/code&gt;. The query parameters will include the &lt;code&gt;code&lt;/code&gt; and &lt;code&gt;state&lt;/code&gt; parameters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code exchange
&lt;/h3&gt;

&lt;p&gt;Now exchange the &lt;code&gt;code&lt;/code&gt; for a &lt;code&gt;token&lt;/code&gt;. The &lt;code&gt;token&lt;/code&gt; is required to access the user profile.&lt;/p&gt;

&lt;p&gt;Let's create a new route &lt;code&gt;GET /sso/callback&lt;/code&gt; to handle the callback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/index.js&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/sso/callback&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="nx"&gt;next&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;code&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boxyhq.com&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;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crm&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;body&lt;/span&gt; &lt;span class="o"&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="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tenant=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;product=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;product&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;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dummy&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get the access token&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;access_token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;oauthController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&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="c1"&gt;// Get the user information&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profile&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;oauthController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Add the profile to the express session&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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;profile&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="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;next&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="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 code, replace the value for &lt;code&gt;tenant&lt;/code&gt; and &lt;code&gt;product&lt;/code&gt; with yours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Protect the dashboard
&lt;/h3&gt;

&lt;p&gt;Now is the time to fix our &lt;code&gt;GET /dashboard&lt;/code&gt; route so that only authenticated users can access it.&lt;/p&gt;

&lt;p&gt;Let's fix it by adding a condition to check if the &lt;code&gt;profile&lt;/code&gt; exists in the session.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;profile&lt;/code&gt; is &lt;code&gt;undefined&lt;/code&gt;, redirect the users back to the &lt;code&gt;/&lt;/code&gt; otherwise display the profile on the dashboard.&lt;/p&gt;

&lt;p&gt;Replace the &lt;code&gt;GET /dashboard&lt;/code&gt; route with the below code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/index.js&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &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="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;profile&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;session&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;profile&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&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="nf"&gt;redirect&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="c1"&gt;// Pass the profile to the view&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&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;profile&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;Replace the &lt;code&gt;views/dashboard.ejs&lt;/code&gt; view with the below code.&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;!-- views/dashboard.ejs --&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Dashboard&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&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;"/stylesheets/style.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Dashboard&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Only authenticated users should access this page.&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;Id - &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;profile.id&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Email - &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;profile.email&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the command line, let's restart the express app then visit the authorize the URL &lt;a href="http://localhost:3000/sso/authorize" rel="noopener noreferrer"&gt;http://localhost:3000/sso/authorize&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you've configured everything okay, it should redirect you to the Auth0 authentication page, then click on the Sign up link and register there&lt;/p&gt;

&lt;p&gt;If the authentication is successful, the app will redirect you to the dashboard and display the &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt; of the user.&lt;/p&gt;

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

&lt;p&gt;Congratulations, you should now have a functioning SAML SSO integrated with your express.js app using the SAML Jackson and Auth0.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;To learn more about SAML Jackson, take a look at the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://boxyhq.com/docs/jackson/overview" rel="noopener noreferrer"&gt;SAML Jackson Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/boxyhq/jackson" rel="noopener noreferrer"&gt;SAML Jackson GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/boxyhq/jackson-examples/tree/main/apps/express" rel="noopener noreferrer"&gt;Express web app that shows how to use SAML Jackson&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your feedback and contributions are welcome!&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1694089182296121402-115" src="https://platform.twitter.com/embed/Tweet.html?id=1694089182296121402"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1694089182296121402-115');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1694089182296121402&amp;amp;theme=dark"
  }



&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>9 Next.js Open Source Projects for Contributions 🚀🚀</title>
      <dc:creator>Kiran Krishnan</dc:creator>
      <pubDate>Fri, 01 Sep 2023 10:28:13 +0000</pubDate>
      <link>https://dev.to/devkiran/9-open-source-nextjs-projects-open-for-contributions-272c</link>
      <guid>https://dev.to/devkiran/9-open-source-nextjs-projects-open-for-contributions-272c</guid>
      <description>&lt;p&gt;I've been using Next.js for a while now and have grown to love it. It's an excellent framework for building full-stack web applications.&lt;/p&gt;

&lt;p&gt;I'm always on the lookout for open-source projects where I can contribute. It's a fantastic way to learn, enhance your skills, and give back to the community.&lt;/p&gt;

&lt;p&gt;In this brief article, I'll share nine open-source Next.js projects that welcome contributions. I hope you discover something that piques your interest.&lt;/p&gt;

&lt;p&gt;Ensure you star the repositories you like; it's a great way to show appreciation.&lt;/p&gt;

&lt;p&gt;Here they are, listed in no particular order.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dub.sh/kiran" rel="noopener noreferrer"&gt;Dub&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;An open-source link management tool for modern marketing teams to create, share, and track short links.&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%2Fvs7p0wbf4kiwa1p1t0bj.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%2Fvs7p0wbf4kiwa1p1t0bj.png" alt="Dub" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&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/dubinc" rel="noopener noreferrer"&gt;
        dubinc
      &lt;/a&gt; / &lt;a href="https://github.com/dubinc/dub" rel="noopener noreferrer"&gt;
        dub
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The modern link attribution platform. Loved by world-class marketing teams like Twilio, Buffer, Framer, Perplexity, Vercel, and more.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;a href="https://dub.co" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Dub is the modern, open-source link attribution platform for short links, conversion tracking, and affiliate programs." src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F28986134%2F442305524-42cf0705-f5a2-4200-bc4a-c5acf0ba9e15.png%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDc4NjM4NjUsIm5iZiI6MTc0Nzg2MzU2NSwicGF0aCI6Ii8yODk4NjEzNC80NDIzMDU1MjQtNDJjZjA3MDUtZjVhMi00MjAwLWJjNGEtYzVhY2YwYmE5ZTE1LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MjElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTIxVDIxMzkyNVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWJiMGQ3MzhlYjlkN2E5MDQxYzZiNDllMjdlYjgxZmRhMjViZWI2MWM4OTM3ZTI5MDlkN2JmOGY5Nzg4MmE4YWQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.Y9dW5dayBnX7v168uRCf4D8KHLVr3WHqCmS7u3gpuDw"&gt;
&lt;/a&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Dub&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;
    The open-source link attribution platform
    &lt;br&gt;
    &lt;a href="https://dub.co" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Learn more »&lt;/strong&gt;&lt;/a&gt;
    &lt;br&gt;
    &lt;br&gt;
    &lt;a href="https://github.com/dubinc/dub#introduction" rel="noopener noreferrer"&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/a&gt; ·
    &lt;a href="https://github.com/dubinc/dub#tech-stack" rel="noopener noreferrer"&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;&lt;/a&gt; ·
    &lt;a href="https://github.com/dubinc/dub#self-hosting" rel="noopener noreferrer"&gt;&lt;strong&gt;Self-hosting&lt;/strong&gt;&lt;/a&gt; ·
    &lt;a href="https://github.com/dubinc/dub#contributing" rel="noopener noreferrer"&gt;&lt;strong&gt;Contributing&lt;/strong&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://twitter.com/dubdotco" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/b938300a89fc0bfaf718a4616e53c2ed562adf42c9fb4149fe2bb569aa40a658/68747470733a2f2f696d672e736869656c64732e696f2f747769747465722f666f6c6c6f772f647562646f74636f3f7374796c653d666c6174266c6162656c3d253430647562646f74636f266c6f676f3d7477697474657226636f6c6f723d306266266c6f676f436f6c6f723d666666" alt="Twitter"&gt;
  &lt;/a&gt;
  &lt;a href="https://news.ycombinator.com/item?id=32939407" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a49356377e0903ddb19e124e2c3746cac32516b9630abf14c2616b072b3aaccf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4861636b65722532304e6577732d3235352d253233464636363030" alt="Hacker News"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/dubinc/dub/blob/main/LICENSE.md" rel="noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/f566ff9dbf2e519927fed2c056621cc0e76654db0851876f9eae58acbe271074/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f647562696e632f6475623f6c6162656c3d6c6963656e7365266c6f676f3d67697468756226636f6c6f723d663830266c6f676f436f6c6f723d666666" alt="License"&gt;
  &lt;/a&gt;
&lt;/p&gt;



&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Introduction&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Dub is the modern, open-source link attribution platform for &lt;a href="https://dub.co/home" rel="nofollow noopener noreferrer"&gt;short links&lt;/a&gt;, &lt;a href="https://dub.co/analytics" rel="nofollow noopener noreferrer"&gt;conversion tracking&lt;/a&gt;, and &lt;a href="https://dub.co/partners" rel="nofollow noopener noreferrer"&gt;affiliate programs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our platform powers 100M+ clicks and 2M+ links monthly, and is used by world-class marketing teams from companies like Twilio, Buffer, Framer, Perplexity, Vercel, Laravel, and &lt;a href="https://dub.co/customers" rel="nofollow noopener noreferrer"&gt;more&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tech Stack&lt;/h2&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/" rel="nofollow noopener noreferrer"&gt;Next.js&lt;/a&gt; – framework&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.typescriptlang.org/" rel="nofollow noopener noreferrer"&gt;TypeScript&lt;/a&gt; – language&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.com/" rel="nofollow noopener noreferrer"&gt;Tailwind&lt;/a&gt; – CSS&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.prisma.io/" rel="nofollow noopener noreferrer"&gt;Prisma&lt;/a&gt; – ORM&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://upstash.com/" rel="nofollow noopener noreferrer"&gt;Upstash&lt;/a&gt; – redis&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tinybird.com/" rel="nofollow noopener noreferrer"&gt;Tinybird&lt;/a&gt; – analytics&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://planetscale.com/" rel="nofollow noopener noreferrer"&gt;PlanetScale&lt;/a&gt; – database&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://next-auth.js.org/" rel="nofollow noopener noreferrer"&gt;NextAuth.js&lt;/a&gt; – auth&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://boxyhq.com/enterprise-sso" rel="nofollow noopener noreferrer"&gt;BoxyHQ&lt;/a&gt; – SSO/SAML&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://turbo.build/repo" rel="nofollow noopener noreferrer"&gt;Turborepo&lt;/a&gt; – monorepo&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stripe.com/" rel="nofollow noopener noreferrer"&gt;Stripe&lt;/a&gt; – payments&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://resend.com/" rel="nofollow noopener noreferrer"&gt;Resend&lt;/a&gt; – emails&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vercel.com/" rel="nofollow noopener noreferrer"&gt;Vercel&lt;/a&gt; – deployments&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pangea.cloud/services/domain-intel/reputation" rel="nofollow noopener noreferrer"&gt;Pangea&lt;/a&gt; - link scanning&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Self-Hosting&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;You can self-host Dub for greater control over your data and design. &lt;a href="https://dub.co/docs/self-hosting/guide" rel="nofollow noopener noreferrer"&gt;Read this guide&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contributing&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;We love our contributors! Here's how you can contribute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/dubinc/dub/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt; if you believe you've encountered a bug.&lt;/li&gt;
&lt;li&gt;Follow…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/dubinc/dub" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
   
&lt;h3&gt;
  
  
  &lt;a href="https://cal.com" rel="noopener noreferrer"&gt;Cal.com&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Meet Cal.com, the event-juggling scheduler for everyone. Focus on meeting, not making meetings. Free for individuals.&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%2Fb9fge1tep39tppsyuplr.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%2Fb9fge1tep39tppsyuplr.png" alt="Cal.com" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&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/calcom" rel="noopener noreferrer"&gt;
        calcom
      &lt;/a&gt; / &lt;a href="https://github.com/calcom/cal.com" rel="noopener noreferrer"&gt;
        cal.com
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Scheduling infrastructure for absolutely everyone.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;
  &lt;a href="https://github.com/calcom/cal.com" rel="noopener noreferrer"&gt;
   &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F8019099%2F210054112-5955e812-a76e-4160-9ddd-58f2c72f1cce.png" alt="Logo"&gt;
  &lt;/a&gt;
  &lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Cal.com (formerly Calendso)&lt;/h3&gt;
&lt;/div&gt;
  &lt;p&gt;
    The open-source Calendly successor
    &lt;br&gt;
    &lt;a href="https://cal.com" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Learn more »&lt;/strong&gt;&lt;/a&gt;
    &lt;br&gt;
    &lt;br&gt;
    &lt;a href="https://github.com/calcom/cal.com/discussions" rel="noopener noreferrer"&gt;Discussions&lt;/a&gt;
    ·
    &lt;a href="https://cal.com" rel="nofollow noopener noreferrer"&gt;Website&lt;/a&gt;
    ·
    &lt;a href="https://github.com/calcom/cal.com/issues" rel="noopener noreferrer"&gt;Issues&lt;/a&gt;
    ·
    &lt;a href="https://cal.com/roadmap" rel="nofollow noopener noreferrer"&gt;Roadmap&lt;/a&gt;
  &lt;/p&gt;
&lt;p&gt;
   &lt;a href="https://www.producthunt.com/products/cal-com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a535bb8e25af9da4cc72ca76e05d1b093c21aec456032550a86285e91cce2580/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f50726f6475637425323048756e742d2532333125323050726f647563742532306f662532307468652532304d6f6e74682d253233444135353245" alt="Product Hunt"&gt;&lt;/a&gt;
   &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/359f16b1763feb28a9384b21fbd519422b293e2ec32a17fd0cb5601bcd9d8f1a/68747470733a2f2f6170692e636865636b6c7968712e636f6d2f76312f6261646765732f67726f7570732f313132303731383f7374796c653d666c6174267468656d653d64656661756c74"&gt;&lt;img src="https://camo.githubusercontent.com/359f16b1763feb28a9384b21fbd519422b293e2ec32a17fd0cb5601bcd9d8f1a/68747470733a2f2f6170692e636865636b6c7968712e636f6d2f76312f6261646765732f67726f7570732f313132303731383f7374796c653d666c6174267468656d653d64656661756c74" alt="Checkly QA"&gt;&lt;/a&gt;
   &lt;a href="https://status.cal.com" rel="nofollow noopener noreferrer"&gt;&lt;img height="20px" src="https://camo.githubusercontent.com/285165edf282cdc7fd5c995cea0856725b95b423579448f7f1433f9a9455f580/68747470733a2f2f626574746572757074696d652e636f6d2f7374617475732d6261646765732f76312f6d6f6e69746f722f61396b662e737667" alt="Uptime"&gt;&lt;/a&gt;
   &lt;a href="https://github.com/calcom/cal.com/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b35a625031fc28560e31b6aec3f2a7b1cb0ae2bf2199acca07bc9e263245d5f0/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f63616c636f6d2f63616c2e636f6d" alt="Github Stars"&gt;&lt;/a&gt;
   &lt;a href="https://news.ycombinator.com/item?id=34507672" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9d39d678b21befe17981accb468a290fb836bc7459eab071e5ffde8ffde3dee1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4861636b65722532304e6577732d253233312d253233464636363030" alt="Hacker News"&gt;&lt;/a&gt;
   &lt;a href="https://github.com/calcom/cal.com/blob/main/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e9fda15ef5a15115c6b4a0b81ce0d77c6b56f5924a2174521b919a92acf09b4a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4147504c76332d707572706c65" alt="License"&gt;&lt;/a&gt;
   &lt;a href="https://github.com/calcom/cal.com/pulse" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/c20f668d8138590d1b74c9ef31c9d472d1afcfc936568bb11144825dcfe8fd2e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6d6d69742d61637469766974792f6d2f63616c636f6d2f63616c2e636f6d" alt="Commits-per-month"&gt;&lt;/a&gt;
   &lt;a href="https://cal.com/pricing" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/23ebdca5c25ae5c70b3849faadfa9aa4ca70cb6b17baa7081c4aa5fd56b8b5b2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f50726963696e672d467265652d627269676874677265656e" alt="Pricing"&gt;&lt;/a&gt;
   &lt;a href="https://jitsu.com?utm_source=github/calcom/cal.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b4c7286076b6a8930169a78bfab7a8fb6c4c90a742e89bdaefe1ded4942480ba/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d6574726963735f747261636b65645f62792d4a495453552d4141303046463f6c6f676f3d646174613a696d6167652f706e673b6261736536342c6956424f5277304b47676f414141414e5355684555674141414134414141414f434159414141416653433352414141414358424957584d41414173544141414c457745416d7077594141414141584e535230494172733463365141414141526e51553142414143786a777638595155414141434b5355524256486742725a445243594177444551763667434f6f4b4f34684f4358493951566e455a77695935694635476156436c61424e74696f43535576435233744d4a617849665a6757344147556f4550567767505a6f5330446d6767334e425644464e624d49736d5943616b334a316a446b3969435176734b4a766b7a7237314e3831476a367644542f4c5532503652685936336a6361666b33594a456267655a70694679632f35484a4b763845663237334e5366414247625166555a686e4f534141414141415355564f524b35435949493d" alt="Jitsu Tracked"&gt;&lt;/a&gt;
   &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/918551f6fc985a516efdc82cd5b069e1819d358fb984796dc6b7fc395e91c336/68747470733a2f2f6170692e636865636b6c7968712e636f6d2f76312f6261646765732f636865636b732f35653034383034382d316235312d343762612d393230392d3630363037353037363232653f726573706f6e736554696d653d74727565"&gt;&lt;img src="https://camo.githubusercontent.com/918551f6fc985a516efdc82cd5b069e1819d358fb984796dc6b7fc395e91c336/68747470733a2f2f6170692e636865636b6c7968712e636f6d2f76312f6261646765732f636865636b732f35653034383034382d316235312d343762612d393230392d3630363037353037363232653f726573706f6e736554696d653d74727565" alt="Checkly Availability"&gt;&lt;/a&gt;
   &lt;a href="https://hub.docker.com/r/calcom/cal.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/6067a1f350a4b35550371955b679231dd8b8a1f99df8e48100c97223ed8ee596/68747470733a2f2f696d672e736869656c64732e696f2f646f636b65722f70756c6c732f63616c636f6d2f63616c2e636f6d"&gt;&lt;/a&gt;
   &lt;a href="https://twitch.tv/calcomtv" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/c308fc072256fe2f793d4b5d3f4e1150718704c459ad8ae86115ec3b3ddc0d27/68747470733a2f2f696d672e736869656c64732e696f2f7477697463682f7374617475732f63616c636f6d74763f7374796c653d666c6174"&gt;&lt;/a&gt;
   &lt;a href="https://github.com/calcom/cal.com/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0fe572a48280e6e692c8e9c5293c6d3cff49539da55554ce3cfd32be5907eef7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f48656c7025323057616e7465642d436f6e747269627574652d626c7565"&gt;&lt;/a&gt;
   &lt;a href="https://cal.com/figma" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/892eecf8491911811c726427a5eda0a0259d35cefa971523628e5499d352b501/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4669676d612d44657369676e25323053797374656d2d626c756576696f6c6574"&gt;&lt;/a&gt;
   &lt;a href="https://contributor-covenant.org/version/1/4/code-of-conduct/%20" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a255dd5886d834f32d77732983977b996990444de26c79df64e34291f1da484e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436f6e7472696275746f72253230436f76656e616e742d312e342d707572706c65"&gt;&lt;/a&gt;
   &lt;a href="https://console.algora.io/org/cal/bounties?status=open" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/8de62c2acc6e7783f0f0f348e8f79c45583ad53018bca491aa1608d66d61676a/68747470733a2f2f696d672e736869656c64732e696f2f656e64706f696e743f75726c3d6874747073253341253246253246636f6e736f6c652e616c676f72612e696f253246617069253246736869656c647325324663616c253246626f756e746965732533467374617475732533446f70656e"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;About the Project&lt;/h2&gt;

&lt;/div&gt;
&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/8019099/250881880-407e727e-ff19-4ca4-bcae-049dca05cf02.gif?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDc4NjM4NjYsIm5iZiI6MTc0Nzg2MzU2NiwicGF0aCI6Ii84MDE5MDk5LzI1MDg4MTg4MC00MDdlNzI3ZS1mZjE5LTRjYTQtYmNhZS0wNDlkY2EwNWNmMDIuZ2lmP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDUyMSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA1MjFUMjEzOTI2WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9NWE4NWI5NDc0Zjc3YWZmNzEyYzA0ZTNiNmI2NWFhZDI4OGRlMTFmODlmODNmNzg0MDc5NjM3YTU5NjdjOTkyOCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.GSnC5SZk--CKmexP-gttQvOM1AeTQyk-35B5xGKsVWc"&gt;&lt;img width="100%" alt="booking-screen" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F8019099%2F250881880-407e727e-ff19-4ca4-bcae-049dca05cf02.gif%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDc4NjM4NjYsIm5iZiI6MTc0Nzg2MzU2NiwicGF0aCI6Ii84MDE5MDk5LzI1MDg4MTg4MC00MDdlNzI3ZS1mZjE5LTRjYTQtYmNhZS0wNDlkY2EwNWNmMDIuZ2lmP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDUyMSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA1MjFUMjEzOTI2WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9NWE4NWI5NDc0Zjc3YWZmNzEyYzA0ZTNiNmI2NWFhZDI4OGRlMTFmODlmODNmNzg0MDc5NjM3YTU5NjdjOTkyOCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.GSnC5SZk--CKmexP-gttQvOM1AeTQyk-35B5xGKsVWc"&gt;&lt;/a&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Scheduling infrastructure for absolutely everyone&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;The open source Calendly successor. You are in charge
of your own data, workflow, and appearance.&lt;/p&gt;
&lt;p&gt;Calendly and other scheduling tools are awesome. It made our lives massively easier. We're using it for business meetings, seminars, yoga classes, and even calls with our families. However, most tools are very limited in terms of control and customization.&lt;/p&gt;
&lt;p&gt;That's where Cal.com comes in. Self-hosted or hosted by us. White-label by design. API-driven and ready to be deployed on your own domain. Full control of your events and data.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Recognition&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;&lt;a href="https://news.ycombinator.com/from?site=cal.com" rel="nofollow noopener noreferrer"&gt;Hacker News&lt;/a&gt;&lt;/h4&gt;

&lt;/div&gt;
&lt;a href="https://news.ycombinator.com/item?id=34507672" rel="nofollow noopener noreferrer"&gt;
  &lt;img width="250" height="54" alt="Featured on Hacker News" src="https://camo.githubusercontent.com/887474612b324993548175f52173d4b6a829066ec7c440229daa86466d66a480/68747470733a2f2f6861636b65726e6577732d62616467652e76657263656c2e6170702f6170693f69643d3334353037363732"&gt;
&lt;/a&gt;
&lt;a href="https://news.ycombinator.com/item?id=26817795" rel="nofollow noopener noreferrer"&gt;
  &lt;img width="250" height="54" alt="Featured on Hacker News" src="https://camo.githubusercontent.com/1b95875eea12a86554df2705977475d34bedd79b72a9187d4d13e2bd68d52aab/68747470733a2f2f6861636b65726e6577732d62616467652e76657263656c2e6170702f6170693f69643d3236383137373935"&gt;
&lt;/a&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;&lt;a href="https://producthunt.com/products/cal-com?utm_source=badge-top-post-badge&amp;amp;utm_medium=badge" rel="nofollow noopener noreferrer"&gt;Product Hunt&lt;/a&gt;&lt;/h4&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://producthunt.com/posts/calendso?utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_souce=badge-calendso" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3acdc49bd355a99e0d68d484e4a6b73c6157fe6d4a72964a054a02e46747db4e/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d323931393130267468656d653d6c6967687426706572696f643d6d6f6e74686c79" alt="Cal.com - The open source Calendly alternative | Product Hunt" width="250" height="54"&gt;&lt;/a&gt; &lt;a href="https://producthunt.com/posts/calendso?utm_source=badge-featured&amp;amp;utm_medium=badge&amp;amp;utm_souce=badge-calendso" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/8fdf36c3047c34fff5323b3c94245a0c5d120ab92528997d37ca6a6ebf601ad0/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f66656174757265642e7376673f706f73745f69643d323931393130267468656d653d6c69676874" alt="Cal.com - The open source Calendly alternative | Product Hunt" width="250" height="54"&gt;&lt;/a&gt; &lt;a href="https://producthunt.com/stories/how-this-open-source-calendly-alternative-rocketed-to-product-of-the-day" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b76cdbdb7d233245e14e7e5060a94ee32b64a8585ef7daa914552bcf08998459/68747470733a2f2f63616c2e636f6d2f6d616b65722d6772616e742e737667" alt="Cal.com - The open source Calendly alternative | Product Hunt" width="250" height="54"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Built With&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/?ref=cal.com" rel="nofollow noopener noreferrer"&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trpc.io/?ref=cal.com" rel="nofollow noopener noreferrer"&gt;tRPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/?ref=cal.com" rel="nofollow noopener noreferrer"&gt;React.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailwindcss.com/?ref=cal.com" rel="nofollow noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://prisma.io/?ref=cal.com" rel="nofollow noopener noreferrer"&gt;Prisma.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.cal.com/daily" rel="nofollow noopener noreferrer"&gt;Daily.co&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contact us&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Meet our sales team for any commercial inquiries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cal.com/sales" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/298517cca77af25bc85b9663f5f3981400cbfff904619bffae30dae85f45d5a5/68747470733a2f2f63616c2e636f6d2f626f6f6b2d776974682d63616c2d6461726b2e737667" alt="Book us with Cal.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Stay Up-to-Date&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Cal.com officially launched as v.1.0 on the 15th of September 2021 and we've…&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/calcom/cal.com" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h3&gt;
  
  
  &lt;a href="https://documenso.com/" rel="noopener noreferrer"&gt;Documenso&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Documenso aims to be the world's most trusted document signing tool. This trust is built by empowering you to self-host Documenso and review how it works under the hood.&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%2F5yykm2lybfdnouj59if3.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%2F5yykm2lybfdnouj59if3.png" alt="Documenso" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&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/documenso" rel="noopener noreferrer"&gt;
        documenso
      &lt;/a&gt; / &lt;a href="https://github.com/documenso/documenso" rel="noopener noreferrer"&gt;
        documenso
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The Open Source DocuSign Alternative.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/13398220/271632689-a643571f-0239-46a6-a73e-6bef38d1228b.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDc4NjM4NjcsIm5iZiI6MTc0Nzg2MzU2NywicGF0aCI6Ii8xMzM5ODIyMC8yNzE2MzI2ODktYTY0MzU3MWYtMDIzOS00NmE2LWE3M2UtNmJlZjM4ZDEyMjhiLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MjElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTIxVDIxMzkyN1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWQ3ZGY4MmQ1MzFlYTQyNTQzYjRjMThiNjFjZDcxNzYwNGJkOTkzOWJjZmFlMTRmYmQyMmViNGEzYWM5Y2NhMDgmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.i60IDNGIBxplzuPlaOIxDszJRIc355Heo3m4m4e_iN8"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F13398220%2F271632689-a643571f-0239-46a6-a73e-6bef38d1228b.png%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDc4NjM4NjcsIm5iZiI6MTc0Nzg2MzU2NywicGF0aCI6Ii8xMzM5ODIyMC8yNzE2MzI2ODktYTY0MzU3MWYtMDIzOS00NmE2LWE3M2UtNmJlZjM4ZDEyMjhiLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MjElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTIxVDIxMzkyN1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWQ3ZGY4MmQ1MzFlYTQyNTQzYjRjMThiNjFjZDcxNzYwNGJkOTkzOWJjZmFlMTRmYmQyMmViNGEzYWM5Y2NhMDgmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.i60IDNGIBxplzuPlaOIxDszJRIc355Heo3m4m4e_iN8" alt="Documenso Logo"&gt;&lt;/a&gt;

&lt;p&gt;
  The Open Source DocuSign Alternative
  &lt;br&gt;
    &lt;a href="https://documenso.com" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Learn more »&lt;/strong&gt;&lt;/a&gt;
    &lt;br&gt;
    &lt;br&gt;
    &lt;a href="https://documen.so/discord" rel="nofollow noopener noreferrer"&gt;Discord&lt;/a&gt;
    ·
    &lt;a href="https://documenso.com" rel="nofollow noopener noreferrer"&gt;Website&lt;/a&gt;
    ·
    &lt;a href="https://github.com/documenso/documenso/issues" rel="noopener noreferrer"&gt;Issues&lt;/a&gt;
    ·
    &lt;a href="https://documen.so/live" rel="nofollow noopener noreferrer"&gt;Upcoming Releases&lt;/a&gt;
    ·
    &lt;a href="https://documen.so/roadmap" rel="nofollow noopener noreferrer"&gt;Roadmap&lt;/a&gt;
  &lt;/p&gt;
&lt;p&gt;
   &lt;a href="https://documen.so/discord" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/8e740f11667c6c351429c14226a3e495a22d37285e2aed0dd0d1062e1f08d837/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d646f63756d656e2e736f2f646973636f72642d253233353836354632" alt="Join Documenso on Discord"&gt;&lt;/a&gt;
   &lt;a href="https://github.com/documenso/documenso/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5229da1f18bddf651b39dc43db7b72b795fb777cc660a1f5086204fd87884bfc/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f646f63756d656e736f2f646f63756d656e736f" alt="Github Stars"&gt;&lt;/a&gt;
   &lt;a href="https://github.com/documenso/documenso/blob/main/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e9fda15ef5a15115c6b4a0b81ce0d77c6b56f5924a2174521b919a92acf09b4a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4147504c76332d707572706c65" alt="License"&gt;&lt;/a&gt;
   &lt;a href="https://github.com/documenso/documenso/pulse" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/91e24a7de0ff071cc9bb0fff0d6c2391845f84a6b1192d17448c6d118f65bfab/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6d6d69742d61637469766974792f6d2f646f63756d656e736f2f646f63756d656e736f" alt="Commits-per-month"&gt;&lt;/a&gt;
   &lt;a href="https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/documenso/documenso" rel="nofollow noopener noreferrer"&gt;
   &lt;img alt="open in devcontainer" src="https://camo.githubusercontent.com/7e81d44d76311da9bb4f60680a6354cc54e7072461664a8af18d2a6f9be91d70/68747470733a2f2f696d672e736869656c64732e696f2f7374617469632f76313f6c6162656c3d446576253230436f6e7461696e657273266d6573736167653d456e61626c656426636f6c6f723d626c7565266c6f676f3d76697375616c73747564696f636f6465"&gt;
   &lt;/a&gt;
   &lt;a href="https://github.com/documenso/documensoCODE_OF_CONDUCT.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/71217453f48cd1f12ba5a720412bb92743010653a5cc69654e627fd99e2e9104/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436f6e7472696275746f72253230436f76656e616e742d322e312d3462616161612e737667" alt="Contributor Covenant"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div&gt;
  &lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/13398220/312843205-d96ed533-6f34-4a97-be9b-442bdb189c69.gif?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDc4NjM4NjcsIm5iZiI6MTc0Nzg2MzU2NywicGF0aCI6Ii8xMzM5ODIyMC8zMTI4NDMyMDUtZDk2ZWQ1MzMtNmYzNC00YTk3LWJlOWItNDQyYmRiMTg5YzY5LmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MjElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTIxVDIxMzkyN1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTQ4MWYwMzMyNGJmODQyYWE3NTRkZDc4MWU3YTBhZGRjZTU5ZWM5YmJhOWM5MGNjMjg1OTY0MmVmNjI2ZjNhNzMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.o_xj8eEOT1UyrLuva17wEK6LvJps1B9rUbJchApz94U"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F13398220%2F312843205-d96ed533-6f34-4a97-be9b-442bdb189c69.gif%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDc4NjM4NjcsIm5iZiI6MTc0Nzg2MzU2NywicGF0aCI6Ii8xMzM5ODIyMC8zMTI4NDMyMDUtZDk2ZWQ1MzMtNmYzNC00YTk3LWJlOWItNDQyYmRiMTg5YzY5LmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MjElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTIxVDIxMzkyN1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTQ4MWYwMzMyNGJmODQyYWE3NTRkZDc4MWU3YTBhZGRjZTU5ZWM5YmJhOWM5MGNjMjg1OTY0MmVmNjI2ZjNhNzMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.o_xj8eEOT1UyrLuva17wEK6LvJps1B9rUbJchApz94U"&gt;&lt;/a&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;About Documenso&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Signing documents digitally should be fast and easy and should be the best practice for every document signed worldwide. This is technically quite easy today, but it also introduces a new party to every signature: The signing tool providers. While this is not a problem in itself, it should make us think about how we want these providers of trust to work. Documenso aims to be the world's most trusted document-signing tool. This trust is built by empowering you to self-host Documenso and review how it works under the hood.&lt;/p&gt;

&lt;p&gt;Join us in creating the next generation of open trust infrastructure.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Recognition&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;
  &lt;a href="https://www.producthunt.com/posts/documenso?utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_souce=badge-documenso" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0fad9d14232bbc29441d38085eaedeebfcbd50c36ad87fd873404640198136a0/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d333935303437267468656d653d6c6967687426706572696f643d6461696c79" alt="Documenso - The open source DocuSign alternative | Product Hunt" width="250" height="54"&gt;&lt;/a&gt;
  &lt;a href="https://www.producthunt.com/posts/documenso?utm_source=badge-featured&amp;amp;utm_medium=badge&amp;amp;utm_souce=badge-documenso" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/8c890da72ad497a81210455781ae58a3c3a7a845463a95a7f7c423b29e2e27d1/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f66656174757265642e7376673f706f73745f69643d333935303437267468656d653d6c69676874" alt="Documenso - The Open Source DocuSign Alternative. | Product Hunt" width="250" height="54"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Community and Next Steps 🎯&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;We're currently working on a redesign of the application, including a revamp of the codebase, so Documenso can be more intuitive to use…&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/documenso/documenso" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h3&gt;
  
  
  &lt;a href="https://formbricks.com/" rel="noopener noreferrer"&gt;Formbricks&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Formbricks is your go-to solution for in-product micro-surveys that will supercharge your product experience. Use micro-surveys to target the right users at the right time without making surveys annoying.&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%2Fjpbqizivkbbg1mp9j4zp.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%2Fjpbqizivkbbg1mp9j4zp.png" alt="Formbricks" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&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/formbricks" rel="noopener noreferrer"&gt;
        formbricks
      &lt;/a&gt; / &lt;a href="https://github.com/formbricks/formbricks" rel="noopener noreferrer"&gt;
        formbricks
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Open Source Qualtrics Alternative
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;Help us grow and star us on Github! ⭐️&lt;/p&gt;
&lt;p&gt;
&lt;a href="https://formbricks.com" rel="nofollow noopener noreferrer"&gt;
&lt;img width="120" alt="Open Source Privacy First Experience Management Solution Qualtrics Alternative Logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F72809645%2F264384676-0086704f-bee7-4d38-9cc8-fa42ee59e004.png%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDc4NjM4NjgsIm5iZiI6MTc0Nzg2MzU2OCwicGF0aCI6Ii83MjgwOTY0NS8yNjQzODQ2NzYtMDA4NjcwNGYtYmVlNy00ZDM4LTljYzgtZmE0MmVlNTllMDA0LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MjElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTIxVDIxMzkyOFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWUzZjBmNTBlYTNiMzUwZGRjZWQ2OTUyZmQ5OTFiMTJmNThlZTVlOTRmMzQ3YTliYTZjNTZiMmRkYWQ3NDVlYTYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.Pn5npu4icGf35fC70Ckn0T-SskBiZHBEXXXUvkGGl8c"&gt;
&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Formbricks&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;
The Open Source Qualtrics Alternative
&lt;br&gt;
&lt;a href="https://formbricks.com/" rel="nofollow noopener noreferrer"&gt;Website&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;a href="https://github.com/formbricks/formbricks/blob/main/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d03f3f82fa23c29cd11795cd7f3ea9cfde269e71c26b7368614a1aa3a4047e8c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4147504c2d707572706c65" alt="License"&gt;&lt;/a&gt; &lt;a href="https://github.com/formbricks/formbricks/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3a7c87f6770b3ae5644ac8b84031c55a3dade180bfbd44f2a1702b50bd1ea715/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f666f726d627269636b732f666f726d627269636b733f6c6f676f3d676974687562" alt="Github Stars"&gt;&lt;/a&gt;
&lt;a href="https://news.ycombinator.com/item?id=32303986" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/509dcae87353a7238131f119db0eb8313821af6d34a660e35e5243107a430d75/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4861636b65722532304e6577732d3132322d253233464636363030" alt="Hacker News"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/bf70024bbb5edaffa7288e95c858139681679719cfc3e573cf49245ca0d53b63/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f50726f6475637425323048756e742d3435352d6f72616e67653f6c6f676f3d70726f6475637468756e74266c6f676f436f6c6f723d253233666666"&gt;&lt;img src="https://camo.githubusercontent.com/bf70024bbb5edaffa7288e95c858139681679719cfc3e573cf49245ca0d53b63/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f50726f6475637425323048756e742d3435352d6f72616e67653f6c6f676f3d70726f6475637468756e74266c6f676f436f6c6f723d253233666666" alt="Product Hunt"&gt;&lt;/a&gt;
&lt;a href="https://github.blog/2023-04-12-github-accelerator-our-first-cohort-and-whats-next/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/efafeaa2d5405e298684bcb2ac82744a31e626773572b2db509e1ae3ca4d244e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f323032332d626c75653f6c6f676f3d676974687562266c6162656c3d476974687562253230416363656c657261746f72" alt="Github Accelerator"&gt;&lt;/a&gt;
&lt;a href="https://github.com/formbricks/formbricks/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0fe572a48280e6e692c8e9c5293c6d3cff49539da55554ce3cfd32be5907eef7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f48656c7025323057616e7465642d436f6e747269627574652d626c7565"&gt;&lt;/a&gt;
&lt;/p&gt;



&lt;div&gt;
&lt;p&gt;
&lt;i&gt;Trusted by&lt;/i&gt;&lt;br&gt;
  &lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/72809645/305124094-924d3693-f66a-4063-bb31-6e5789a8175a.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDc4NjM4NjgsIm5iZiI6MTc0Nzg2MzU2OCwicGF0aCI6Ii83MjgwOTY0NS8zMDUxMjQwOTQtOTI0ZDM2OTMtZjY2YS00MDYzLWJiMzEtNmU1Nzg5YTgxNzVhLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MjElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTIxVDIxMzkyOFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWMzNjQ3ZjAwMWE5YzA2ZDQ0OGVmZWZkYjJiZTkwM2MxMWQyYzdlNjI4Zjk2NDgxMGU3NTJmNWZkYzQ2MmM1OTcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.b3lS5BHXkLd8dKyJ2OM934EmlsZWsAMvh0eNSAxTens"&gt;&lt;img width="867" alt="clients-hi-res" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F72809645%2F305124094-924d3693-f66a-4063-bb31-6e5789a8175a.png%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDc4NjM4NjgsIm5iZiI6MTc0Nzg2MzU2OCwicGF0aCI6Ii83MjgwOTY0NS8zMDUxMjQwOTQtOTI0ZDM2OTMtZjY2YS00MDYzLWJiMzEtNmU1Nzg5YTgxNzVhLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MjElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTIxVDIxMzkyOFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWMzNjQ3ZjAwMWE5YzA2ZDQ0OGVmZWZkYjJiZTkwM2MxMWQyYzdlNjI4Zjk2NDgxMGU3NTJmNWZkYzQ2MmM1OTcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.b3lS5BHXkLd8dKyJ2OM934EmlsZWsAMvh0eNSAxTens"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;
&lt;a href="https://trendshift.io/repositories/2570" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/23dd9adfaba15cabc1c62b5b2d7acd4805b32914e4f676e8331b1dc040cad378/68747470733a2f2f7472656e6473686966742e696f2f6170692f62616467652f7265706f7369746f726965732f32353730" alt="Trendshift Badge for formbricks/formbricks" width="250" height="55"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ About Formbricks&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/e2c25e9b7c1fce99c6314379fb0341980f44529b2ae136956be878071bf8558b/68747470733a2f2f6769746875622d70726f64756374696f6e2d757365722d61737365742d3632313064662e73332e616d617a6f6e6177732e636f6d2f3637353036352f3234393434313936372d63636238396561332d383262342d346266322d386432632d3532383732316563333133622e706e67"&gt;&lt;img width="1527" alt="formbricks-sneak" src="https://camo.githubusercontent.com/e2c25e9b7c1fce99c6314379fb0341980f44529b2ae136956be878071bf8558b/68747470733a2f2f6769746875622d70726f64756374696f6e2d757365722d61737365742d3632313064662e73332e616d617a6f6e6177732e636f6d2f3637353036352f3234393434313936372d63636238396561332d383262342d346266322d386432632d3532383732316563333133622e706e67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Formbricks provides a free and open source surveying platform. Gather feedback at every point in the user journey with beautiful in-app, website, link and email surveys. Build on top of Formbricks or leverage prebuilt data analysis capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it out in the cloud at &lt;a href="https://app.formbricks.com/auth/signup" rel="nofollow noopener noreferrer"&gt;formbricks.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;💪 Mission: Empower your team, craft an irresistible experience.&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Formbricks is both a free and open source survey platform - and a privacy-first experience management platform. Use in-app, website, link and email surveys to gather user and customer insights at every point of their journey. Leverage Formbricks Insight Platform or build your own. Life's too short for mediocre UX.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Table of Contents&lt;/h3&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/formbricks/formbricks#features" rel="noopener noreferrer"&gt;Features&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/formbricks/formbricks#getting-started" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/formbricks/formbricks#cloud-version" rel="noopener noreferrer"&gt;Cloud Version&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/formbricks/formbricks#self-hosted-version" rel="noopener noreferrer"&gt;Self-hosted Version&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/formbricks/formbricks#development" rel="noopener noreferrer"&gt;Development&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/formbricks/formbricks#contribution" rel="noopener noreferrer"&gt;Contribution&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/formbricks/formbricks#contact-us" rel="noopener noreferrer"&gt;Contact&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/formbricks/formbricks#security" rel="noopener noreferrer"&gt;Security&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/formbricks/formbricks#license" rel="noopener noreferrer"&gt;License&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Features&lt;/h3&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;📲 Create &lt;strong&gt;conversion-optimized surveys&lt;/strong&gt; with our no-code editor with…&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;


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


&lt;h3&gt;
  
  
  &lt;a href="https://github.com/boxyhq/saas-starter-kit" rel="noopener noreferrer"&gt;Next.js SaaS Starter Kit&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Next.js based SaaS starter kit saves you months of development by starting you off with all the features that are the same in every product, so you can focus on what makes your app unique.&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%2Fg3p34z3aiyj5ast7atz6.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%2Fg3p34z3aiyj5ast7atz6.png" alt="Next.js SaaS Starter Kit" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&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/boxyhq" rel="noopener noreferrer"&gt;
        boxyhq
      &lt;/a&gt; / &lt;a href="https://github.com/boxyhq/saas-starter-kit" rel="noopener noreferrer"&gt;
        saas-starter-kit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🔥 Enterprise SaaS Starter Kit - Kickstart your enterprise app development with the Next.js SaaS boilerplate 🚀
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
  
  
  &lt;img alt="BoxyHQ Banner" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fboxyhq%2Fjackson%2Fassets%2F66887028%2Fb40520b7-dbce-400b-88d3-400d1c215ea1"&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;⭐ Enterprise SaaS Starter Kit&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;
    &lt;a href="https://github.com/boxyhq/saas-starter-kit/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5e43d0a0f3257e1aa43f2d8279881f51ba0b6409f61c6ba30f90ab55fd007ae0/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f626f787968712f736161732d737461727465722d6b6974" alt="Github stargazers"&gt;&lt;/a&gt;
    &lt;a href="https://github.com/boxyhq/saas-starter-kit/issues" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/47115d2a65b1da753e6f94ee76cf8724e42807328253516e942f9bc301667f68/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732f626f787968712f736161732d737461727465722d6b6974" alt="Github issues"&gt;&lt;/a&gt;
    &lt;a href="https://github.com/boxyhq/saas-starter-kit/blob/main/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5a956d34259ce354eca158a1646e20487b0f24da39be66a620b9ab531efda79e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f626f787968712f736161732d737461727465722d6b6974" alt="license"&gt;&lt;/a&gt;
    &lt;a href="https://twitter.com/BoxyHQ" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/02b4d4981e69026d82f914d6a1bb77f4a1f3c766b503be8589add8c8da2e924a/68747470733a2f2f696d672e736869656c64732e696f2f747769747465722f666f6c6c6f772f426f787948513f7374796c653d736f6369616c" alt="Twitter"&gt;&lt;/a&gt;
    &lt;a href="https://www.linkedin.com/company/boxyhq" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/dd2878e0e84abc79161f4658dff533060ad65a954fdc43b72445f9f7825d14e2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c696e6b6564496e2d626c7565" alt="LinkedIn"&gt;&lt;/a&gt;
    &lt;a href="https://discord.gg/uyb7pYt4Pa" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/54d3e3bd7a04964300a6e41394a65667819d2f526001483f33cc99b8de819add/68747470733a2f2f696d672e736869656c64732e696f2f646973636f72642f383737353835343835323335363330313330" alt="Discord"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;The Open Source Next.js SaaS boilerplate for Enterprise SaaS app development.&lt;/p&gt;

&lt;p&gt;Please star ⭐ the repo if you want us to continue developing and improving the SaaS Starter Kit! 😀&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📖 Additional Resources&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Video - &lt;a href="https://www.youtube.com/watch?v=oF8QIwQIhyo" rel="nofollow noopener noreferrer"&gt;BoxyHQ's SaaS Starter Kit: Your Ultimate Enterprise-Compliant Boilerplate&lt;/a&gt; &lt;br&gt;
Blog - &lt;a href="https://boxyhq.com/blog/enterprise-ready-saas-starter-kit" rel="nofollow noopener noreferrer"&gt;Enterprise-ready Saas Starter Kit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next.js-based SaaS starter kit saves you months of development by starting you off with all the features that are the same in every product, so you can focus on what makes your app unique.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🛠️ Built With&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org" rel="nofollow noopener noreferrer"&gt;Next.js&lt;/a&gt;
This is a React framework that provides features such as server-side rendering and static site generation. It's used for building the user interface of your application. The main configuration for Next.js can be found in &lt;code&gt;next.config.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.com" rel="nofollow noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;
This is a utility-first CSS framework for rapidly building custom user interfaces. It's used for styling the application. The…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/boxyhq/saas-starter-kit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  &lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;Supabase Studio&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Supabase is an open-source Firebase alternative. Supabase studio is a dashboard for managing your Supabase projects.&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%2Ff4ykmhgj4cqx5yxp1bc1.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%2Ff4ykmhgj4cqx5yxp1bc1.png" alt="Supabase Studio" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&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/supabase" rel="noopener noreferrer"&gt;
        supabase
      &lt;/a&gt; / &lt;a href="https://github.com/supabase/supabase" rel="noopener noreferrer"&gt;
        supabase
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The open source Firebase alternative. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/8291514/213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png#gh-light-mode-only"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F8291514%2F213727234-cda046d6-28c6-491a-b284-b86c5cede25d.png%23gh-light-mode-only"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/8291514/213727225-56186826-bee8-43b5-9b15-86e839d89393.png#gh-dark-mode-only"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F8291514%2F213727225-56186826-bee8-43b5-9b15-86e839d89393.png%23gh-dark-mode-only"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Supabase&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://supabase.com" rel="nofollow noopener noreferrer"&gt;Supabase&lt;/a&gt; is an open source Firebase alternative. We're building the features of Firebase using enterprise-grade open source tools.&lt;/p&gt;

&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 Hosted Postgres Database. &lt;a href="https://supabase.com/docs/guides/database" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Authentication and Authorization. &lt;a href="https://supabase.com/docs/guides/auth" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Auto-generated APIs
&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 REST. &lt;a href="https://supabase.com/docs/guides/api" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 GraphQL. &lt;a href="https://supabase.com/docs/guides/graphql" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Realtime subscriptions. &lt;a href="https://supabase.com/docs/guides/realtime" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li class="task-list-item"&gt;

 Functions

&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 Database Functions. &lt;a href="https://supabase.com/docs/guides/database/functions" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Edge Functions &lt;a href="https://supabase.com/docs/guides/functions" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li class="task-list-item"&gt;

 File Storage. &lt;a href="https://supabase.com/docs/guides/storage" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;

&lt;li class="task-list-item"&gt;

 AI + Vector/Embeddings Toolkit. &lt;a href="https://supabase.com/docs/guides/ai" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;

&lt;li class="task-list-item"&gt;

 Dashboard&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsupabase%2Fsupabase%2Fmaster%2Fapps%2Fwww%2Fpublic%2Fimages%2Fgithub%2Fsupabase-dashboard.png" alt="Supabase Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch "releases" of this repo to get notified of major updates.&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsupabase%2Fsupabase%2Fd5f7f413ab356dc1a92075cb3cee4e40a957d5b1%2Fweb%2Fstatic%2Fwatch-repo.gif" alt="Watch this repo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Documentation&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;For full documentation, visit &lt;a href="https://supabase.com/docs" rel="nofollow noopener noreferrer"&gt;supabase.com/docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To see how to Contribute, visit &lt;a href="https://github.com/supabase/supabase./DEVELOPERS.md" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Community &amp;amp; Support&lt;/h2&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/supabase/supabase/discussions" rel="noopener noreferrer"&gt;Community Forum&lt;/a&gt;. Best for: help with building, discussion about database best practices.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/supabase/supabase/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;. Best for: bugs and errors you encounter using Supabase.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://supabase.com/docs/support#business-support" rel="nofollow noopener noreferrer"&gt;Email Support&lt;/a&gt;. Best for: problems with your database or infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://discord.supabase.com" rel="nofollow noopener noreferrer"&gt;Discord&lt;/a&gt;. Best for: sharing your applications and hanging out with the community.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Supabase is a combination of open source tools. We’re building the features of Firebase using enterprise-grade, open source…&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/supabase/supabase" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h3&gt;
  
  
  &lt;a href="https://unkey.dev/" rel="noopener noreferrer"&gt;Unkey&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Open Source API Key Management. API authentication made easy.&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%2Fl0s0f6mqx5qnbszy523h.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%2Fl0s0f6mqx5qnbszy523h.png" alt="Unkey" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&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/unkeyed" rel="noopener noreferrer"&gt;
        unkeyed
      &lt;/a&gt; / &lt;a href="https://github.com/unkeyed/unkey" rel="noopener noreferrer"&gt;
        unkey
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Open source API management platform
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
    &lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Unkey&lt;/h1&gt;

&lt;/div&gt;
    &lt;div class="markdown-heading"&gt;
&lt;h5 class="heading-element"&gt;Open Source API authentication and authorization&lt;/h5&gt;

&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;a href="https://go.unkey.com" rel="nofollow noopener noreferrer"&gt;unkey.com&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contributing&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;We welcome contributions! To ensure your work doesn't conflict with ongoing work, please:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read our &lt;a href="https://engineering.unkey.com/contributing" rel="nofollow noopener noreferrer"&gt;contributing guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discuss your ideas with us in public channels (GitHub/Discord) BEFORE starting development&lt;/strong&gt; (No private DMs)&lt;/li&gt;
&lt;li&gt;Get approval from a core team member&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This helps us align efforts and prevents duplicated or conflicting work.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Let's talk&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://cal.com/team/unkey/user-interview?utm_source=banner&amp;amp;utm_campaign=oss" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Book us with Cal.com" src="https://camo.githubusercontent.com/298517cca77af25bc85b9663f5f3981400cbfff904619bffae30dae85f45d5a5/68747470733a2f2f63616c2e636f6d2f626f6f6b2d776974682d63616c2d6461726b2e737667"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Authors&lt;/h2&gt;

&lt;/div&gt;
&lt;a href="https://github.com/unkeyed/unkey/graphs/contributors" rel="noopener noreferrer"&gt;
  &lt;img src="https://camo.githubusercontent.com/a83912d954f5b63b821dc11b54af8572c7224aa196e0e3ac1645c44b58060c56/68747470733a2f2f636f6e747269622e726f636b732f696d6167653f7265706f3d756e6b657965642f756e6b6579"&gt;
&lt;/a&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Stats&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/93d4ac049f4d3c06efdec212076d850b9ce3b9a0979d978abe23bde458dcd537/68747470733a2f2f7265706f62656174732e6178696f6d2e636f2f6170692f656d6265642f376666666362356539346664306132376239633464366666653264376533323631646132643065342e737667"&gt;&lt;img src="https://camo.githubusercontent.com/93d4ac049f4d3c06efdec212076d850b9ce3b9a0979d978abe23bde458dcd537/68747470733a2f2f7265706f62656174732e6178696f6d2e636f2f6170692f656d6265642f376666666362356539346664306132376239633464366666653264376533323631646132643065342e737667" alt="Alt" title="Repobeats analytics image"&gt;&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/unkeyed/unkey" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
 

&lt;h3&gt;
  
  
  &lt;a href="https://typebot.io/" rel="noopener noreferrer"&gt;Typebot&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Typebot is an open-source chatbot builder. It allows you to create advanced chatbots visually, embed them anywhere on your web/mobile apps, and collect results in real time.&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%2F6j7yc5uribe4uufusetc.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%2F6j7yc5uribe4uufusetc.png" alt="Typebot" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&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/baptisteArno" rel="noopener noreferrer"&gt;
        baptisteArno
      &lt;/a&gt; / &lt;a href="https://github.com/baptisteArno/typebot.io" rel="noopener noreferrer"&gt;
        typebot.io
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      💬 Typebot is a powerful chatbot builder that you can self-host.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;br&gt;
&lt;p&gt;
&lt;a href="https://typebot.io" rel="nofollow noopener noreferrer"&gt;
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FbaptisteArno%2Ftypebot.io.%2F.github%2Fimages%2Fbanner.png" alt="Typebot banner"&gt;
&lt;/a&gt;
&lt;/p&gt;



&lt;p&gt;
Typebot is an Fair Source chatbot builder. It allows you to create advanced chatbots visually, embed them anywhere on your web/mobile apps, and collect results in real-time
&lt;/p&gt;

&lt;p&gt;
&lt;a href="https://github.com/baptistearno/typebot.io/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b9b216973becc30f861b9c74780f288ea7718806cfb78920c5b912d31869d30e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f626170746973746561726e6f2f74797065626f742e696f" alt="Github Stars"&gt;&lt;/a&gt;
&lt;a href="https://github.com/baptistearno/typebot.io/pulse" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/372f7aa498418785f1e83dd9a5e2d9e974a6a9a5dc7dbff5785d0ffbd6336202/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6d6d69742d61637469766974792f6d2f626170746973746561726e6f2f74797065626f742e696f" alt="Commits per month"&gt;&lt;/a&gt;
&lt;a href="https://docs.typebot.io/self-hosting/guides/docker" rel="nofollow noopener noreferrer"&gt;
&lt;img src="https://camo.githubusercontent.com/f72e32084125565fddadc91cb27d791afbdcaa604c5df74a6e612eebbc13feec/68747470733a2f2f696d672e736869656c64732e696f2f646f636b65722f70756c6c732f626170746973746561726e6f2f74797065626f742d6275696c646572"&gt;
&lt;/a&gt;
&lt;a href="https://github.com/baptistearno/typebot.io/blob/main/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e9fda15ef5a15115c6b4a0b81ce0d77c6b56f5924a2174521b919a92acf09b4a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4147504c76332d707572706c65" alt="License"&gt;
&lt;/a&gt;&lt;a href="https://status.typebot.io" rel="nofollow noopener noreferrer"&gt;&lt;img height="20px" src="https://camo.githubusercontent.com/285165edf282cdc7fd5c995cea0856725b95b423579448f7f1433f9a9455f580/68747470733a2f2f626574746572757074696d652e636f6d2f7374617475732d6261646765732f76312f6d6f6e69746f722f61396b662e737667" alt="Uptime"&gt;&lt;/a&gt;
&lt;a href="https://github.com/baptisteArno/typebot.io/issues/new?template=bug_report.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/ebaebdaee73de3ab2d37290995bdfd313e5e913a834e6a742ccf8161995796a4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5265706f72742061206275672d4769746875622d253233314638304330" alt="Report a bug"&gt;&lt;/a&gt;
&lt;a href="https://typebot.io/discord" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/71880b65030840b6c714734089f3ce69e69e6f2869f66e01aeda6c660432ac8a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4a6f696e20636f6d6d756e6974792d446973636f72642d253233343034454544" alt="Ask a question"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;
  &lt;b&gt;&lt;a href="https://app.typebot.io/register" rel="nofollow noopener noreferrer"&gt;Try Typebot&lt;/a&gt;&lt;/b&gt;
  •
  &lt;b&gt;&lt;a href="https://docs.typebot.io/" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/b&gt;

&lt;/h3&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Builder demo&lt;/h2&gt;
&lt;/div&gt;


  
    
    

    &lt;span class="m-1"&gt;demo.mp4&lt;/span&gt;
    
  

  

  


&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Typebot makes it easy to create advanced chatbots. It provides the building block that are adaptable to any business use case. I improve Typebot regularly with bug fixes, new features, and performance improvements regularly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chat builder&lt;/strong&gt; with 34+ building blocks such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💬 Bubbles: Text, Image / GIF, video, audio, embed.&lt;/li&gt;
&lt;li&gt;🔤 Inputs: Text, email, phone number, buttons, picture choice, date picker, payment (Stripe), file picker... inputs&lt;/li&gt;
&lt;li&gt;🧠 Logic: Conditional branching, URL redirections, scripting (Javascript), A/B testing&lt;/li&gt;
&lt;li&gt;🔌 Integrations: Webhook / HTTP requests, OpenAI, Google Sheets, Google Analytics, Meta Pixel, Zapier, Make.com, Chatwoot, More to come...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Theme&lt;/strong&gt; your chatbot to match your brand identity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎨 Customize the fonts, background, colors, roundness, shadows, and more&lt;/li&gt;
&lt;li&gt;💪…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/baptisteArno/typebot.io" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
  
&lt;h3&gt;
  
  
  &lt;a href="https://www.openstatus.dev/" rel="noopener noreferrer"&gt;OpenStatus&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;OpenStatus is an open-source monitoring system with a beautiful status page. You can choose to host it yourself or use our hosted version.&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%2F4tg2jnwwibk1zsyj2bp1.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%2F4tg2jnwwibk1zsyj2bp1.png" alt="OpenStatus" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&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/openstatusHQ" rel="noopener noreferrer"&gt;
        openstatusHQ
      &lt;/a&gt; / &lt;a href="https://github.com/openstatusHQ/openstatus" rel="noopener noreferrer"&gt;
        openstatus
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🏓  The open-source synthetic monitoring platform  🏓
    &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;h3 class="heading-element"&gt;OpenStatus&lt;/h3&gt;
&lt;/div&gt;
  &lt;p&gt;
  &lt;a href="https://status.openstatus.dev" rel="nofollow noopener noreferrer"&gt;
  &lt;img src="https://camo.githubusercontent.com/38ed91e0e35d6c3ed20b39749f54d4c891c2aa02ddaeee4d141db1d98e90ceb8/68747470733a2f2f7374617475732e6f70656e7374617475732e6465762f6261646765"&gt;
  &lt;/a&gt;
  &lt;/p&gt;
  &lt;p&gt;The Open-Source synthetic monitoring platform
    &lt;br&gt;
    &lt;a href="https://www.openstatus.dev" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Learn more »&lt;/strong&gt;&lt;/a&gt;
    &lt;br&gt;
    &lt;br&gt;
    &lt;a href="https://www.openstatus.dev/discord" rel="nofollow noopener noreferrer"&gt;Discord&lt;/a&gt;
    ·
    &lt;a href="https://www.openstatus.dev" rel="nofollow noopener noreferrer"&gt;Website&lt;/a&gt;
    ·
    &lt;a href="https://github.com/openstatushq/openstatus/issues" rel="noopener noreferrer"&gt;Issues&lt;/a&gt;
  &lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;About OpenStatus 🏓&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;OpenStatus is open-source synthetic monitoring platform.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Synthetic monitoring&lt;/strong&gt;: Monitor your website and APIs globally and receive
notifications when they are down or slow.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Recognitions 🏆&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://trendshift.io/repositories/1780" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/1456f5d15191099af651d619158145ae8d28c7d5b70c7794f914d19e2f7ff792/68747470733a2f2f7472656e6473686966742e696f2f6170692f62616467652f7265706f7369746f726965732f31373830" alt="openstatusHQ%2Fopenstatus | Trendshift" width="250" height="55"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=37740870" rel="nofollow noopener noreferrer"&gt;&lt;br&gt;
  &lt;img alt="Featured on Hacker News" src="https://camo.githubusercontent.com/92769b4b6728055aa28dc8bc0faa1ba255a481ee1eef69ea000f7a46a2663d82/68747470733a2f2f6861636b657262616467652e6e6f772e73682f6170693f69643d3337373430383730" width="250" height="55"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contact us 💌&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;If you are interested in our enterprise plan or need special features, please
email us at &lt;a href="https://github.com/openstatusHQ/openstatusmailto:ping@openstatus.dev" rel="noopener noreferrer"&gt;ping@openstatus.dev&lt;/a&gt; or book a
call&lt;br&gt;&lt;br&gt;
&lt;a href="https://cal.com/team/openstatus/30min" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Book us with Cal.com" src="https://camo.githubusercontent.com/298517cca77af25bc85b9663f5f3981400cbfff904619bffae30dae85f45d5a5/68747470733a2f2f63616c2e636f6d2f626f6f6b2d776974682d63616c2d6461726b2e737667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Roadmap 🗺️&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Here's our &lt;a href="https://openstatus.productlane.com/roadmap" rel="nofollow noopener noreferrer"&gt;roadmap&lt;/a&gt; feel free to
contribute to it.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contributing 🤝&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;If you want to help us building the best status page and alerting system, you
can check our
&lt;a href="https://github.com/openstatusHQ/openstatus/blob/main/CONTRIBUTING.MD" rel="noopener noreferrer"&gt;contributing guidelines&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Top Contributors&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/openstatushq/openstatus/graphs/contributors" rel="noopener noreferrer"&gt;&lt;br&gt;
  &lt;img src="https://camo.githubusercontent.com/b0eec28446b585df008c5595fddaa7886db6fe80e1c921a6edecc5367fd21ba8/68747470733a2f2f636f6e747269622e726f636b732f696d6167653f7265706f3d6f70656e73746174757368712f6f70656e737461747573"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Made with &lt;a href="https://contrib.rocks" rel="nofollow noopener noreferrer"&gt;Contrib.rocks&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Stats&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/b0170e19f96f2e55d8d6239e6fcc8802e839baea5b21f18f6cebe94ec737cdca/68747470733a2f2f7265706f62656174732e6178696f6d2e636f2f6170692f656d6265642f313830656565313539633031323866363833613330663135663531616333356264626439666134342e737667"&gt;&lt;img src="https://camo.githubusercontent.com/b0170e19f96f2e55d8d6239e6fcc8802e839baea5b21f18f6cebe94ec737cdca/68747470733a2f2f7265706f62656174732e6178696f6d2e636f2f6170692f656d6265642f313830656565313539633031323866363833613330663135663531616333356264626439666134342e737667" alt="Alt" title="Repobeats analytics image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tech stack 🥞&lt;/h2&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/" rel="nofollow noopener noreferrer"&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailwindcss.com/" rel="nofollow noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ui.shadcn.com/" rel="nofollow noopener noreferrer"&gt;shadcn/ui&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tinybird.co/?ref=openstatus.dev" rel="nofollow noopener noreferrer"&gt;tinybird&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://turso.tech/" rel="nofollow noopener noreferrer"&gt;turso&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orm.drizzle.team/" rel="nofollow noopener noreferrer"&gt;drizzle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://resend.com/" rel="nofollow noopener noreferrer"&gt;Resend&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://depot.dev/?utm_source=Opource=OpenStatus" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b8eaf4c5ce553c0dda2e82335dff6000e84a2e66d937e0283b6aa3c1ed3788ed/68747470733a2f2f6465706f742e6465762f6261646765732f6275696c742d776974682d6465706f742e737667" alt="Built with Depot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started 🚀&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;With Devbox&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;You can use &lt;a href="https://www.jetify.com/devbox/" rel="nofollow noopener noreferrer"&gt;Devbox&lt;/a&gt; and get started with the following commands:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Devbox
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;curl -fsSL https://get.jetify.com/devbox &lt;span class="pl-k"&gt;|&lt;/span&gt; bash&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Install project dependencies, build and start services
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;devbox services up&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&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/openstatusHQ/openstatus" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
 

&lt;p&gt;&lt;a href="https://saas-starter-kits.com/" rel="noopener noreferrer"&gt;Top SaaS Starter Kits &amp;amp; Boilerplates&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>nextjs</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Next.js 13 - Use Server Action to submit form data</title>
      <dc:creator>Kiran Krishnan</dc:creator>
      <pubDate>Thu, 31 Aug 2023 17:32:13 +0000</pubDate>
      <link>https://dev.to/devkiran/nextjs-13-use-server-action-to-submit-form-data-23f5</link>
      <guid>https://dev.to/devkiran/nextjs-13-use-server-action-to-submit-form-data-23f5</guid>
      <description>&lt;p&gt;Next.js 13 introduces a new feature called &lt;a href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions" rel="noopener noreferrer"&gt;Server Actions&lt;/a&gt;. It is built on top of React Actions.&lt;/p&gt;

&lt;p&gt;Server Actions are a way to run code on the server when a user performs an action on the client.&lt;/p&gt;

&lt;p&gt;For example, when a user submits a form, you can run code on the server to process the form data.&lt;/p&gt;

&lt;p&gt;In this article, we will learn how to use Server Actions to submit form data.&lt;/p&gt;

&lt;p&gt;Let's create a new Next.js app using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the experimental server actions feature by adding the following code to the &lt;code&gt;next.config.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;serverActions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new page with a signup form at &lt;code&gt;app/signup/page.tsx&lt;/code&gt; with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SignupPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"POST"&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;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&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="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&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;"email"&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;input&lt;/span&gt; &lt;span class="na"&gt;type&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;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&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;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Create Account&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&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;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This page can be accessed at &lt;code&gt;/signup&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now you can add a function within the page component to handle the form submission. This function will be called when the user submits the form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SignupPage&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;createAccount&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;formData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormData&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use server&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;name&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;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;email&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;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Validate the form data and save it to the database&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Form code&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;use server&lt;/code&gt; directive makes sure that this function is only executed on the server.&lt;/p&gt;

&lt;p&gt;Within the &lt;code&gt;createAccount&lt;/code&gt; function, we can access the form data using the &lt;code&gt;FormData&lt;/code&gt; API. We can then process the form data and save it to the database.&lt;/p&gt;

&lt;p&gt;Let's use this function to handle the form submission by adding the &lt;code&gt;action&lt;/code&gt; attribute to the form element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;createAccount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"POST"&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;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the complete code for the page component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SignupPage&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;createAccount&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;formData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormData&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use server&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;name&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;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;email&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;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;password&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;log&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="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;createAccount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"POST"&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;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&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="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&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;"email"&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;input&lt;/span&gt; &lt;span class="na"&gt;type&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;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&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;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Create Account&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&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;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>beginners</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Supabase ⚡ database functions</title>
      <dc:creator>Kiran Krishnan</dc:creator>
      <pubDate>Thu, 31 Aug 2023 04:21:01 +0000</pubDate>
      <link>https://dev.to/devkiran/supabase-database-functions-26k</link>
      <guid>https://dev.to/devkiran/supabase-database-functions-26k</guid>
      <description>&lt;p&gt;In this article, we will learn about Supabase database functions and how to invoke them from your app.&lt;/p&gt;

&lt;p&gt;Supabase database functions are &lt;a href="https://www.postgresql.org/docs/current/functions.html" rel="noopener noreferrer"&gt;PostgreSQL functions&lt;/a&gt; that you can invoke from your app using the Supabase client.&lt;/p&gt;

&lt;p&gt;Database function allow you to carry out operations that would normally take several queries and round trips in a single function within the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a table
&lt;/h2&gt;

&lt;p&gt;Let's create a new table called &lt;code&gt;restaurants&lt;/code&gt; and add some data to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;restaurants&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;phone_number&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;website&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add some restaurants to the table so we can test our database function. We'll add 5 restaurants to the table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;restaurants&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;website&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&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="s1"&gt;'Tasty Bites'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'A cozy bistro with a wide range of delicious dishes.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'123 Main St, Cityville'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+1234567890'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'info@tastybites.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'www.tastybites.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'approved'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;restaurants&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;website&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Sushi Delight'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Experience the finest sushi in town.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'456 Oak Ave, Urbantown'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+9876543210'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'hello@sushidelight.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'www.sushidelight.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'approved'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;restaurants&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;website&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Pizza Haven'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Authentic Italian pizzas made with love.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'789 Pine Rd, Villageland'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+5551234567'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'info@pizzahaven.net'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'www.pizzahaven.net'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;restaurants&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;website&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Spice Fusion'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'A fusion of flavors from around the world.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1010 Spice Blvd, Flavortown'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+1231231234'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'contact@spicefusion.co'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'www.spicefusion.co'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'approved'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;restaurants&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;website&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Café Euphoria'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'A serene café serving premium coffees and pastries.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'222 Serenity Ln, Tranquiltown'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+9879879876'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'info@cafeeuphoria.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'www.cafeeuphoria.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rejected'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Explore the database function syntax
&lt;/h2&gt;

&lt;p&gt;Here is the basic syntax for creating a database function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;return_type&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;DECLARE&lt;/span&gt;
    &lt;span class="c1"&gt;-- declare variables&lt;/span&gt;
  &lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="c1"&gt;-- function body&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A database function has the following parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;function_name&lt;/code&gt; is the name of the function.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;arguments&lt;/code&gt; are the arguments that the function accepts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;return_type&lt;/code&gt; is the type of value that the function returns.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;function body&lt;/code&gt; is the code that the function executes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Function that returns records
&lt;/h2&gt;

&lt;p&gt;Let's create a simple function that returns the all the records from the &lt;code&gt;restaurants&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;get_restaurants&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;SETOF&lt;/span&gt; &lt;span class="n"&gt;restaurants&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;restaurants&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&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 see the &lt;code&gt;get_restaurants&lt;/code&gt; function returns &lt;code&gt;SETOF restaurants&lt;/code&gt;. This means that the function returns a set of records from the &lt;code&gt;restaurants&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;You can invoke the function from the SQL editor or from your Next.js app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;get_restaurants&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call the function from your Next.js app using the Supabase client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="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="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_restaurants&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;p&gt;You can also write the same function using the &lt;code&gt;RETURNS TABLE&lt;/code&gt; syntax.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;get_restaurants_2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;phone_number&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;website&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;restaurants&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Function that returns specific columns
&lt;/h2&gt;

&lt;p&gt;If you want to ignore certain columns from the &lt;code&gt;restaurants&lt;/code&gt; table, you can do so by not including them in the &lt;code&gt;RETURNS TABLE&lt;/code&gt; syntax and select only the columns you want to return from the &lt;code&gt;restaurants&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;In this example, we only returns the &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;website&lt;/code&gt; columns from the &lt;code&gt;restaurants&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;get_restaurants_3&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;website&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;r&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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;website&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;restaurants&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Function that returns a single value
&lt;/h2&gt;

&lt;p&gt;You can also create a function that returns a single value. In this example, we return the total number of restaurants in the &lt;code&gt;restaurants&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;get_restaurants_count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;BEGIN&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;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;restaurants&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Function that accepts arguments
&lt;/h2&gt;

&lt;p&gt;You can also create a function that accepts arguments. In this example, we create a function that accepts a &lt;code&gt;status&lt;/code&gt; argument and returns all the restaurants with that status.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;get_restaurants_by_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r_status&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;SETOF&lt;/span&gt; &lt;span class="n"&gt;restaurants&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;restaurants&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r_status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&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 invoke the function using Supabase client as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="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="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_restaurants_by_status&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;r_status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;approved&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;



</description>
      <category>javascript</category>
      <category>supabase</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>BoxyHQ + Cerbos: Merging SSO and Authorization</title>
      <dc:creator>Kiran Krishnan</dc:creator>
      <pubDate>Mon, 24 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/boxyhq/boxyhq-cerbos-merging-sso-and-authorization-o01</link>
      <guid>https://dev.to/boxyhq/boxyhq-cerbos-merging-sso-and-authorization-o01</guid>
      <description>&lt;p&gt;In this article, we will see how to enable Enterprise SSO login (based on the SAML single sign-on protocol) using Okta for your Next.js app and relay role-based access directly from the Identity Provider to Cerbos.&lt;/p&gt;

&lt;p&gt;We'll be using BoxyHQ's open-source &lt;a href="https://dev.to/enterprise-sso"&gt;Enterprise SSO&lt;/a&gt; solution (called SAML Jackson) to interface with Okta. We'll use the principle of minimal UI and include only the necessary interface in our example application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to SAML single sign-on​
&lt;/h2&gt;

&lt;p&gt;Security Assertion Markup Language (SAML) was designed for traditional web applications in the early 2000s. The goal was to provide a seamless user experience for applications by federating authentication to an IdP.&lt;/p&gt;

&lt;p&gt;As a result, applications no longer had to maintain identities for users. All they had to do was to redirect the browser to the IdP which would then authenticate the user and return an assertion about the logged-in user.&lt;/p&gt;

&lt;p&gt;This assertion in effect was a token, asserting to the app that the user authenticated at the IdP and the assertion is valid for the set period contained within it. You can read more about SAML and other SSO protocols here.&lt;/p&gt;

&lt;p&gt;SAML continues to be a very popular choice of protocol when there's a need to provide a single sign-on (SSO) experience for an enterprise application. Microsoft has great resources explaining SAML authentication and SAML at a deeper level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of using SAML​
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Increased Security​
&lt;/h3&gt;

&lt;p&gt;SAML is at its heart a security standard and as it provides a single point of authentication that takes place in a secure environment it adds an extra layer of security to your service that most enterprise customers will ask for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved user experience​
&lt;/h3&gt;

&lt;p&gt;As a user, SAML is very simple and pleasant to use as you only have to log in once and then you can access all your external services on a dashboard with a single click. This saves the user time and makes their overall experience of your product better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reduces cost​
&lt;/h3&gt;

&lt;p&gt;Without SAML you have to maintain account information across multiple services but when you use SAML this is all managed by the IdP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to BoxyHQ​
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://boxyhq.com/" rel="noopener noreferrer"&gt;BoxyHQ&lt;/a&gt; implements and maintains an open-source project that handles all the service provider functionality for implementing SAML, hiding away all the complexity you read about above behind the more popular and well-understood OAuth 2.0 protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to Cerbos​
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cerbos.dev/" rel="noopener noreferrer"&gt;Cerbos&lt;/a&gt; is the open-core, language-agnostic, scalable authorization solution that makes user permissions and authorization simple to implement and manage by writing context-aware access control policies for your application resources.&lt;/p&gt;

&lt;p&gt;In this tutorial, we are using BoxyHQ as the service provider layer between our example Next.js application and Okta as the Identity Provider to showcase the benefits of syncing roles and permissions during SSO login.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of the example app​
&lt;/h2&gt;

&lt;p&gt;The example app is a simple Next.js app that uses &lt;a href="https://boxyhq.com/docs/jackson/overview" rel="noopener noreferrer"&gt;SAML Jackson for SAML SSO&lt;/a&gt; authentication and &lt;a href="https://docs.cerbos.dev/cerbos/latest/quickstart.html" rel="noopener noreferrer"&gt;Cerbos for authorization&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The app has two pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/&lt;/code&gt; - Display the authenticated user's profile and authorization decisions returned by Cerbos API.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/saml-connection&lt;/code&gt; - Update the SAML connection details.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The example app runs on port &lt;code&gt;3000&lt;/code&gt;. SAML Jackson and Cerbos are running on ports &lt;code&gt;5225&lt;/code&gt; and &lt;code&gt;3593&lt;/code&gt; respectively within Docker containers.&lt;/p&gt;

&lt;p&gt;You are free to use cloud versions of Cerbos, the instructions for those wouldn’t change a lot but have been avoided in this article to keep it short.&lt;/p&gt;

&lt;p&gt;The example app is configured to work with 2 roles: &lt;code&gt;app-admin&lt;/code&gt; and &lt;code&gt;app-user&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;app-admin&lt;/code&gt; role has access to all resources.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;app-user&lt;/code&gt; role has access to resources only the user owns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Take a look at the &lt;code&gt;cerbos/policies/contact.yaml&lt;/code&gt; file to see how the policies are defined for the example app. You can try changing the policies and see how the authorization decisions change in the app.&lt;/p&gt;

&lt;p&gt;The following diagram shows the architecture of the example app.&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%2Fjdvmrbf5ksothxa2s86w.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%2Fjdvmrbf5ksothxa2s86w.png" alt="example app architecture" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Setup the example app​
&lt;/h3&gt;

&lt;p&gt;Clone the example app repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/boxyhq/jackson-cerbos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the example app directory and run &lt;code&gt;npm install&lt;/code&gt; to install the dependencies.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd jackson-cerbos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;If you navigate to &lt;code&gt;http://localhost:3000&lt;/code&gt; on your browser, you should see the following 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%2Fi3olowlm1qnvdninwa7c.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%2Fi3olowlm1qnvdninwa7c.png" alt="example app home" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This would mean that the example app is running successfully and you can now proceed to the next step.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/boxyhq/jackson-cerbos/blob/main/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt; file repository has all details about the example app. You can refer to it if you have anything unclear in this article.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Setup SAML App in Okta​
&lt;/h3&gt;

&lt;p&gt;The next step is to set up a SAML app in Okta. If you don't have an Okta account, you can create a free developer account &lt;a href="https://developer.okta.com/signup/" rel="noopener noreferrer"&gt;https://developer.okta.com/signup/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alternatively, you can also use other SAML IdPs like Azure AD, OneLogin, Auth0, etc. The steps for those would be similar to the ones for Okta.&lt;/p&gt;

&lt;p&gt;In this article, we will use Okta as the IdP.&lt;/p&gt;

&lt;p&gt;If you are unfamiliar with creating a SAML app in Okta, you can follow the instructions &lt;a href="https://boxyhq.com/docs/jackson/sso-providers/okta" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You will want to use the &lt;strong&gt;Assertion Consumer Service URL&lt;/strong&gt; and &lt;strong&gt;Entity ID&lt;/strong&gt; from &lt;a href="http://localhost:5225/.well-known/saml-configuration" rel="noopener noreferrer"&gt;http://localhost:5225/.well-known/saml-configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Do not worry about the roles attribute yet, we’ll configure that in a later step once we have ensured the SSO login flow works correctly.&lt;/p&gt;

&lt;p&gt;Next, download the &lt;strong&gt;SAML Metadata XML file&lt;/strong&gt; from the Okta app. You will need this file in the next step.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Create a SAML connection in SAML Jackson​
&lt;/h3&gt;

&lt;p&gt;Now that we have the SAML app setup in Okta, we need to create a SAML connection in SAML Jackson.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;a href="http://localhost:3000/saml-connection" rel="noopener noreferrer"&gt;http://localhost:3000/saml-connection&lt;/a&gt; on your browser and paste the contents of the SAML Metadata XML file you downloaded in the previous step in the &lt;strong&gt;XML Metadata&lt;/strong&gt; field. Click on &lt;strong&gt;Create SAML Connection&lt;/strong&gt; to create the SAML connection.&lt;/p&gt;

&lt;p&gt;If you see the status &lt;strong&gt;SAML SSO Enabled&lt;/strong&gt; on the page, it means that the SAML connection was created successfully.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Login with SAML SSO​
&lt;/h3&gt;

&lt;p&gt;Now that we have the SAML connection setup, we can try logging in with SAML SSO.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;a href="http://localhost:3000/login" rel="noopener noreferrer"&gt;http://localhost:3000/login&lt;/a&gt; on your browser, enter the email address and click on &lt;strong&gt;Continue with SAML SSO&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You will be redirected to the Okta login page. Enter the credentials and click on &lt;strong&gt;Sign In&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You will be redirected back to the home page of the example app and you should see the Profile of the authenticated user.&lt;/p&gt;

&lt;p&gt;This means that you have successfully logged in with SAML SSO.&lt;/p&gt;

&lt;p&gt;Since we haven't configured the roles attribute yet, all the users will be assigned the &lt;strong&gt;app-user&lt;/strong&gt; role by default.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Configure the groups attribute in Okta App​
&lt;/h3&gt;

&lt;p&gt;Now that we have ensured that the SSO login flow works correctly, we can configure the groups attribute in Okta.&lt;/p&gt;

&lt;p&gt;Groups attribute allows you to map the groups in your identity provider to the roles in your application.&lt;/p&gt;
&lt;h4&gt;
  
  
  Create a group in Okta​
&lt;/h4&gt;

&lt;p&gt;First, we need to create a group in Okta. Navigate to the Directory &amp;gt; Groups from the left navigation menu.&lt;/p&gt;

&lt;p&gt;Click on &lt;strong&gt;Add group&lt;/strong&gt; and enter the group &lt;strong&gt;name&lt;/strong&gt; and &lt;strong&gt;description (optional)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next create two new groups: &lt;strong&gt;app-admin&lt;/strong&gt; and &lt;strong&gt;app-user&lt;/strong&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%2Fjiv80v5wbymx0xqzk0h7.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%2Fjiv80v5wbymx0xqzk0h7.png" alt="create okta group" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After creating the groups, you will see the groups listed in the Groups page.&lt;/p&gt;

&lt;p&gt;Next we need to add the users to the groups. Navigate to the group you just created and click on the &lt;strong&gt;People&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;Click on the &lt;strong&gt;Assign people&lt;/strong&gt; button and assign the users you want to add to the group.&lt;/p&gt;

&lt;p&gt;Repeat the same steps for the second group.Configure the groups attribute in Okta&lt;/p&gt;

&lt;p&gt;Now that we have created the groups in Okta, we need to configure the groups attribute in Okta.&lt;/p&gt;

&lt;p&gt;Navigate to the &lt;strong&gt;SAML Settings&lt;/strong&gt; tab in the Okta app and click on &lt;strong&gt;Edit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now click on the &lt;strong&gt;Configure SAML&lt;/strong&gt; tab and scroll down to the &lt;strong&gt;Group Attribute Statements&lt;/strong&gt; section.&lt;/p&gt;

&lt;p&gt;Click on &lt;strong&gt;Add Another&lt;/strong&gt; and enter the following values&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: groups&lt;/li&gt;
&lt;li&gt;Name format: Unspecified&lt;/li&gt;
&lt;li&gt;Filter: Starts with &lt;strong&gt;app-&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&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%2Fob7vuyidtobg5nvdi52z.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%2Fob7vuyidtobg5nvdi52z.png" alt="okta group statements" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the &lt;strong&gt;app-&lt;/strong&gt; prefix in the filter. This will ensure that only the groups that start with &lt;strong&gt;app-&lt;/strong&gt; are returned in the SAML response from Okta. So if you have other groups in Okta, they will not be returned in the SAML response. This is useful if you want to map only a subset of the groups in Okta to the roles in your application.&lt;/p&gt;

&lt;p&gt;There are also other filter options available in the Group Attribute Statements section. You can read more about them in the Okta documentation.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Test the authorization​
&lt;/h3&gt;

&lt;p&gt;Now that we have configured the &lt;strong&gt;group&lt;/strong&gt; attribute, we can test the authorization.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; on your browser and click the &lt;strong&gt;Sign Out&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;Let's try logging in with a user that has the &lt;strong&gt;app-admin&lt;/strong&gt; role.&lt;/p&gt;

&lt;p&gt;You will be redirected to the Okta login page. Enter the credentials and click on &lt;strong&gt;Sign In&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You will be redirected back to the home page of the example app and you should see the Profile of the authenticated user.&lt;/p&gt;
&lt;h4&gt;
  
  
  Access API authorized by Cerbos​
&lt;/h4&gt;

&lt;p&gt;In the &lt;strong&gt;Access API authorized by Cerbos&lt;/strong&gt; section, we've added a table to display Cerbos authorization decisions for the authenticated user for the resources in the example app.&lt;/p&gt;

&lt;p&gt;Here is the overview of the policies defined in the &lt;code&gt;cerbos/policies/contact.yaml&lt;/code&gt; file.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user with the &lt;strong&gt;app-admin&lt;/strong&gt; role can perform all actions on all resources.&lt;/li&gt;
&lt;li&gt;A user with the &lt;strong&gt;app-user&lt;/strong&gt; role can perform read and update actions on resources they own.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resourcePolicy: version: default resource: contact rules: - actions: ['read', 'create', 'update', 'delete'] effect: EFFECT_ALLOW roles: - app-admin - actions: ['read', 'update'] effect: EFFECT_ALLOW roles: - app-user condition: match: expr: request.resource.attr.author == request.principal.id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Guarded Routes​
&lt;/h4&gt;

&lt;p&gt;In the Guarded Routes section at the bottom of the page, we've added 3 routes to showcase how Cerbos can be used to guard routes in your application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route the &lt;em&gt;Admin user role can access&lt;/em&gt;: Can be accessed by the user with the app-admin role.&lt;/li&gt;
&lt;li&gt;Route the &lt;em&gt;User and Admin user roles can access&lt;/em&gt;: Can be accessed by the user with the app-user or app-admin role.&lt;/li&gt;
&lt;li&gt;Route the &lt;em&gt;User does not have access to&lt;/em&gt;: Can be accessed by the user who owns the resource.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Closing thoughts​
&lt;/h2&gt;

&lt;p&gt;In this article, we have seen how to integrate SAML SSO with Jackson and Cerbos. We have implemented authentication and authorization using SAML SSO, Jackson and Cerbos.&lt;/p&gt;

&lt;p&gt;This opens up whole new possibilities for enterprise apps using Jackson and Cerbos. I’d love to hear of all the cool apps and features you are going to build, please reach out to Jackson or Cerbos if you found this article useful.&lt;/p&gt;

&lt;p&gt;Access the source code for this article here: &lt;a href="https://github.com/boxyhq/jackson-cerbos" rel="noopener noreferrer"&gt;https://github.com/boxyhq/jackson-cerbos&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Learn More​
&lt;/h2&gt;

&lt;p&gt;To learn more about SAML Jackson and Cerbos, take a look at the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://boxyhq.com/" rel="noopener noreferrer"&gt;BoxyHQ Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cerbos.dev/" rel="noopener noreferrer"&gt;Cerbos Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://boxyhq.com/docs/jackson/overview" rel="noopener noreferrer"&gt;SAML Jackson Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cerbos.dev/cerbos/latest/index.html" rel="noopener noreferrer"&gt;Cerbos Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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/boxyhq" rel="noopener noreferrer"&gt;
        boxyhq
      &lt;/a&gt; / &lt;a href="https://github.com/boxyhq/jackson" rel="noopener noreferrer"&gt;
        jackson
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🔥 Streamline your web application's authentication with Jackson, an SSO service supporting SAML and OpenID Connect protocols. Beyond enterprise-grade Single Sign-On, it also supports Directory Sync via the SCIM 2.0 protocol for automatic user and group provisioning/de-provisioning. 🤩
    &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;SAML Jackson: Open Source Enterprise SSO And Directory Sync&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://bestpractices.coreinfrastructure.org/projects/7493" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3edfd6a63b93427a8b61cf54425dad9f98c5820f8ff2b3cfa9b1f57c4b8ac876/68747470733a2f2f626573747072616374696365732e636f7265696e6672617374727563747572652e6f72672f70726f6a656374732f373439332f6261646765" alt="OpenSSF Best Practices Badge"&gt;&lt;/a&gt;
&lt;a href="https://www.npmjs.com/package/@boxyhq/saml-jackson" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/8ec58b9b815dd8f3e5ef976fb5441757beb1e3b4e22b1ca371f4e33810a722c8/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64742f40626f787968712f73616d6c2d6a61636b736f6e" alt="NPM downloads badge"&gt;&lt;/a&gt;
&lt;a href="https://hub.docker.com/r/boxyhq/jackson" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a4e44d8ab9a1a3aa1da716a95c7e41bc803315b19631afe301021c417c921656/68747470733a2f2f696d672e736869656c64732e696f2f646f636b65722f70756c6c732f626f787968712f6a61636b736f6e" alt="Docker pull statistics badge"&gt;&lt;/a&gt;
&lt;a href="https://github.com/boxyhq/jackson/blob/main/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/6ff0c3e58dbb0437ba71a92c418dddd20aec82b64f259f6abd2691b0d25b1ba1/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f626f787968712f6a61636b736f6e" alt="Apache 2.0 license badge"&gt;&lt;/a&gt;
&lt;a href="https://github.com/boxyhq/jackson/issues" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/991d409e68ba02dec61ad2f341df5fcebd7a4010f9f4673f5d90f748501574ef/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732f626f787968712f6a61636b736f6e" alt="Open Github issues badge"&gt;&lt;/a&gt;
&lt;a href="https://github.com/boxyhq/jackson/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/15549a398b9d29bae35a3876b171447f8933e7a4b070460f1d16b2664a61a219/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f626f787968712f6a61636b736f6e" alt="Github stargazers"&gt;&lt;/a&gt;
&lt;a href="https://www.npmjs.com/package/@boxyhq/saml-jackson" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e0583d7d459e91813d672b07e0be3dfd1f8e27c138784f6cb2c373573ad9ea7e/68747470733a2f2f696d672e736869656c64732e696f2f6e6f64652f762f40626f787968712f73616d6c2d6a61636b736f6e" alt="Nodejs version support badge"&gt;&lt;/a&gt;
&lt;a href="https://raw.githubusercontent.com/boxyhq/jackson/main/swagger/swagger.json" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e2a3acac954e001cb770e33b7afd1bee1aa4b3876d04a30074e667e8740096ec/68747470733a2f2f696d672e736869656c64732e696f2f737761676765722f76616c69642f332e303f7370656355726c3d68747470732533412532462532467261772e67697468756275736572636f6e74656e742e636f6d253246626f787968712532466a61636b736f6e2532466d61696e25324673776167676572253246737761676765722e6a736f6e" alt="Swagger Validator badge"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;SAML Jackson bridges or proxies a SAML login flow to OAuth 2.0 or OpenID Connect, abstracting away all the complexities of the SAML protocol. It also supports Directory Sync via the SCIM 2.0 protocol for automatic user and group provisioning/de-provisioning.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We now also support OpenID Connect providers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/boxyhq/jacksonsamljackson480.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fboxyhq%2Fjacksonsamljackson480.gif" alt="A quick demo of the admin portal without sound to show an overview of what to expect. It shows features such as SSO, the ability to set up SSO connections, Setup Links, Directory sync, and more"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Directory Sync&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;SAML Jackson also supports Directory Sync based on the SCIM 2.0 protocol.&lt;/p&gt;
&lt;p&gt;Directory sync helps organizations automate the provisioning and de-provisioning of their users. As a result, it streamlines the user lifecycle management process by saving valuable organizational hours, creating a single truth source of the user identity data, and facilitating them to keep the data secure.&lt;/p&gt;
&lt;p&gt;For complete documentation, visit &lt;a href="https://boxyhq.com/docs/directory-sync/overview" rel="nofollow noopener noreferrer"&gt;boxyhq.com/docs/directory-sync/overview&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🌟 Why star this repository?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;If you find this project helpful, please consider supporting us by starring &lt;a href="https://github.com/boxyhq/jackson" rel="noopener noreferrer"&gt;the repository&lt;/a&gt; and sharing it with others. This helps others find the project…&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/boxyhq/jackson" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>authorization</category>
      <category>security</category>
      <category>sso</category>
    </item>
    <item>
      <title>How to Build a Custom Raycast Extension to Access Dev.to Articles</title>
      <dc:creator>Kiran Krishnan</dc:creator>
      <pubDate>Sat, 25 Feb 2023 18:41:05 +0000</pubDate>
      <link>https://dev.to/devkiran/how-to-build-a-custom-raycast-extension-to-access-devto-articles-2lj4</link>
      <guid>https://dev.to/devkiran/how-to-build-a-custom-raycast-extension-to-access-devto-articles-2lj4</guid>
      <description>&lt;p&gt;In this tutorial, we will build a custom &lt;a href="https://www.raycast.com/store" rel="noopener noreferrer"&gt;Raycast extension&lt;/a&gt; that fetches the latest articles from Dev.to and displays them in a list and opens the selected article in the browser.&lt;/p&gt;

&lt;p&gt;Visit the &lt;a href="https://developers.raycast.com/basics/getting-started#system-requirements" rel="noopener noreferrer"&gt;Raycast Doc&lt;/a&gt; to learn more about the system requirements you need to start building extensions.&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%2Fy0nmxn9ltyh4zspgt02h.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%2Fy0nmxn9ltyh4zspgt02h.png" width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a new extension
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Open the &lt;strong&gt;Create Extension&lt;/strong&gt; command in Raycast&lt;/li&gt;
&lt;li&gt;Choose the Template &lt;strong&gt;Static List&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name your extension &lt;strong&gt;Devto Articles&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name the command &lt;strong&gt;Latest Articles&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Extension&lt;/strong&gt; from the bottom right corner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will create a new directory called &lt;code&gt;devto-articles&lt;/code&gt; in your extensions directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the dependencies
&lt;/h2&gt;

&lt;p&gt;Now, let's dive into the code.&lt;/p&gt;

&lt;p&gt;Open the extension directory &lt;code&gt;devto-articles&lt;/code&gt; in your favorite code editor.&lt;/p&gt;

&lt;p&gt;Install the dependencies by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before continuing, let's install the &lt;code&gt;@raycast/api&lt;/code&gt; package. It contains a set of components that make it easier to build extensions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @raycast/utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build the extension
&lt;/h2&gt;

&lt;p&gt;Now we are ready to start building the extension.&lt;/p&gt;

&lt;p&gt;Open the &lt;code&gt;src/index.tsx&lt;/code&gt; file, this is where we will write the code that fetches the latest articles from Dev.to and displays them in a list.&lt;/p&gt;

&lt;p&gt;Remove the existing code from the &lt;code&gt;src/index.tsx&lt;/code&gt; so that we can start from scratch.&lt;/p&gt;

&lt;p&gt;Now, let's import the &lt;code&gt;useFetch&lt;/code&gt; hook from &lt;code&gt;@raycast/utils&lt;/code&gt;. &lt;code&gt;useFetch&lt;/code&gt; can be used to fetch data from an API.&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;ActionPanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;List&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;@raycast/api&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;useFetch&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;@raycast/utils&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;p&gt;Define the type of the article object to map the response from the API.&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Article&lt;/span&gt; &lt;span class="o"&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&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="nl"&gt;readable_publish_date&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="nl"&gt;comments_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;url&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's use the &lt;code&gt;useFetch&lt;/code&gt; hook to fetch the latest articles from Dev.to.&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isLoading&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="nx"&gt;articles&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useFetch&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Article&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://dev.to/api/articles&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;&lt;code&gt;useFetch&lt;/code&gt; returns an object with two properties: &lt;code&gt;isLoading&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;isLoading&lt;/code&gt; is a boolean that indicates whether the data is being fetched or not.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data&lt;/code&gt; is the response that is fetched from the API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's use the &lt;code&gt;List&lt;/code&gt; component to display the articles in a list.&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;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;List&lt;/span&gt; &lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Item&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"list-icon.png"&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comments_count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; comments`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;accessories&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;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readable_publish_date&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ActionPanel&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;Action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OpenInBrowser&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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;ActionPanel&lt;/span&gt;&lt;span class="p"&gt;&amp;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;))&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;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;List&lt;/code&gt; component takes an &lt;code&gt;isLoading&lt;/code&gt; prop that indicates whether the data is being fetched or not.&lt;/p&gt;

&lt;p&gt;It also takes a &lt;code&gt;children&lt;/code&gt; prop that is an array of &lt;code&gt;List.Item&lt;/code&gt; components.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ActionPanel&lt;/code&gt; is a component that contains a set of actions that can be performed on the selected item.&lt;/p&gt;

&lt;p&gt;In our case, we want to open the selected article in the browser. So, we will use the &lt;code&gt;Action.OpenInBrowser&lt;/code&gt; component to open the article in the browser when the user selects it.&lt;/p&gt;

&lt;p&gt;Here is the final code:&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;ActionPanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;List&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;@raycast/api&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;useFetch&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;@raycast/utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Article&lt;/span&gt; &lt;span class="o"&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&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="nl"&gt;readable_publish_date&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="nl"&gt;comments_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;url&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isLoading&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="nx"&gt;articles&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useFetch&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Article&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://dev.to/api/articles&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt; &lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Item&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"list-icon.png"&lt;/span&gt;
          &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comments_count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; comments`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;accessories&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;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readable_publish_date&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ActionPanel&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;Action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OpenInBrowser&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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;ActionPanel&lt;/span&gt;&lt;span class="p"&gt;&amp;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;))&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run the extension
&lt;/h2&gt;

&lt;p&gt;You can run the extension by running the following command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Find the extension in Raycast by searching for &lt;strong&gt;Devto Articles&lt;/strong&gt; and run the command &lt;strong&gt;Latest Articles&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>react</category>
    </item>
    <item>
      <title>How to validate Next.js API routes using Zod</title>
      <dc:creator>Kiran Krishnan</dc:creator>
      <pubDate>Wed, 22 Feb 2023 17:18:58 +0000</pubDate>
      <link>https://dev.to/devkiran/how-to-validate-nextjs-api-routes-using-zod-2133</link>
      <guid>https://dev.to/devkiran/how-to-validate-nextjs-api-routes-using-zod-2133</guid>
      <description>&lt;p&gt;In this article, you'll learn how to validate Next.js API routes using Zod.&lt;/p&gt;

&lt;p&gt;Let's create a new file called &lt;code&gt;/api/users.ts.&lt;/code&gt; The API endpoint &lt;code&gt;/api/users&lt;/code&gt; will accept POST requests with a JSON body.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&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;next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&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;NextApiRequest&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;NextApiResponse&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&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="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="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&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;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;405&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&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="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="s2"&gt;`Method &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Not Allowed`&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="c1"&gt;// Validate the request body&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="nf"&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="nf"&gt;json&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="s2"&gt;Success&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;h2&gt;
  
  
  Install Zod
&lt;/h2&gt;

&lt;p&gt;Let's install &lt;a href="https://github.com/colinhacks/zod" rel="noopener noreferrer"&gt;Zod&lt;/a&gt; using npm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;zod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Next, we will create the schema to represent the request body.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&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;zod&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;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&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="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;zipcode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;subcribe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&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 sample request body will look like this:&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kiran"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kiran@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"zipcode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subcribe"&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;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&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;
  
  
  Validate the request body
&lt;/h2&gt;

&lt;p&gt;Next step is to validate the request body using the above schema. We will use the &lt;code&gt;safeParse&lt;/code&gt; method to validate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&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;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// If the request body is invalid, return a 400 error with the validation errors&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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="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="nf"&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="nf"&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="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="s2"&gt;Invalid request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Now you can safely use the data to create a user&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;name&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;zipcode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subcribe&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the request validation fails, we will return a 400 status code with the validation errors. You can access the validation errors using the &lt;code&gt;errors&lt;/code&gt; property on the JSON response.&lt;/p&gt;

&lt;p&gt;Here is how the response will look like if the request body is invalid:&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;"error"&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;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"validation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invalid_string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"path"&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="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;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 can use the library &lt;a href="https://www.npmjs.com/package/zod-error" rel="noopener noreferrer"&gt;Zod Error&lt;/a&gt; to convert the validation errors to a more readable format.&lt;/p&gt;

&lt;p&gt;Here is the full code for the API endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&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;next&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;z&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;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&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;NextApiRequest&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;NextApiResponse&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&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="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="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;405&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&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="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="s2"&gt;`Method &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Not Allowed`&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;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&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="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;zipcode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;subcribe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&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;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="o"&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;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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="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="nf"&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="nf"&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="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="s2"&gt;Invalid request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="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;name&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;zipcode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subcribe&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;data&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="nf"&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="nf"&gt;json&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="s2"&gt;Success&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;Here is the curl command to test the API endpoint:&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;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; http://localhost:3000/api/users &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"name":"Kiran","email":"kiran@example.com","zipcode":"12345","subcribe":false}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're planning to build a SaaS app, then you might be interested in &lt;a href="https://github.com/devkiran/NextAPI" rel="noopener noreferrer"&gt;NextAPI&lt;/a&gt;. NextAPI is a Next.js RESTful API Starter for building SaaS Apps. NextAPI can handle the heavy lifting for common SaaS features such as authentication, team management, invites, subscriptions, and more.&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>NextAPI - A Next.js RESTful API Starter for building SaaS Apps</title>
      <dc:creator>Kiran Krishnan</dc:creator>
      <pubDate>Thu, 09 Feb 2023 11:15:46 +0000</pubDate>
      <link>https://dev.to/devkiran/nextapi-a-nextjs-restful-api-starter-for-building-saas-apps-5gcc</link>
      <guid>https://dev.to/devkiran/nextapi-a-nextjs-restful-api-starter-for-building-saas-apps-5gcc</guid>
      <description>&lt;p&gt;NextAPI is a Next.js RESTful API Starter designed specifically for building SaaS apps.&lt;/p&gt;

&lt;p&gt;NextAPI is a great starting point for anyone looking to build a SaaS app with Next.js.&lt;/p&gt;

&lt;p&gt;Look no further than NextAPI if you want to build a SaaS application with a fully functional backend in minutes.&lt;/p&gt;

&lt;p&gt;Learn more about &lt;a href="https://github.com/devkiran/NextAPI" rel="noopener noreferrer"&gt;NextAPI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With its powerful combination of Next.js, Supabase, and Prisma, it provides the perfect foundation for creating scalable and feature-rich SaaS apps.&lt;/p&gt;

&lt;p&gt;One of the biggest advantages of using NextAPI is that it takes care of the heavy lifting for common SaaS features such as authentication, team management, invites, subscriptions, and more.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NextAPI is currently in beta. Please report any bugs or issues you encounter on GitHub.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Built with
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://supabase.io/" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.prisma.io/" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react.email/" rel="noopener noreferrer"&gt;React Email&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Team Management&lt;/li&gt;
&lt;li&gt;Team Invites&lt;/li&gt;
&lt;li&gt;Subscriptions (Coming soon)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  API routes
&lt;/h2&gt;

&lt;p&gt;NextAPI comes with a set of API routes that you can use within your React components.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/api/auth/signup&lt;/td&gt;
&lt;td&gt;Sign up a new user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/api/auth/signin&lt;/td&gt;
&lt;td&gt;Sign in an existing user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/api/teams&lt;/td&gt;
&lt;td&gt;Create a new team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/api/teams&lt;/td&gt;
&lt;td&gt;Get all teams for user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/api/teams/:slug&lt;/td&gt;
&lt;td&gt;Get a team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;/api/teams/:slug&lt;/td&gt;
&lt;td&gt;Delete a team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;/api/teams/:slug&lt;/td&gt;
&lt;td&gt;Update a team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/api/teams/:slug/invites&lt;/td&gt;
&lt;td&gt;Create a new invite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/api/teams/:slug/invites&lt;/td&gt;
&lt;td&gt;Get all invites for team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/api/teams/:slug/invites/:inviteId&lt;/td&gt;
&lt;td&gt;Get an invite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;/api/teams/:slug/invites/:inviteId&lt;/td&gt;
&lt;td&gt;Delete an invite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/api/teams/:slug/members&lt;/td&gt;
&lt;td&gt;Get all members for team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;/api/teams/:slug/members/:memberId&lt;/td&gt;
&lt;td&gt;Update a member&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;/api/teams/:slug/members/:memberId&lt;/td&gt;
&lt;td&gt;Delete a member&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/api/me&lt;/td&gt;
&lt;td&gt;Get current user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;/api/me&lt;/td&gt;
&lt;td&gt;Update current user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/api/invites/:inviteId/accept&lt;/td&gt;
&lt;td&gt;Accept an invite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/api/invites/:inviteId/decline&lt;/td&gt;
&lt;td&gt;Reject an invite&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://documenter.getpostman.com/view/13376907/2s8ZDYWgZ1" rel="noopener noreferrer"&gt;API Documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Emails are sent for the following events
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When a new user signs up&lt;/li&gt;
&lt;li&gt;When a new invite is created&lt;/li&gt;
&lt;li&gt;When a user is added to a team&lt;/li&gt;
&lt;li&gt;When a user is removed from a team&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fork this repository&lt;/li&gt;
&lt;li&gt;Clone your forked repository&lt;/li&gt;
&lt;li&gt;Navigate to the project directory&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npm install&lt;/code&gt; to install dependencies&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npm run dev&lt;/code&gt; to start the development server&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Sending Emails with React Email and Next.js</title>
      <dc:creator>Kiran Krishnan</dc:creator>
      <pubDate>Sat, 21 Jan 2023 16:59:19 +0000</pubDate>
      <link>https://dev.to/devkiran/sending-emails-with-react-email-and-nextjs-32p6</link>
      <guid>https://dev.to/devkiran/sending-emails-with-react-email-and-nextjs-32p6</guid>
      <description>&lt;p&gt;In this tutorial, we will learn how to create and send HTML emails with React Email and Next.js&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react.email/" rel="noopener noreferrer"&gt;React Email&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nodemailer.com/about/" rel="noopener noreferrer"&gt;Nodemailer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Let's start with a new Next.js project. You can use the &lt;a href="https://nextjs.org/docs/api-reference/create-next-app" rel="noopener noreferrer"&gt;Next.js CLI&lt;/a&gt; to create a new project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app next-email &lt;span class="nt"&gt;--ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for the project to be created. Once the project is created, let's move into the project directory.&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;cd &lt;/span&gt;next-email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Email Template
&lt;/h2&gt;

&lt;p&gt;We will use the &lt;a href="https://react.email/" rel="noopener noreferrer"&gt;React Email&lt;/a&gt; to create a new email template.&lt;/p&gt;

&lt;p&gt;Let's install dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;react-email @react-email/html @react-email/text @react-email/section @react-email/container &lt;span class="nt"&gt;-E&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's create a new email template using the React Email components. This is a simple React component that will be converted to HTML email template.&lt;/p&gt;

&lt;p&gt;We will create a new file &lt;code&gt;emails/WelcomeTemplate.tsx&lt;/code&gt; and add the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Html&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;@react-email/html&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;Text&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;@react-email/text&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;Section&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;@react-email/section&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;Container&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;@react-email/container&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;WelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Section&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;heading&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;Hi&lt;/span&gt; &lt;span class="nx"&gt;there&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;paragraph&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;Welcome&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;our&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Container&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Section&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Html&lt;/span&gt;&lt;span class="err"&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="c1"&gt;// Styles for the email template&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#ffffff&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;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;20px 0 48px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;580px&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;heading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;32px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;700&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#484848&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;paragraph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;18px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#484848&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;h2&gt;
  
  
  Preview Email Template
&lt;/h2&gt;

&lt;p&gt;Add the following script in your &lt;code&gt;package.json&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preview-email&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;email dev&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;Now, you can preview your email template by running the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run preview-email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start a local server and open your email template in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send Email with Nodemailer
&lt;/h2&gt;

&lt;p&gt;Let's install Nodemailer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;nodemailer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;--save-dev&lt;/span&gt; @types/nodemailer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new helper file &lt;code&gt;lib/email.ts&lt;/code&gt; and add the following code. This will take care of sending emails using Nodemailer and the SMTP server.&lt;/p&gt;

&lt;p&gt;Find the source code on &lt;a href="https://github.com/devkiran/NextAPI/blob/main/lib/server/email/sendEmail.tsx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&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;nodemailer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;EmailPayload&lt;/span&gt; &lt;span class="o"&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;string&lt;/span&gt;
  &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Replace with your SMTP credentials&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;smtpOptions&lt;/span&gt; &lt;span class="o"&gt;=&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="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;SMTP_HOST&lt;/span&gt; &lt;span class="o"&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="nf"&gt;parseInt&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;SMTP_PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2525&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="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="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;SMTP_USER&lt;/span&gt; &lt;span class="o"&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="na"&gt;pass&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;SMTP_PASSWORD&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendEmail&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="nx"&gt;EmailPayload&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;transporter&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="nf"&gt;createTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;smtpOptions&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;transporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMail&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="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;SMTP_FROM_EMAIL&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="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;Make sure your &lt;code&gt;.env&lt;/code&gt; file contains the following environment variables.&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;SMTP_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;SMTP_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;SMTP_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;SMTP_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;SMTP_FROM_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's create a new file &lt;code&gt;pages/api/send-email.ts&lt;/code&gt; and add the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&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;next&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;render&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;@react-email/render&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;WelcomeTemplate&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;../../emails/WelcomeTemplate&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;sendEmail&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;../../lib/email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&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;NextApiRequest&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;NextApiResponse&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="nf"&gt;sendEmail&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;kiran@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;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome to NextAPI&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WelcomeTemplate&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="nf"&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="nf"&gt;json&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="s2"&gt;Email sent successfully&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;Now you can visit &lt;code&gt;http://localhost:3000/api/send-email&lt;/code&gt; to send the email.&lt;/p&gt;




&lt;p&gt;If you're planning to build a SaaS app, then you might be interested in &lt;a href="https://github.com/devkiran/NextAPI" rel="noopener noreferrer"&gt;NextAPI&lt;/a&gt;. NextAPI is a Next.js RESTful API Starter for building SaaS Apps. NextAPI can handle the heavy lifting for common SaaS features such as authentication, team management, invites, subscriptions, and more.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>HelpUp: Knowledge Base / Help Center Builder</title>
      <dc:creator>Kiran Krishnan</dc:creator>
      <pubDate>Wed, 07 Dec 2022 19:18:29 +0000</pubDate>
      <link>https://dev.to/devkiran/build-the-knowledge-base-helpcenter-and-scale-your-customer-support-5g9p</link>
      <guid>https://dev.to/devkiran/build-the-knowledge-base-helpcenter-and-scale-your-customer-support-5g9p</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;HelpUp&lt;/strong&gt; - A simple knowledge base builder. It helps you build a knowledge base for your customer support team. It is a self-hosted solution that you can deploy on your own infrastructure. It is built using Next.js, Supabase and MongoDB.&lt;/p&gt;

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

&lt;p&gt;Search No More: Build an application with full-text search capabilities using MongoDB Atlas and Atlas Search&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://helpup.vercel.app/" rel="noopener noreferrer"&gt;https://helpup.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://helpup.vercel.app/helpcenter/shop" rel="noopener noreferrer"&gt;Demo HelpCenter for a E-commerce Store&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&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%2Ffx8l99pce3ger33won6v.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%2Ffx8l99pce3ger33won6v.png" alt="Image description" width="800" height="462"&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%2Fgeku3pgxgium0erj1t9f.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%2Fgeku3pgxgium0erj1t9f.png" alt="Image description" width="800" height="462"&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%2Fdzoi8rvbg7vxd9ik40mh.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%2Fdzoi8rvbg7vxd9ik40mh.png" alt="Image description" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://www.loom.com/share/bdf078d628a44190a7d43913b60266c5" rel="noopener noreferrer"&gt;Watch demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://www.loom.com/share/3ed8a5bfb945461f9007470f05d3ba33" rel="noopener noreferrer"&gt;Watch demo&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;HelpUp is a simple knowledge base builder. It helps you build a knowledge base for your customer support team. You can add as many workspaces as you want. Each workspace has its own set of articles. You can group articles into collections. You can search articles by title, content.&lt;/p&gt;

&lt;p&gt;Here are some of the features of the app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an account&lt;/li&gt;
&lt;li&gt;Create and manage workspaces&lt;/li&gt;
&lt;li&gt;Create and manage collections&lt;/li&gt;
&lt;li&gt;Create and manage articles&lt;/li&gt;
&lt;li&gt;Customize the landing page of the help center&lt;/li&gt;
&lt;li&gt;Search articles by title and content&lt;/li&gt;
&lt;li&gt;Autocomplete search title&lt;/li&gt;
&lt;li&gt;Highlight search term in the article content&lt;/li&gt;
&lt;li&gt;Allow users to share feedback on the article&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/devkiran/helpup" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/devkiran/helpup/blob/main/LICENSE.md" rel="noopener noreferrer"&gt;MIT&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How I built it
&lt;/h3&gt;

&lt;p&gt;I built the app using Next.js, Supabase, and MongoDB. I used MongoDB Atlas to store and query the data.&lt;/p&gt;

&lt;p&gt;Let's take a look at the important parts of the app.&lt;/p&gt;
&lt;h4&gt;
  
  
  Search articles
&lt;/h4&gt;

&lt;p&gt;I created a &lt;strong&gt;MongoDB Atlas Search Index&lt;/strong&gt; &lt;code&gt;searchArticles&lt;/code&gt; to search the articles. I used the following mapping for the index.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mappings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dynamic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fields&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contentText&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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;string&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;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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;string&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;workspaceId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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;objectId&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;storedSource&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspaceId&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;Then created a &lt;strong&gt;Atlas Function&lt;/strong&gt; to search the articles. It is a simple function that takes the &lt;code&gt;searchTerm&lt;/code&gt; and &lt;code&gt;workspaceId&lt;/code&gt; as input and returns the search results. Here is the code for the function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &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="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;searchTerm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;query&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;docs&lt;/span&gt; &lt;span class="o"&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;services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;mongodb-atlas&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;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;helpup&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;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Article&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;aggregate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;$search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;searchArticles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;compound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;must&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;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;searchTerm&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&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;contentText&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="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;filter&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;equals&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workspaceId&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;workspaceId&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="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;span class="na"&gt;$project&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;title&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="na"&gt;slug&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="na"&gt;contentText&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="na"&gt;updatedAt&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="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;docs&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;Then I created a &lt;strong&gt;HTTPS Endpoint&lt;/strong&gt; &lt;code&gt;/searchArticles&lt;/code&gt; to call the above Atlas Function. I called this endpoint from Next.js &lt;code&gt;getServerSideProps&lt;/code&gt; function to get the search results. Here is the code to invoke the endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="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;NEXT_PUBLIC_ATLAS_APP_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/searchArticles`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;searchTerm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;searchTerm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspaceId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceId&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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="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;articles&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Autocomplete search
&lt;/h4&gt;

&lt;p&gt;I created another &lt;strong&gt;MongoDB Atlas Search Index&lt;/strong&gt; &lt;code&gt;autocompleteArticles&lt;/code&gt; to autocomplete the search title. I used the following mapping for the index.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mappings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dynamic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fields&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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;autocomplete&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;workspaceId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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;objectId&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;storedSource&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspaceId&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;Created a new &lt;strong&gt;Atlas Function&lt;/strong&gt; to autocomplete the search title. It is a simple function that takes the &lt;code&gt;searchTerm&lt;/code&gt; and &lt;code&gt;workspaceId&lt;/code&gt; as input and returns the search results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &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="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;searchTerm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;query&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;docs&lt;/span&gt; &lt;span class="o"&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;services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;mongodb-atlas&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;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;helpup&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;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Article&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;aggregate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;$search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;autocompleteArticles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;compound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;must&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;autocomplete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;searchTerm&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;title&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="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;filter&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;equals&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workspaceId&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;workspaceId&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="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;span class="na"&gt;$project&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;title&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="na"&gt;slug&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="na"&gt;contentText&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="na"&gt;updatedAt&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="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;docs&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 created another &lt;strong&gt;HTTPS Endpoint&lt;/strong&gt; &lt;code&gt;/autocompleteArticles&lt;/code&gt; to call the above Atlas Function. I called this endpoint from the client-side to get the autocomplete search results.&lt;/p&gt;

&lt;p&gt;Here is the link to the &lt;a href="https://github.com/devkiran/helpup/blob/main/components/docs/ArticleSearchBar.tsx" rel="noopener noreferrer"&gt;search and autocomplete code&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>github</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Enterprise-ready SaaS Starter Kit</title>
      <dc:creator>Kiran Krishnan</dc:creator>
      <pubDate>Tue, 27 Sep 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/boxyhq/enterprise-ready-saas-starter-kit-4p34</link>
      <guid>https://dev.to/boxyhq/enterprise-ready-saas-starter-kit-4p34</guid>
      <description>&lt;p&gt;Enterprise-ready SaaS Starter Kit is a &lt;strong&gt;Next.js&lt;/strong&gt; based SaaS Starter Kit that can save hundreds of development hours while building &lt;a href="https://boxyhq.com/blog/enterprise-readiness-made-simple" rel="noopener noreferrer"&gt;enterprise-ready SaaS apps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/boxyhq/saas-starter-kit" rel="noopener noreferrer"&gt;Kickstart your enterprise app development with Next.js SaaS Starter Kit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's look at some of the enterprise-ready features the SaaS kit offers.&lt;/p&gt;

&lt;h2&gt;
  
  
  SAML SSO​
&lt;/h2&gt;

&lt;p&gt;SAML stands for Security Assertion Markup Language. It is an XML-based open standard for transferring identity data between an identity provider (IdP) and a service provider (SP).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://boxyhq.com/blog/understanding-saml-sso-the-basics-from-the-solution-providers-side" rel="noopener noreferrer"&gt;Single Sign On (SSO)&lt;/a&gt; allows your customers to manage their team's users outside your built-in user table.&lt;/p&gt;

&lt;p&gt;SAML SSO is integrated with the help of &lt;a href="https://github.com/boxyhq/jackson" rel="noopener noreferrer"&gt;SAML Jackson&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Directory Sync (SCIM)​
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://boxyhq.com/blog/understanding-scim-and-directory-sync" rel="noopener noreferrer"&gt;Directory sync&lt;/a&gt; helps organizations automate the provisioning and de-provisioning of their users.&lt;/p&gt;

&lt;p&gt;As a result, it streamlines the user lifecycle management process by saving valuable organizational hours, creating a single truth source of the user identity data, and facilitating them to keep the data secure.&lt;/p&gt;

&lt;p&gt;Directory Sync is integrated with the help of &lt;a href="https://github.com/boxyhq/jackson#directory-sync" rel="noopener noreferrer"&gt;SAML Jackson&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks &amp;amp; Events​
&lt;/h2&gt;

&lt;p&gt;Webhooks are a way for systems to notify external applications that a specific event has occurred in your SaaS app without receiving a request.&lt;/p&gt;

&lt;p&gt;Webhooks are a great solution if the client does not know when an event will occur and wants to be notified in real-time.&lt;/p&gt;

&lt;p&gt;Webhook is integrated with the help of &lt;a href="https://github.com/svix/svix-webhooks" rel="noopener noreferrer"&gt;Svix&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Team Management​
&lt;/h2&gt;

&lt;p&gt;Teams describe the functionality that enables modern software to be coordinated and managed.&lt;/p&gt;

&lt;p&gt;Through Teams, SaaS app users invite others to collaboratively use the application with them by creating an account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Features​
&lt;/h2&gt;

&lt;p&gt;Let's also look at other standard features the SaaS kit offers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create account&lt;/li&gt;
&lt;li&gt;Sign in with Email and Password&lt;/li&gt;
&lt;li&gt;Sign in with Magic Link&lt;/li&gt;
&lt;li&gt;Update account&lt;/li&gt;
&lt;li&gt;Manage team&lt;/li&gt;
&lt;li&gt;Manage team members&lt;/li&gt;
&lt;li&gt;Invite users to the team&lt;/li&gt;
&lt;li&gt;Accept invitation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started​
&lt;/h2&gt;

&lt;p&gt;Please follow these simple steps to get a local copy up and running.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Setup​
&lt;/h3&gt;

&lt;p&gt;Clone or fork this GitHub repository&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/boxyhq/saas-starter-kit.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Go to the project folder​
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd saas-starter-kit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Install dependencies​
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Set up your .env file​
&lt;/h3&gt;

&lt;p&gt;Duplicate &lt;code&gt;.env.example&lt;/code&gt; to &lt;code&gt;.env&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;cp .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Set up database schema​
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Start the server​
&lt;/h3&gt;

&lt;p&gt;In a development environment:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Start the Prisma Studio​
&lt;/h3&gt;

&lt;p&gt;Prisma Studio is a visual editor for the data in your database.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributing guide​
&lt;/h2&gt;

&lt;p&gt;Contributions make the open-source community a fantastic place to learn, inspire, and create. Any contributions you make are greatly appreciated.&lt;/p&gt;

&lt;p&gt;We also invite new supporters to contribute to the repository if you are interested.&lt;/p&gt;

</description>
      <category>enterprisereadiness</category>
      <category>nextjstemplate</category>
      <category>saasstarterkit</category>
      <category>saas</category>
    </item>
    <item>
      <title>Open-source Next.js SaaS Starter Kit</title>
      <dc:creator>Kiran Krishnan</dc:creator>
      <pubDate>Wed, 21 Sep 2022 03:25:46 +0000</pubDate>
      <link>https://dev.to/devkiran/open-source-enterprise-saas-starter-kit-262g</link>
      <guid>https://dev.to/devkiran/open-source-enterprise-saas-starter-kit-262g</guid>
      <description>&lt;h2&gt;
  
  
  Enterprise SaaS Starter Kit
&lt;/h2&gt;

&lt;p&gt;Recently we (&lt;a href="https://github.com/boxyhq" rel="noopener noreferrer"&gt;BoxyHQ&lt;/a&gt;) launched an open-source Enterprise SaaS Starter Kit on GitHub. This repository is still in an early stage of development.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/boxyhq/saas-starter-kit" rel="noopener noreferrer"&gt;Kickstart your enterprise app development with Next.js SaaS Starter Kit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is Next.js based project that saves you months of development by starting you off with all the same features in every SaaS app, so you can focus on what makes your app unique.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Please follow these simple steps to get a local copy up and running.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Node.js (Version: &amp;gt;=15.x &amp;lt;17)&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;NPM&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  1. Setup
&lt;/h4&gt;

&lt;p&gt;Clone or fork this GitHub repository&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/boxyhq/saas-starter-kit.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Go to the project folder
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;saas-starter-kit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Install dependencies
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Set up your .env file
&lt;/h4&gt;

&lt;p&gt;Duplicate &lt;code&gt;.env.example&lt;/code&gt; to &lt;code&gt;.env&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;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  5. Set up database schema
&lt;/h4&gt;



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

&lt;/div&gt;



&lt;h4&gt;
  
  
  6. Start the server
&lt;/h4&gt;

&lt;p&gt;In a development environment:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  7. Start the Prisma Studio
&lt;/h4&gt;

&lt;p&gt;Prisma Studio is a visual editor for the data in your database.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;The app supports the following features as of now.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create account&lt;/li&gt;
&lt;li&gt;Sign in with Email and Password&lt;/li&gt;
&lt;li&gt;Sign in with Magic Link&lt;/li&gt;
&lt;li&gt;Sign in with SAML SSO&lt;/li&gt;
&lt;li&gt;Directory Sync (SCIM)&lt;/li&gt;
&lt;li&gt;Update account&lt;/li&gt;
&lt;li&gt;Create team&lt;/li&gt;
&lt;li&gt;Invite users to the team&lt;/li&gt;
&lt;li&gt;Manage team members&lt;/li&gt;
&lt;li&gt;Update team settings&lt;/li&gt;
&lt;li&gt;Configure SAML SSO&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Coming Soon
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Audit logs&lt;/li&gt;
&lt;li&gt;Unit and integration tests&lt;/li&gt;
&lt;li&gt;Dark mode&lt;/li&gt;
&lt;li&gt;Mobile-first UI&lt;/li&gt;
&lt;li&gt;Billing &amp;amp; subscriptions&lt;/li&gt;
&lt;li&gt;Internationalization&lt;/li&gt;
&lt;li&gt;Roles and Permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;Contributions make the open-source community a fantastic place to learn, inspire, and create. Any contributions you make are greatly appreciated.&lt;/p&gt;

&lt;p&gt;We also invite new supporters to contribute to the repository if you are interested.&lt;/p&gt;

&lt;p&gt;Here are some &lt;a href="https://github.com/boxyhq/saas-starter-kit/issues" rel="noopener noreferrer"&gt;open issues&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Discover a collection of &lt;a href="https://www.kirandev.com/saas-starter-kits" rel="noopener noreferrer"&gt;SaaS Starter Kits&lt;/a&gt; based on &lt;a href="https://www.kirandev.com/saas-starter-kits/nextjs" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;, Supabase, React.js, Remix, Ruby on Rails, Laravel, and Go.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>saas</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
