Next x Nest - Connecting your NextJs App to a NestJs Backend
Tags: nextjs, nestjs, webdev, oauth2
Published: 2023-08-28 19:11:30

As a Fullstack dev, there are plenty of times when you’d be involved in the creation of the backend while also helping out in the frontend. As a Web3 dev, that’s especially true since most frontend developers might know of `react-query`, `swr` and `axios` but not of `ethersjs` and `viem`. So, it tends to become a habit of sorts to help on the front-end side.

Quite recently, our company has been considering NestJs as a candidate for the backend stack. Having used it before, it’s really a no-brainer as to why someone would not want to utilize NestJs. There are other alternatives but when compared to barebones, from-scratch development using Express, I would always prefer a framework like Nest. It helps focus the development strategies while staying true to the underlying library (be it `fastify` or `express`). In short, it helps avoid chaos.

In my recent series, I have explored the usage of NextJS. But those were particularly Web3-focused. All things considered, it seemed like a next step – to explore how to use NextJS with Next Auth and combine that with a NestJS backend.

## So, what to build & what would be the objectives?
But as with every series, a mini-project is needed. Seeing how creating a “Kanban Board App” is all the rage right now amongst frontend devs on Youtube using DND Kit, I thought why not give it a try ourselves? So, we will create an end-to-end sort of project in this series – A personal Kanban Board with:
1.	NestJS in the backend
2.	NextJs in the frontend
3.	Next Auth handling the authentication on the frontend
4.	Supabase for Auth DB on both ends
5.	NoSQL DB for the backend

The objectives here would be to answer the following questions through the mini-project:
1.	How to use NextAuth with Next Js and a Custom Backend?
2.	How to configure Next Auth’s Google Provider to work with a custom Backend?
3.	How to manage Authentication and Authorization when using Next Auth with Google or a similar provider and a custom Backend?
4.	Can the Access Token and ID tokens from Next Auth providers be utilized with NestJS?
5.	How to manage authentication on REST with a GraphQL backend in NestJs?
6.	Can multiple Prisma providers be used in a single NestJS backend? If so, then how?

We won’t be answering all the questions outright in this article. In fact, along the way, there might be more questions added to the list. Rest assured, these questions will be answered by the time we reach the end of the series.

In this article, we will focus on how to use Next Auth with NextJs and make it talk securely with the NestJs Backend. Things become easier if we use `CredentialsProviders` of Next-Auth. If used with a NestJs Backend, one needs to utilize the `authorize()` method. Once the user enters the credentials on the frontend, this callback is invoked. The `authorize()` callback will send the credentials to the NestJs backend’s API. The NestJS Backend Auth API will receive the credentials (username and password or something similar) and if the user exists will send back an access token (JWT) or something. Back in the `authorize()` callback, when the access token is received, that will be included in the User’s session in the Next Auth through the `session` callback. Simple as that.

Things become a bit complicated with Next and Next Auth if you take into account Providers like Google, Gitlab, Github, and so on. These providers send back `access_token` and other similar tokens to next auth and are tied tightly to work seamlessly with NextJs and optionally a DB. The USP of NextJS (being a Fullstack framework aiming to provide completeness out of the box) also becomes a burden on some Devs trying to customize things to suit the use case.

What many devs fail to realize is that Next Auth, through these providers, is supposed to provide Authentication (**authN**). The Authorization (**authZ**) part of management still rests with us. It Must be configured by us. Say if the Google Provider provides `access_token` and `id_token`, should these be used for authZ? No. One should create their own methods of authZ. This can range from simple second JWTs in the session to LDAP.

But where to insert this mechanism? How do we insert it so that it can also function with a custom backend? One cannot simply leave the backend open to all requests. AuthN and AuthZ is needed there as well.

With these questions in mind, let’s get started with this series.

## Housekeeping
Before we jump right into coding, there are some steps we need to take. This tutorial assumes basic familiarity with NestJS and NextJS. Knowledge of next auth is a plus but not mandatory. We would also need to use Supabase and Prisma. So, the reader should be familiar with the basics there as well.

### Setting up Supabase
Just head over to supabase.com and signup if you haven’t already. Create a new project under the organization of your choice. In my case, I have selected the default org (called *abhik-99*). The project I have created is called *“Next Fullstack”* as shown in the image below. The image below shows the Supabase Dashboard after creating the project.


![Supabase](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yyogn9jsl417aey14ob4.png)

We will use the Next Auth quickstarts to get up and running fast. Click on your project, then click on the “SQL Editor” icon from the left panel (1.). Head over to *“Quickstarts”* as shown below (2.). After that click on *“Next Auth Schema Setup”* option as marked below (3.). 


