Back to Blog
Guide | Next.js

Full-stack Next.js with Nhost and Hasura

27 February 2021
Transparent lines
Banner of Full-stack Next.js with Nhost and Hasura

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:

  • Next.js - 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.

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 Next.js 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.


_10
npx create-next-app --example with-typescript-eslint-jest web-app

Creating a Next.js appCreating a Next.js 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.


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

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.

Creating a new project on NhostCreating a new project on Nhost

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.


_10
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.


_10
nhost login
_10
_10
# Make sure you run this in the root directory of your project
_10
nhost init
_10
_10
# Starts all four development servers
_10
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 ClI runningNhost ClI running

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.


_10
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.


_11
import 'tailwindcss/tailwind.css'
_11
import type { AppProps } from 'next/app'
_11
import { NhostApolloProvider } from '@nhost/react-apollo'
_11
_11
const MyApp = ({ Component, pageProps }: AppProps) => (
_11
<NhostApolloProvider gqlEndpoint={process.env.NEXT_PUBLIC_GRAPHQL_URL}>
_11
<Component {...pageProps} />
_11
</NhostApolloProvider>
_11
)
_11
_11
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.js 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.


_10
language="yaml"
_10
_10
REGISTRATION_CUSTOM_FIELDS=display_name
_10
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.


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

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


_14
import nhost from 'nhost-js-sdk'
_14
_14
const config = {
_14
/*
_14
Again, this value comes from an environment variable
_14
as mentioned above. In development, I've set the value
_14
to http://localhost:9001
_14
*/
_14
base_url: process.env.NEXT_PUBLIC_BACKEND_URL,
_14
}
_14
_14
nhost.initializeApp(config)
_14
_14
export const auth = nhost.auth()


_16
import 'tailwindcss/tailwind.css'
_16
_16
import type { AppProps } from 'next/app'
_16
import { NhostApolloProvider } from '@nhost/react-apollo'
_16
import { NhostAuthProvider } from '@nhost/react-auth'
_16
_16
import { auth } from '@lib/nhost'
_16
_16
const MyApp = ({ Component, pageProps }: AppProps) => (
_16
<NhostAuthProvider auth={auth}>
_16
<NhostApolloProvider gqlEndpoint={process.env.NEXT_PUBLIC_GRAPHQL_URL}>
_16
<Component {...pageProps} />
_16
</NhostApolloProvider>
_16
</NhostAuthProvider>
_16
)
_16
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.


_89
import { useState } from 'react'
_89
import Link from 'next/link'
_89
import { useRouter } from 'next/router'
_89
_89
import { auth } from '@lib/nhost'
_89
_89
const Login = () => {
_89
const router = useRouter()
_89
const [email, setEmail] = useState('')
_89
const [password, setPassword] = useState('')
_89
_89
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
_89
e.preventDefault()
_89
_89
try {
_89
await auth.login(email, password)
_89
router.push('/')
_89
} catch (error) {
_89
console.error(error)
_89
}
_89
}
_89
_89
return (
_89
<section className="flex h-screen flex-col items-center justify-center space-y-5 bg-indigo-900">
_89
<div className="rounded-none bg-white px-5 py-8 shadow-xl sm:w-10/12 sm:rounded-lg md:w-8/12 lg:w-6/12 xl:w-4/12">
_89
<h1 className="mb-5 text-left text-lg font-semibold text-gray-900">
_89
Log in to your account
_89
</h1>
_89
<form className="space-y-5" onSubmit={handleSubmit}>
_89
<label className="block">
_89
<span className="mb-1 block text-sm font-medium text-gray-700">
_89
Your Email
_89
</span>
_89
<input
_89
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"
_89
type="email"
_89
placeholder="farazpatankar@gmail.com"
_89
required
_89
value={email}
_89
onChange={(e) => setEmail(e.target.value)}
_89
/>
_89
</label>
_89
<label className="block">
_89
<span className="mb-1 block text-sm font-medium text-gray-700">
_89
Your Password
_89
</span>
_89
<input
_89
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"
_89
type="password"
_89
placeholder="••••••••"
_89
required
_89
value={password}
_89
onChange={(e) => setPassword(e.target.value)}
_89
/>
_89
</label>
_89
<button
_89
type="submit"
_89
className="group relative flex w-full justify-center rounded-md border border-transparent bg-indigo-800 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
_89
>
_89
<span className="absolute inset-y-0 left-0 flex items-center pl-3">
_89
<svg
_89
xmlns="http://www.w3.org/2000/svg"
_89
viewBox="0 0 20 20"
_89
fill="currentColor"
_89
className="h-5 w-5 text-indigo-500 group-hover:text-indigo-400"
_89
>
_89
<path
_89
fillRule="evenodd"
_89
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"
_89
clipRule="evenodd"
_89
/>
_89
</svg>
_89
</span>
_89
Sign in
_89
</button>
_89
</form>
_89
</div>
_89
<div className="text-center text-sm text-gray-400">
_89
<Link href="/forgot-password">
_89
<a className="text-indigo-200 underline hover:text-white">
_89
Forgot password
_89
</a>
_89
</Link>
_89
</div>
_89
</section>
_89
)
_89
}
_89
_89
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.


_10
curl --header "Content-Type: application/json" \
_10
--request POST \
_10
--data '{"email":"farazpatankar@gmail.com","password":"password"}' \
_10
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 flowLogin 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 consoleHasura console

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


_32
import Link from 'next/link'
_32
import { useAuth } from '@nhost/react-auth'
_32
_32
import { FullPageSpinner } from '@components/common/spinner'
_32
_32
const Home = () => {
_32
const { signedIn } = useAuth()
_32
_32
if (signedIn === null) return <FullPageSpinner />
_32
_32
return (
_32
<div className="flex h-screen w-full flex-col items-center justify-center">
_32
{!signedIn ? (
_32
<h1 className="mb-3 text-4xl font-bold text-purple-900">
_32
Thanks for logging in!
_32
</h1>
_32
) : (
_32
<>
_32
<h1 className="mb-3 text-4xl font-bold text-purple-900">
_32
Hello from Botler
_32
</h1>
_32
<Link href="/login">
_32
<a className="flex rounded-md border border-transparent bg-indigo-800 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
_32
Login
_32
</a>
_32
</Link>
_32
</>
_32
)}
_32
</div>
_32
)
_32
}
_32
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.

Full Sample Login FlowFull Sample Login Flow

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 DeploymentsNhost Deployments Vercel DeploymentsVercel Deployments

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!

Share this post

Twitter LogoLinkedIn LogoFacebook Logo