Free tier launching soon. Star our Github repo to keep up to date. 🙌
2021-02-28

Dynamically creating an account for Gumroad customers

FarazPatankar13
Faraz Patankar

This is the first post in a series of posts documenting my journey building Botler - your personal AI butler. In this post, I scaffold the initial application used to configure the bot according to the user's needs. In subsequent posts, we will go over some more complex features adding functionality to the bot itself.

A couple of prerequisites before we start. The assumption is that you know and have worked with React before, and it will also help if you at least had a basic idea of what Hasura is and what it does for us. In this post/tutorial, we use:

  • NextJS - To generate and build our React application with TypeScript. It's okay if you haven't worked with Next or TypeScript before. If you know and understand React, this post should be very easy to follow. You can also use plain old JS and/or React (without Next) by making some small changes along the way.

  • Nhost - We will be using Nhost for basically everything else. Creating a project on Nhost automatically sets up and deploys a Hasura instance which gives us a PostgreSQL database and GraphQL APIs. On top of this, you also get authentication (with social providers), storage, and serverless functions right out of the box.

A small note about Nhost being a paid service. You get a two-week free trial (no credit card required) and even after that, the starter plan only costs $4/mo. As a comparison, if you were to deploy a droplet on DigitalOcean (which I've done in the past) to run your Hasura instance, it'd cost you $5/mo and you'd still have to set everything up yourself. The amount of time you'll save not worrying about setting up authentication or storage alone makes signing up for Nhost worth it.

Also, this is probably going to be another huge post and huge posts can be overwhelming. If you get stuck somewhere, take a step back and read through the section again and you'll probably figure it out. If you're still stuck, feel free to reach out to me on Twitter and I'll respond as soon as possible.

And now that we finally have everything out of the way, let's build this.

Setting up a NextJS application

Let's start by setting up our Next app. There are several ways to do this but I think the easiest is to use the create-next-app CLI to scaffold the application with everything set up. By default, it uses the base template but you can use any of the official examples as starting points. My personal favorite is this one with TypeScript, ESLint, Prettier and Jest (with React testing library) already configured but feel free to use any of them.

npx create-next-app --example with-typescript-eslint-jest web-app
Scaffolding the Next app
Scaffolding the Next app

You should now have a Next app set up with whatever template you decided to go with. You can simply cd into it and run yarn dev to have your app running at http://localhost:3000/.

Adding TailwindCSS (Optional)

This step is optional (but recommended) but I add Tailwind to every single project I build. It comes with some great utility classes and really speeds up the initial development. Their guide for setting up Tailwind with Next is pretty straightforward and should be very easy to follow. I am also using the tailwindcss-forms plugin that they provide.

Configuring absolute imports (Optional)

Another optional (but recommended) thing I do in all my projects is set up absolute imports. Next makes this very easy by allowing you to do this in your jsconfig.json or tsconfig.json based on which way you went. The snippet is what mine looks like after configuring absolute imports and here is a link to the Next docs for the same.

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@pages/*": ["pages/*"],
      "@components/*": ["components/*"],
      "@lib/*": ["lib/*"]
    },
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "exclude": ["node_modules", ".next", "out"],
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"]
}

Setting up Nhost and Hasura

Now that we have our frontend application set up, let's take care of setting up our database, APIs, and authentication using Nhost.

Create an account (if you don't already have one) and then click the create project button to set up a new project. Add a name and a URL for your project and then select the trial plan. For the location, I generally just go with whatever region is closest to me (or to most of my users) and use that as a starting point. Once you enter all the details and click the button, Nhost should start creating your project. The flow is pretty quick and took about 90 seconds for me. You should now be able to click the Go to project button to view your project.

Create project flow
Create project flow

The project dashboard might be a bit overwhelming with all the options listed there but don't worry too much about it for now. We're going to go over all of them one by one when we use them within our app. Something really cool to note though is that by simply creating a new project, we already have a database and GraphQL APIs set up with Hasura along with authentication and storage.

The next thing we want to do is set up the Nhost CLI to work with our app locally. We can start by globally installing the CLI.

npm install -g nhost

Once that's done, we would need to log in to the CLI and initialize our project. After the initialization is complete, we should be able to run everything Nhost provides in our development environment.

nhost login

# Make sure you run this in the root directory of your project
nhost init

# Starts all four development servers
nhost dev

A few things to note here:

  • I got an error when running nhost init because I didn't have the Hasura CLI installed. If you see the same error, you can install the Hasura CLI through NPM by following the instructions here.
  • The next error I saw was when I tried to run nhost dev but I wasn't running Docker on my local machine. Turning Docker back on fixed this issue so if you don't already have [Docker] (and Docker Compose), you'd want to set those up as well before locally developing with Nhost.
  • If you run into any other issues, the CLI is open source so feel free to visit the repository and create an issue asking for help.

Once I successfully ran all the commands, I had all 4 services up and running and the Hasura console automatically open in a new tab.

nhost dev
nhost dev

Again, four different APIs and a lot of stuff present in the Hasura console but don't worry about any of it yet. We'll get to some of it in the next section itself!

Connecting our Next app to Hasura using Nhost

So far, we have individually set up our Next app, our database, and our APIs. There's really no point doing all this if they're all separate and can't talk to each other so let's fix that. In this section, we'll add a few libraries and connect our Next app to our Hasura instance using the endpoints provided by Nhost.

yarn add @nhost/react-apollo @apollo/client graphql graphql-tag

Now that we have installed these packages, we want to use the NhostApolloProvider from the @nhost/react-apollo package to communicate with Nhost. We can do that by wrapping our app with the provider and passing it our GraphQL endpoint.

pages/_app.tsx
import 'tailwindcss/tailwind.css'

import type { AppProps } from 'next/app'
import { NhostApolloProvider } from '@nhost/react-apollo'

const MyApp = ({ Component, pageProps }: AppProps) => (
  <NhostApolloProvider gqlEndpoint={process.env.NEXT_PUBLIC_GRAPHQL_URL}>
    <Component {...pageProps} />
  </NhostApolloProvider>
)

export default MyApp

Replace the contents of your _app.tsx file (you may have to create it, you can read more about the custom app with Next here) with the above snippet. The gqlEndpoint comes from an environment variable. We do this because we want the URL to be different when we're in our development environment vs. when we're in production.

When we set up Nhost locally, it created a .env.development file for us. We can open that file and add our new environment variable there. Below is a snippet of what it should look like once we've done that.

.env.development
REGISTRATION_CUSTOM_FIELDS=display_name
NEXT_PUBLIC_GRAPHQL_URL=http://localhost:8080/v1/graphql

Believe it or not, that's all we had to do to get our Next app to talk to our Hasura instance. We don't have to fiddle with any ApolloClient configuration because @nhost/react-apollo takes care of that for us. We can't really test this yet as we don't have any data in our database so let's fix that in the next section.

Authentication with Nhost

To help set up authentication with Nhost within our Next app, we will be installing two more packages. nhost-js-sdk will help us interact with Nhost's auth and storage and @nhost/react-auth will give us a provider to wrap our app with so we always have access to the auth data.

yarn add nhost-js-sdk @nhost/react-auth

Let's now configure these libraries so we can use them within our app.

lib/nhost.ts

import nhost from 'nhost-js-sdk'

const config = {
  /*
    Again, this value comes from an environment variable
    as mentioned above. In development, I've set the value
    to http://localhost:9001
  */
  base_url: process.env.NEXT_PUBLIC_BACKEND_URL,
}