![Supabase Project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xcm32qf54o0gtfi4m363.png)

As shown in the image below, this will open the SQL Editor with the Statements to run (1.). Click on *Run* to execute those SQL statements (2.). Supabase will run those and create your tables for you. The schema name would be `next_auth`.

![Supabase Next Auth Setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/68ldj70dgkvo1pjj3jmm.png)


You might be tempted to think that this is it. But this is just half the process. Our schema is created. But by default, it is created as a private schema. So, while we can manipulate it from Supabase Dashboard, we will not be able to connect to the Supabase instance and query it. To make sure that the schema we created can be accessed using something like *Prisma*, we need to make it public. To make sure that queries can be run, we need to tell Supabase to insert a `search_path` for the schema. These two are separate steps which we will cover next.

![Supabase Search Path](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62n9ygnbkxhmoulfhesx.png)

As shown in the image above, to make our custom Supabase Schema accessible and query-able, we need to go to *Settings* (1.). After this, click on *API* (2.). This option will show you the essentials like the Service role key, anon key, and more. Scroll down to the *API Settings*. Here, we need to add our custom schema which in our case is `next_auth` (3.). This needs to be followed up with adding the same schema to the *Extra Search Path* option (4.). Click on *Save* to make sure these changes take effect.

This completes our Supabase setup. Next, we will move to setting up our NextJs and NestJs projects inside a mono repo.

### Setting up NextxNest Project
Typically, one would first initialize the monorepo first and then add the required projects. But we won’t follow the general script of steps here.


