Back to Articles

Integrating Clerk, Prisma, and Supabase into Next.js for User Session

Jul 29, 2024 (6 months ago)

5 min read

78 views

Setting up user sessions isn’t just about signing users in; it’s about integrating several systems to work together, handling security, managing user data, and much more. It’s a lot, and often, it’s exactly these kinds of necessary but tricky tasks that slow down our progress, turning an exciting project into a series of frustrating technical challenges.

We can turn those daunting tasks into manageable steps by combining different tools, such as Clerk for authentication, Prisma for object-relational mapping, and Supabase as a backend service.

Setting the Stage for User Sessions: Integrating Clerk into Next.js

We start by wrapping our application with ClerkProvider, and use ClerkLoading and ClerkLoaded components to manage and observe authentication state in Clerk. The SignInButton and UserButton components will render a sign in interface and a user profile dropdown, respectively. The SignedIn and SignedOut components help in conditionally rendering UI based on the user’s authentication state.

// src/app/layout.tsx

import {
    ClerkLoaded,
    ClerkLoading,
    ClerkProvider,
    SignInButton,
    SignedIn,
    SignedOut,
    UserButton,
} from '@clerk/nextjs';

import '../globals.css';
import Loading from './loading';

export default async function RootLayout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <html lang="en" suppressHydrationWarning>
            <ClerkProvider>
                <body className="w-screen min-h-screen flex bg-background">
                    <ClerkLoading>
                        <Loading />
                    </ClerkLoading>
                    <ClerkLoaded>
                        <header
                            className="sticky top-0 w-full h-16 flex justify-between items-center gap-4 border-b px-6"
                        >
                            <span>My App</span>
                            <SignedIn>
                                <UserButton />
                            </SignedIn>
                            <SignedOut>
                                <SignInButton />
                            </SignedOut>
                        </header>
                        {children}
                    </ClerkLoaded>
                </body>
            </ClerkProvider>
        </html>
    );
}

Bridging the Gap: Integrating Clerk with Next.js via Webhooks

Once we’ve got Clerk up and running, we have to make sure that whatever happens in Clerk matches what’s in our database. Whenever someone signs up or deletes their account using Clerk, we also have to create or delete the user from our database.

We set up a webhook from Clerk’s dashboard to notify our app instantly whenever there’s a new session event, firing an HTTP POST request to the specified endpoint in our Next.js API.

Heads up! To test webhook functionality locally, you will have to use tools like localtunnel to expose your localhost port to the internet. You can find more details on how to do this on the official docs.

Set up a Webhook in Clerk to send events to our application


Making Data Persistence Simple: Integrating Prisma ORM and Supabase

With our Next.js application now receiving user events in real-time, the next step is to persist these changes in our database. We will use Prisma to manage our app’s data with ease. It is an open-source Object-Relational Mapping (ORM) tool that provides a type-safe API to interact with our preferred database.

Supabase has been my go-to backend provider for a while now. It’s robust, performant, well-documented and has a very generous free tier that’s perfect for building MVPs and validating ideas quickly.

// schema.prisma 

generator client {
  provider      = "prisma-client-js"
}

datasource db {
  provider  = "postgresql" 
  url       = env("SUPABASE_DATABASE_URL")
  directUrl = env("SUPABASE_DIRECT_URL")
}

// User - Associated with a clerk user
model User {
  id              String       @id @default(cuid())
  createdAt       DateTime     @default(now())
  updatedAt       DateTime     @updatedAt
  clerkUserId     String?      @unique
}

Now, we are ready to set up an API route in our Next.js application to be called by Clerk’s webhook whenever there is a user session update. In the new route handler, we use the upsert function provided by Prisma, which updates the existing user data if found, or inserts new data if it does not exist in the database. On the other hand, we use the delete function to delete the user from our database when the account is deleted through Clerk’s service.

// src/app/api/webhooks/clerk

import { prisma } from '@/libs/database';
import type { WebhookEvent } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';

// Clerk Webhook: create or delete a user in the database by Clerk ID
export async function POST(req: Request) {
    try {
        // Parse the Clerk Webhook event
        const evt = (await req.json()) as WebhookEvent;

        const { id: clerkUserId } = evt.data;
        if (!clerkUserId)
            return NextResponse.json(
                { error: 'No user ID provided' },
                { status: 400 },
            );

        // Create or delete a user in the database based on the Clerk Webhook event
        let user = null;
        switch (evt.type) {
            case 'user.created': {
                user = await prisma.user.upsert({
                    where: {
                        clerkUserId,
                    },
                    update: {
                        clerkUserId,
                    },
                    create: {
                        clerkUserId,
                    },
                });
                break;
            }
            case 'user.deleted': {
                user = await prisma.user.delete({
                    where: {
                        clerkUserId,
                    },
                });
                break;
            }
            default:
                break;
        }

        return NextResponse.json({ user });
    } catch (error) {
        return NextResponse.json({ error }, { status: 500 });
    }
}

The End Result

With all these elements in place — Clerk ensuring secure, easy-to-manage user sessions, Prisma providing a smooth database experience with simple ORM, and Supabase standing strong as the backend — our Next.js project has now a robust, effective user session and profile management.

We can augment the user’s session with relevant data for our application, like profile information, account preferences, or application data.

If you think this might be a good solution, here’s a complete free boilerplate kit that you can use as a starting point for your next project. It’s fully integrated with authentication and database services and includes preconfigured features for emailing, error tracking, i18n, a landing page template and a functional dashboard example.

Thank you for reading!