nhost.initializeApp(config)

export const auth = nhost.auth()

pages/_app.tsx

import 'tailwindcss/tailwind.css'

import type { AppProps } from 'next/app'
import { NhostApolloProvider } from '@nhost/react-apollo'
import { NhostAuthProvider } from '@nhost/react-auth'

import { auth } from '@lib/nhost'

const MyApp = ({ Component, pageProps }: AppProps) => (
  <NhostAuthProvider auth={auth}>
    <NhostApolloProvider gqlEndpoint={process.env.NEXT_PUBLIC_GRAPHQL_URL}>
      <Component {...pageProps} />
    </NhostApolloProvider>
  </NhostAuthProvider>
)

export default MyApp

Basically, in the first snippet, we configure and initialize the auth function in the nhost-js-sdk, and in the next snippet, we pass it to the NhostAuthProvider so we can check the auth status throughout our application.

Now that everything is set up (for real this time), let's create a new page where our users can log in to the app.

A small caveat, Next creates a route for you whenever you create a new file in the pages directory. If you aren't using Next, you'd probably want to use something like React Router at this point.

pages/login.tsx

import { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'

import { auth } from '@lib/nhost'

const Login = () => {
  const router = useRouter()
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()

    try {
      await auth.login(email, password)
      router.push('/')
    } catch (error) {
      console.error(error)
    }
  }

  return (
    <section className="bg-indigo-900 h-screen flex flex-col items-center justify-center space-y-5">
      <div className="px-5 py-8 bg-white rounded-none shadow-xl sm:rounded-lg sm:w-10/12 md:w-8/12 lg:w-6/12 xl:w-4/12">
        <h1 className="mb-5 text-lg font-semibold text-left text-gray-900">
          Log in to your account
        </h1>
        <form className="space-y-5" onSubmit={handleSubmit}>
          <label className="block">
            <span className="block mb-1 text-sm font-medium text-gray-700">
              Your Email
            </span>
            <input
              className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
              type="email"
              placeholder="farazpatankar@gmail.com"
              required
              value={email}
              onChange={(e) => setEmail(e.target.value)}
            />
          </label>
          <label className="block">
            <span className="block mb-1 text-sm font-medium text-gray-700">
              Your Password
            </span>
            <input
              className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
              type="password"
              placeholder="••••••••"
              required
              value={password}
              onChange={(e) => setPassword(e.target.value)}
            />
          </label>
          <button
            type="submit"
            className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-800 hover:bg-indigo-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
          >
            <span className="absolute left-0 inset-y-0 flex items-center pl-3">
              <svg
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 20 20"
                fill="currentColor"
                className="h-5 w-5 text-indigo-500 group-hover:text-indigo-400"
              >
                <path
                  fillRule="evenodd"
                  d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
                  clipRule="evenodd"
                />
              </svg>
            </span>
            Sign in
          </button>
        </form>
      </div>
      <div className="text-sm text-center text-gray-400">
        <Link href="/forgot-password">
          <a className="text-indigo-200 underline hover:text-white">
            Forgot password
          </a>
        </Link>
      </div>
    </section>
  )
}