![Rock](https://media.tenor.com/kEdtBWkn2vEAAAAC/the-rock-look.gif)

We will first set up our sub-projects and then create a monorepo out of it all. This roundabout way of doing things will help answer the question “How do we create a Monorepo from an existing project?”. We will use *Turborepo* to handle the monorepo part of things and use NPM as the package manager.

#### Initializing NextJS
Setting up NextJs is really simple. All you need to run is `npx create-next-app@latest`. This will install `create-next-app` if you don’t have it already and then direct you through a series of questions. In our case, albeit the project name, everything else will be set to the default setting.

We will call our NextJS 13 project **“frontend”**. Following the prompts from the script, you should have a folder called `frontend`. This will be where we create (unsurprisingly) the frontend for our Kanban project. Integrating Next Auth and Supabase into NextJS will be covered in the later sections.

#### Initializing NestJS
The more convenient way of using NestJs is to install the NestJS CLI globally and use it again and again to create new projects, controllers, sub-apps, and microservices. Running `npm I -g @nestjs/cli` will do precisely that for you.

Assuming you have already done that, move to the parent folder of *`frontend`* and run `nest new backend`. If you get an error that the `nest` script cannot be found or something even after installing Nest CLI, try running `npx nest new backend` to see if it works.

This will create a Nest project called `backend` at the same level as our Next project called `frontend`. Make sure to remove the `node_modules` folders from both. Furthermore, move to the `package.json` inside `backend` NestJS project and rename the script `start:dev` to just `dev`. This will match the NestJs dev script with the dev script name in the NextJS project.

Additionally, find the `main.ts` file of your Nest project and change the PORT from the default `3000` to `3001`. This will prevent a port collision between the NextJS project and our backend.

#### Creating a Monorepo
Lastly, we will create a Turborepo monorepo out of an already existing project. In the folder containing `backend` and `frontend`, run `npm init`. This will create the `package.json` we need. Once you have the `package.json`, we need to add `turbo` dependencies and the scripts along with denoting the *workspaces*. 

Create a folder called **`apps`** and move the `backend` and `frontend` folders inside it. The `apps` folder is our workspace. The complete `package.json` will look something similar to the script below. 

```json
{
  "name": "your-jira",
  "version": "1.0.0",
  "description": "An end to end mini-project which explores using NextJs, Next Auth on the frontend and NestJs backend by making a lightweight Kanban board called \\\"Your Jira\\\".",
  "workspaces": [
    "apps/*"
  ],
  "keywords": [
    "nest",
    "next",
    "turborepo",
    "next-auth",
    "supabase"
  ],
  "author": "Abhik Banerjee",
  "packageManager": "npm@9.6.3",
  "devDependencies": {
    "eslint": "^8.47.0",
    "prettier": "^3.0.2",
    "tsconfig": "*",
    "turbo": "latest"
  },
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "format": "prettier --write \"**/*.{ts,tsx,md}\""
  },
  "license": "ISC"
}


```

Next, we need to create a Turbo script. This is done by creating a `turbo.json` file. This file contains the build, lint, test, and dev instructions. A simple `turbo.json` file might look like the script below. You may copy the `turbo.json` file below. The `pipeline` property is where we specify the instructions for build, lint, and test tasks. It also contains the instructions for running the project in Dev mode. In our case, we tell Turbo to not cache anything and every time something changes, rerun the script and refresh.

```json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"]
    },
    "lint": {},
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

```

After all these steps, you may lastly add a `.gitignore` file. You can run `npm i` to install your dependencies globally. Following this, running `npm run dev` will start the dev server for both your backend and frontend simultaneously. Your directory structure should look something similar to the image given below.

![VS Code Directory Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zdsnh40gfhnsdy6lm94h.png)


> **Note**: Normally, you’d have just one root `tsconfig.json` and have the individual apps inherit from this by the `extends` keyword. But I am choosing to be liberal here. So, we are going to have two separate `tsconfig.json` in this project for now. You may change that if you want.

## Adding Next Auth and Supabase to Next
Now we move on to the first part of the integration. In this section, we will integrate Supabase with NextJS and use Next Auth for Authentication. You might be thinking “Well, where does NestJs fit into all of this?”. It doesn’t. All will be cleared as we go.

### Adding Environment Variables to NextJs
Normally, you’d do something like just add a `.env` file and be done with it. But that’s not how we do things. If you have been following my previous articles, you’d know by now a better way to add Environment variables to Next.

First, head over to the root of the NextJS project. In our case, that is the root of the `frontend` folder (not the `app` folder mind you!). Next, create a file called **`additional-env.d.ts`**. This is where we will define the types of our environment variables. In our case, the file would look something similar to the Code file given below. The code file below contains all the env variables needed for this part of things.

```typescript
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: "development" | "production";
      NEXTAUTH_SECRET: string; //Session Secret
      APP_JWT_SECRET: string; //Authorization Token secret
      NEXTAUTH_URL: string;
      
      GOOGLE_CLIENT_ID: string;
      GOOGLE_CLIENT_SECRET: string;
      
      NEXT_PUBLIC_SUPABASE_URL: string;
      SUPABASE_SERVICE_ROLE_KEY: string;
    }
  }
}

// If this file has no import/export statements (i.e. is a script)
// convert it into a module by adding an empty export statement.
export {};

```

Add this file to be picked up by the TS server in this project. To do this, head over to the `tsconfig.json` of the `frontend` app, and in the `include` array add the name of this file. Lastly, create a `.env` at the same level and add the variable names to it.

The values for `NEXT_PUBLIC_SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY` can be obtained from the Supabase Settings. Head over to the API section in settings as shown in the Supabase section above and you will the respective values in the “Project URL” and “Project API Keys” section as shown below.

![Supabase Settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0lxauat61sa9q3j3ui14.png)

The remaining values will be discussed below.

### Google Client Credentials
For using Google Sign-in, we will need Google API keys. These API keys will be used by the `GoogleProvider` of Next Auth. For these values, head over to the **Credentials** section of *“API & Services”* as shown below (1.). After this, click on *Create Credentials* as shown below. Then select *”OAuth client ID”*. 

![GCP API & Services console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1rum1oj4rgh10fr12bgx.png)

In the next screen, you will need to select the *” Application Type”* as “Web Application” (1.). This will prompt you for a name for the App (2.). In our case, I have named it “Your Jira” (pretty presumptuous). The *”Authorized Javascript Origins”* records the URLs from where the OAuth requests can originate. In our mini-project, the Sign-in requests can independently originate from the frontend and backend (running on `localhost:3000` and `localhost:3001` respectively). So, we will need to add them both.

![GCP OAuth Options](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7b58u59w2tks5ydswzyk.png)

*”Authorized redirect URIs”* represent the locations where the user will be redirected to after the user selects which Google account to sign in from. Again, this will need to be 2 URIs – one for frontend and another for backend. Clicking on *Create* will create the OAuth Client and give you the client IDs as shown below. In the image below, you will see 2 client secrets. That’s because I have purposefully generated two different client secrets. But you can work with just one. This will be the `GOOGLE_CLIENT_SECRET`.

![GCP OAuth Client Secret Section](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dq5nvue36ipee3w78pc0.png)

You will get the client ID in the first screen in this subsection after you create your OAuth. This will serve as the value for our `GOOGLE_CLIENT_ID`. With this, we have the Google bit of things with us.

### Integrating Next Auth
All this time, it has been just setup and setup. And now, more so 😊

To get started with integrating Next Auth into our Next JS app, head over to the `frontend` repo. Create a folder called `api`. Inside this, create another folder called `auth`. Finally, create a **folder** called `[…nextauth]` (the name is really important here). Inside this folder, create a file called `route.ts`. The directory structure should look something similar to the image shown below (do not pay attention to other folders starting with `_`, we will get to them).


![INext Auth directory structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vmm05s3nwbx68i5qbplk.png)

Basically, we are defining a backend API route `/auth` which Next Auth will handle. The route controllers are defined inside the `route.ts` file and will look something similar to the code below. In the code below, we build the core of the NextJS Authentication system.

In line 7 below, we start to define our Next Auth handler. Inside the `providers` array is where we keep all our authentication providers. This shouldn’t be anything new if you have been following my past articles. The difference here is that instead of using `CredentialsProvider` as we have up till now, we are now using the `GoogleProvider`. Inside the `GoogleProvider` constructor, it’s mostly the default options. We are passing in our `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` which we obtained from the section above. We also tell the provider that we want a prompt for consent of the user everytime and we want the `offline` mode of authentication. This will make sure that we get a `refresh_token`.

```typescript
import { signJwt } from "@/app/_lib/jwt";
import { SupabaseAdapter } from "@auth/supabase-adapter";
import { Adapter } from "next-auth/adapters";
import NextAuth from "next-auth/next";
import GoogleProvider from "next-auth/providers/google";

const handler = NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      authorization: {
        params: {
          prompt: "consent",
          access_type: "offline",
          response_type: "code",
        },
      },
    }),
  ],
  session: {
    strategy: "jwt",
  },
  adapter: SupabaseAdapter({
    url: process.env.NEXT_PUBLIC_SUPABASE_URL,
    secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
  }) as Adapter,
  callbacks: {
    async jwt({ token, account }) {
      if (account) {
        token.auth_token = await signJwt({
          sub: token.sub,
          id_token: account.id_token,
          access_token: account.access_token,
          expires_at: account.expires_at,
        });
      }
      return token;
    },
    async session({ session, token }) {
      session.auth_token = token.auth_token as string;
      return session;
    },
  },
});

export { handler as GET, handler as POST };


```
Now, we need to explicitly set `strategy` to JWT here. Because we are using a DB in the form of Supabase, Next Auth will by default use the session strategy. But in our mini-project, we are going to use JWTs. We set the Supabase Adapter options at line 24. Mostly default options here.

> **PS**: If you haven’t already, run `npm i @auth/supabase-adapter`.

The `callbacks` part is crucial. Normally, Next Auth and NextJS work seamlessly to manage sessions. This also includes encryption and decryption of session JWTs. But we want to use another custom JWT. This is where the `jwt()` callback comes in. This callback is invoked whenever JWT creation or updation is needed. Since we are using the `jwt` strategy, this callback will be invoked time and again. This callback receives the decrypted Next Auth JWT token. During Sign-in, this callback also receives another parameter called `account` which contains the details sent back by the provider.

In our case, `GoogleProvider` will send back the `id_token`, `access_token` and `expires_at` which we will use to create our own custom JWT token using the `signJwt()` method. This custom, encrypted JWT will be attached to the token through the `auth_token` property.

The Next Auth Token comes from the `jwt()` callback to the `session()` callback. In the `session()` callback, we will once again need to add the `auth_token` property taken from `token` into the `session` object. This will make sure that whenever we access the session on the frontend after sign in, we get the custom JWT token. This is how we should customize Next Auth when using it with  Third Party providers like `GitlabProvider`, `GithubProvider` and in our case, `GoogleProvider`.
 
This custom JWT token will be used as an authorization token. Basically, this authZ token is our way of interacting with the NestJs backend while the `access_token` and `id_token` acts as the authN token and tells us we are interacting with a valid Google User. The code below is what we keep inside a separate `jwt` folder inside the `_lib` folder.

```typescript
import jwt from "jsonwebtoken";

export const signJwt = async (payload: any, expiresIn = "1d") => {
  const token = await jwt.sign(payload, process.env.APP_JWT_SECRET, {
    algorithm: "HS512",
    expiresIn,
  });
  return token;
};

export const verifyJwt = (token: string) => {
  const data = jwt.verify(token, process.env.APP_JWT_SECRET, {
    algorithms: ["HS512"],
  });
  return data
};

```

As you can see, the above code is just a wrapper around JWT creation. The APP_JWT_SECRET serves as the secret key for our custom JWT. This secret needs to match the JWT secret on the Nest backend end for the whole thing to seamlessly work.

Lastly, create a `types` folder, and inside it, create a `next-auth.d.ts` file. Paste the following code inside it. This piece of code tells Next Auth that in addition to whatever Next Auth is sending inside the session of a signed-in user, we want to include our custom property called `auth_token` (which will hold our authZ JWT token).

```typescript
import NextAuth from "next-auth"

declare module "next-auth" {
  interface Session extends Session {
    auth_token: string;
  }
}

```

With this, we have fully integrated Next Auth with Google OAuth Provider into our NextJS project and customized it to interact with  a custom NestJS backend.

### Adding an AuthContext, Appbar and Sign In button
Firstly, to use Next Auth client side, we need to wrap our app in a Context. Luckily, Next Auth provides us with that out of the box. We will follow the convention we have been using thus far in my articles and create a `_providers` folder which will contain all the providers (keep in mind the files inside this folder will have `.tsx` extension since they will be used on the frontend side). The Root provider will then be wrapped around the root layout (`layout.tsx` file at the root of the `app` folder inside `frontend`). The image below shows the intended code.


![Next JS 13+ and Next Auth](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7vr33irj81wgden61nzu.png)

Next, create a `_components` folder and create a `SignoutButton.tsx` component. We will make this a client component. It will handle the authentication flow from the client side. Inside the component, we import the `useSession()` hook from Next Auth as shown below. This hook will provide the session data. The `session` object (renamed from the `data` property destructured from the returns of the hook) will contain a `user` property. 

```typescript
"use client";
import { signIn, signOut, useSession } from "next-auth/react";
import React from "react";

const SignoutButton = () => {
  const { data: session } = useSession();
  if (session?.user) {
    return (
      <div className="flex gap-4 ml-auto">
        <p className="text-sm tracking-tight text-sky-400">
          {session.user.name}
        </p>
        <button onClick={() => signOut()}>Sign Out</button>
      </div>
    );
  }
  return <button onClick={() => signIn()}>Sign in</button>;
};

export default SignoutButton;


```

This `user` property will contain the session details. Thanks to all the changes we did in the previous subsection, this `user` property will contain also the `auth_token` which can be used to interact with the Nest Backend. The absence of this `user` property will tell us that the user has not authenticated. We will display “Sign In” if so. Otherwise, we will display “Sign Out”. 

> **NOTE**: you have used multiple providers for authN and you would like to customize the Sign In page, then in the `signOut()` and `signIn()` functions you would need to also provide the ID of the providers. But here we will use the Sign In page provided automatically by Next Auth. So, we do not need to pass anything to these functions.

After this, we need to create a simple Appbar. For this, create a component called `Appbar.tsx` inside the `_components` folder. As shown in the code section below, it will be a server component. Since we are not using any hooks or client-side interaction, we can limit the interactivity provided by client components to the `SignoutButton` component only.

```typescript
import React from "react";
import SignoutButton from "./SignoutButton";

const Appbar = () => {
  return (
    <header className="flex justify-end gap-4 p-4 bg-gradient-to-b from-blue-900 to-black shadow">
      <SignoutButton />
    </header>
  );
};

export default Appbar;


```

Lastly, add the above `Appbar` component to the root `layout.tsx` like done below and that’s it! We have completed our integration of Next Auth and Supabase into NextJS app. In the next section, we will just test out the functionality developed thus far.

```typescript
import "./globals.css";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import Providers from "./_providers/Provders";
import Appbar from "./_components/Appbar";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Providers>
          <Appbar />
          {children}
        </Providers>
      </body>
    </html>
  );
}


```

### Testing out Frontend
The `frontend` NextJs app will interact with the Supabase instance that we have previously provisioned through Next Auth. Next Auth will use Google OAuth 2.0 through the `GoogleProvider` and the client credentials we picked up from the Google Cloud Console. To run our apps, just run `npm run dev` and Turbo will take care of the rest. Our backend and frontend will start parallelly.

![Frontend Demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0oenn2xhc8fdwiykz7s2.gif)

If the user signing in is a new user, then a new entry is created in Supabase in the **`users`** table. Along with this, another related record is created in the **`accounts`** table. The former is for storing your user profiles while the latter stores all the providers that the users in your **`users`** table have used to sign in. The real magic of the **`accounts`** table becomes clear when using multiple OAuth providers. But here, for the sake of simplicity, we will just use Google OAuth 2.0.

{% embed https://youtu.be/kz1DwYqjEOE %}

## Conclusion
This concludes the first article in the series. While I wanted to complete the integration Passport and NestJS and show a working demo, I realize that the article has become too long.

For this reason, I will conclude this article here abruptly and in the next article, we will cover the NestJS + Passport + Supabase part and use the `auth_token` JWT from frontend to interact with the backend.

Until next time, take care and continue to build awesome things!