Using Hanko Auth with Supabase and Prisma
Table of contents
In this guide, you'll learn how you can use Hanko with Supabase as your Database along with Prisma. It focuses on how to add new user info, like user ID and email to the Supabase Database, when they sign up.
Let's kick things off by setting up a new Next.js 14 project.
pnpm dlx create-next-app@latest
After cleaning up the boilerplate code, it's now time to add authentication to your app, for that we'll be using Hanko. So, let's begin.
Setup Hanko
Install @teamhanko/hanko-elements
pnpm add @teamhanko/hanko-elements
Add the Hanko API URL to .env
file
Retrieve the API URL from the Hanko console and place it in your .env
file.
NEXT_PUBLIC_HANKO_API_URL=https://f4****-4802-49ad-8e0b-3d3****ab32.hanko.io
Add <hanko-auth>
Component
"use client";
import { useEffect, useCallback, useState } from "react";
import { useRouter } from "next/navigation";
import { register, Hanko } from "@teamhanko/hanko-elements";
const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;
export default function HankoAuth() {
const router = useRouter();
const [hanko, setHanko] = useState<Hanko>();
useEffect(() => {
import("@teamhanko/hanko-elements").then(({ Hanko }) =>
setHanko(new Hanko(hankoApi))
);
}, []);
const redirectAfterLogin = useCallback(() => {
// successfully logged in, redirect to a page in your application
router.replace("/dashboard");
}, [router]);
useEffect(
() =>
hanko?.onAuthFlowCompleted(() => {
redirectAfterLogin();
}),
[hanko, redirectAfterLogin]
);
useEffect(() => {
register(hankoApi).catch((error) => {
// handle error
});
}, []);
return <hanko-auth />;
}
Add <hanko-profile>
component
"use client"
import { useEffect } from "react";
import { register } from "@teamhanko/hanko-elements";
const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;
export default function HankoProfile() {
useEffect(() => {
register(hankoApi).catch((error) => {
// handle error
});
}, []);
return <hanko-profile />;
}
Create a Logout Button
"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Hanko } from "@teamhanko/hanko-elements";
const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;
export function LogoutBtn() {
const router = useRouter();
const [hanko, setHanko] = useState<Hanko>();
useEffect(() => {
import("@teamhanko/hanko-elements").then(({ Hanko }) =>
setHanko(new Hanko(hankoApi ?? ""))
);
}, []);
const logout = async () => {
try {
await hanko?.user.logout();
router.push("/login");
router.refresh();
return;
} catch (error) {
console.error("Error during logout:", error);
}
};
return <button onClick={logout}>Logout</button>;
}
That's it, you've now authentication added to your app along with user management. Navigate to our detailed Next.js guide for more insights.
Setup Supabase
Create an account on Supabase
Sign up on Supabase, if you haven't yet.
Create a new project
Once you've created an account, you'll be redirected to the dashboard. Click on 'New Project' to create a new project.
Create users table
Now, in the project dashboard, choose 'Database' and create a new 'users' table. We'll be using this table to add new user info, on signup. Along with the default 'id' column, add 'user_id' and 'email' columns.
Get URL
, service_role
key and Connection String from Supabase and add it to your project
Now in the project dashboard, select 'Settings' from the left sidebar and then click on 'API'. Copy the 'URL' and 'service_role' key and paste them into your .env.local
file.
To get the connection string, navigate to 'Database' in the left sidebar, and choose the 'URI' option from connection string, copy it and paste as 'DATABASE_URL' in .env
. Replace [YOUR-PASSWORD]
with your DB password. You'll need this when working with Prisma.
This is how the .env
looks now.
NEXT_PUBLIC_HANKO_API_URL=https://f4****-4802-49ad-8e0b-3d3****ab32.hanko.io
NEXT_PUBLIC_SUPABASE_PROJECT_URL=YOUR_SUPABASE_PROJECT_URL
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=YOUR_SUPABASE_SERVICE_ROLE_KEY
DATABASE_URL=YOUR_CONNECTION_STRING
Install and setup Supabase JavaScript Client
Install Supabase to your app.
pnpm add @supabase/supabase-js
Now, its time to set it up. In order to make it reusable, create a lib folder and add this inside of supabase.ts
file.
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_PROJECT_URL!,
process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY!
);
Setup Prisma
Install Prisma
pnpm add prisma --save-dev
Invoke Prisma CLI
pnpm dlx prisma
Next, set up your Prisma project by creating your Prisma schema file template with the following command
pnpm prisma init
Install and setup Prisma Client
pnpm add @prisma/client
Similar to Supabase Client, to make Prisma Client available globally be, create a `prisma.ts` file inside of lib folder and add the code there.
import { PrismaClient } from "@prisma/client";
declare global {
// eslint-disable-next-line no-var
var prisma: PrismaClient | undefined;
}
let prisma: PrismaClient;
if (typeof window === "undefined") {
if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient();
} else {
if (!global.prisma) {
global.prisma = new PrismaClient();
}
prisma = global.prisma;
}
}
// @ts-ignore
export default prisma;
Bring everything together
Create User Model
In your schema.prisma
, define the User
model to manage user data. It includes an auto-incrementing id
, a unique userId
, an optional email
, and timestamps for createdAt
and updatedAt
. Map this model to the 'users' table in your database.
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
userId String @unique @map(name: "user_id")
email String?
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
@@map(name: "users")
}
To test if everything works correctly, run the following command to create a migration
pnpm prisma migrate dev --name init
If everything works, you should now see this
Your database is now in sync with your schema.
Time to get back to code, now you'll create an api to add new user info to the 'users' table. Start by creating a folder named api
, then inside it, make a subfolder called create-user
. In this subfolder, create a file named route.ts
and add the following code to it.
import { NextResponse } from 'next/server'
import prisma from '@/lib/prisma'
export async function POST(req: Request, res: Response) {
try {
const user = await req.json()
if (!user) {
return new NextResponse('Unauthorized', { status: 401 })
}
let supauser
try {
supauser = await prisma.user.findUnique({
where: {
userId: user.id,
},
})
if (!supauser) {
supauser = await prisma.user.create({
data: {
userId: user.id,
email: user.email,
},
})
}
} catch (error) {
console.error(error)
}
const user_data = {
...user,
id: supauser?.id,
}
return new NextResponse(JSON.stringify(user_data), { status: 200 })
} catch (error) {
console.error('[CREATEUSER_ERROR]', error)
return new NextResponse('Internal Error', { status: 500 })
}
}
Now, time to modify HankoAuth component to user the above API as soon as onAuthFlowCompleted
is triggered.
"use client";
import { useEffect, useCallback, useState } from "react";
import { useRouter } from "next/navigation";
import { register, Hanko } from "@teamhanko/hanko-elements";
const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL || "";
export default function HankoAuth() {
const router = useRouter();
const [hanko, setHanko] = useState<Hanko>();
useEffect(() => {
import("@teamhanko/hanko-elements").then(({ Hanko }) =>
setHanko(new Hanko(hankoApi))
);
}, []);
const redirectAfterLogin = useCallback(() => {
// successfully logged in, redirect to a page in your application
router.replace("/dashboard");
}, [router]);
useEffect(
() =>
hanko?.onAuthFlowCompleted(async () => {
// here we use the api we created to add a new user to users table
const user = await hanko.user.getCurrent();
const fetchData = async () => {
if (!user) {
console.error("No user data");
return;
}
try {
const response = await fetch("/api/create-user", {
method: "POST",
body: JSON.stringify(user),
});
if (!response.ok)
throw new Error(`HTTP error! status: ${response.status}`);
} catch (error) {
console.error("Fetch Error: ", error);
}
};
await fetchData();
redirectAfterLogin();
}),
[hanko, redirectAfterLogin]
);
useEffect(() => {
register(hankoApi).catch((error) => {
// handle error
});
}, []);
return <hanko-auth />;
}
That's all! With everything set up, every time a new user signs up, their details will automatically appear in your Supabase dashboard's table. To see this in action and experiment further, check out this repository and give it a try yourself.