export default Login

So that's a lot of code and kind of a big snippet but you only really care about the handleSubmit function. I attached the whole file because I didn't want to share an incomplete example. To log our users in, we ask them for their email and password and then call the auth.login function that we get from the Nhost JS SDK.

Currently, when we try to log in, we will get an error because we don't have any users. So let's go ahead and create one using the /auth/register API by making a CURL request to it.

curl --header "Content-Type: application/json" \
  --request POST \
  --data '{"email":"farazpatankar@gmail.com","password":"password"}' \
  http://localhost:9001/auth/register

localhost:9001 is where the Auth and Storage server runs and /auth/register is the endpoint for creating a new user. I created one with my email, you can simply replace it with your own. Once we do this, we should be able to log in using the component we just created.

Login flow
Login flow

As you can see in the GIF above, we create a user using the CURL command I shared and then go back to our app and try to sign in. Our app then redirects us to the home page because that's what we've written in our handleSubmit function.

If you go ahead and open the Hasura console in your local environment, you'll notice that the user has been created there as well.

Hasura console
Hasura console

Let's also go ahead and update the code on our home page to accurately show the users' session status.

pages/index.tsx

import Link from 'next/link'
import { useAuth } from '@nhost/react-auth'

import { FullPageSpinner } from '@components/common/spinner'

const Home = () => {
  const { signedIn } = useAuth()

  if (signedIn === null) return <FullPageSpinner />

  return (
    <div className="h-screen w-full flex flex-col items-center justify-center">
      {!signedIn ? (
        <h1 className="text-purple-900 text-4xl font-bold mb-3">
          Thanks for logging in!
        </h1>
      ) : (
        <>
          <h1 className="text-purple-900 text-4xl font-bold mb-3">
            Hello from Botler
          </h1>
          <Link href="/login">
            <a className="flex py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-800 hover:bg-indigo-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
              Login
            </a>
          </Link>
        </>
      )}
    </div>
  )
}
export default Home

If you go through the snippet above, you'll notice that we're using the useAuth hook from @nhost/react-auth. This hook tells us whether the user is signed in or not. If the value of signedIn is null, it means that the hook is still trying to figure out whether or not the user is signed in. In that case, we show a spinner, and once we figure out the status, we show the content accordingly.

The GIF below demonstrates the flow mentioned above.

Entire flow from the home page
Entire flow from the home page

Deploying

If the existing features weren't enough, Nhost also has an excellent GitHub integration. If you connect your repository to the Nhost console, Nhost automatically deploys your changes whenever you push to the master or the main branch. You can also do deployments manually using the nhost deploy command. Here's a link to the docs for the same.

As for the Next.js app, you can deploy it to Vercel using the instructions mentioned here. Just make sure you add the proper environment variables to your project on Vercel; otherwise, things do not work as expected. So far, those would be the NEXT_PUBLIC_GRAPHQL_URL and the NEXT_PUBLIC_BACKEND_URL. You can find both these URLs in your Nhost dashboard.

On my end, I have integrated GitHub with both Vercel and Nhost. That way, whenever I push to my main branch, both services deploy automatically.

Nhost deployment
Nhost deployment
Vercel deployment
Vercel deployment

A few notes

  • We didn't build the registration flow. The reason for that is, I want to keep the platform invite-only for the time being. So the idea is, I add users manually, and then they can log in and configure everything they want to. You probably want to allow your users to sign up and to do that, you can refer to this example (specifically this file) by the Nhost team. You can also view all the available methods in the docs here.

  • We didn't do any error handling. Yep, not in this demo we didn't. But, we probably will in subsequent posts as and when we make the product more production-ready. I can see this turning into a pretty complicated app by the time we're done.

  • If you want to check the flow live, you can visit this URL. I have created a dummy user that you can log in with. The email is test@email.com and the password is password.

Closing

Well, that was quite a long post, wasn't it? But look at how much we achieved! We set up a NextJS app, a PostgreSQL database and GraphQL API using Hasura, authentication and storage using Nhost and we are already allowing people to log in to our app. We also have fully functional development and production environments along with automated deployments!

Let me know what you think about the article and if you have any feedback at all here. You can also tweet at me with any questions/issues you may run into while trying to build this and I'll try and respond as soon as possible.

Oh, and please keep an eye out for the follow-up posts as I am going to try and document the whole journey of building this!

What's next?
Did you find this blog post interesting? Follow Nhost on Twitter.
Subscribe to the newsletter
Future of app development - stay up-to-date.
Try Nhost for Free Today
Focus on your app and your users!

Serverless backend for modern web and mobile apps

© Nhost
githubtwitterdiscordyoutubelinkedin