Sync Clerk Users with Your Database Using Prisma in Next.js

Published on July 10, 2023
Thumbnail

In this article, you will learn how to synchronize your Clerk users with your database using Prisma in your Next.js application. We will utilize Prisma for creating, updating, and deleting users in the database. Before we begin, make sure you have already set up Clerk in your Next.js application. If you haven't done so, you can follow the official guide to set up Clerk.

Setting up Prisma

To get started, follow these steps:

Install Prisma by running the following command in your terminal:

npm install prisma --save-dev
npm install prisma --save-dev

Initialize Prisma by running the following command:

npx prisma init
npx prisma init

This command will create a new directory called prisma with a schema.prisma file inside it. Open the schema.prisma file and add a new model called User. This model will be used to store our users. Here's an example of the modified schema.prisma file:

generator client {
  provider = "prisma-client-js"
}
 
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
 
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  firstName String
  lastName  String
  imageUrl  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
generator client {
  provider = "prisma-client-js"
}
 
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
 
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  firstName String
  lastName  String
  imageUrl  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

Make sure to add the DATABASE_URL environment variable to your .env file:

DATABASE_URL=
DATABASE_URL=

Migrate the database by running the following command:

npx prisma migrate dev --name init
npx prisma migrate dev --name init

This command will create a new migration file and apply it to your database. It will also create a new Prisma client in the @prisma/client package.

Create a Prisma client instance. Create a new file called prisma.ts in the lib directory and add the following code to it:

import { PrismaClient } from '@prisma/client'
 
const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined
}
 
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
 
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
import { PrismaClient } from '@prisma/client'
 
const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined
}
 
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
 
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

This client instance will be used to interact with our database.

Creating a Hook to Sync Users

To sync users, we will need to install the svix package to verify the signature of the webhook request and the @clerk/clerk-sdk-node package to get the type of the webhook event. Install these packages by running the following command:

npm install svix @clerk/clerk-sdk-node
npm install svix @clerk/clerk-sdk-node

Next, create an endpoint that Clerk will use to send webhooks. Create a new file called app/api/hooks/user/route.ts and add the following code:

import type { WebhookEvent } from '@clerk/clerk-sdk-node'
import { Webhook } from 'svix'
 
import { prisma } from '@/lib/prisma'
 
export async function POST(request: Request) {
  const body = (await request.json()) as WebhookEvent
  const headers = {
    'svix-id': request.headers.get('SVIX-ID') as string,
    'svix-timestamp': request.headers.get('SVIX-TIMESTAMP') as string,
    'svix-signature': request.headers.get('SVIX-SIGNATURE') as string,
  }
 
  const webhook = new Webhook(process.env.SVIX_USER_HOOK_SIGNING_SECRET)
 
  try {
    webhook.verify(JSON.stringify(body), headers)
  } catch (error) {
    return new Response('Invalid signature', { status: 401 })
  }
 
  switch (body.type) {
    case 'user.created':
      await prisma.user.create({
        data: {
          id: body.data.id,
          firstName: body.data.first_name,
          lastName: body.data.last_name,
          imageUrl: body.data.profile_image_url,
        },
      })
      return new Response('User created')
    case 'user.updated':
      await prisma.user.update({
        where: {
          id: body.data.id,
        },
        data: {
          firstName: body.data.first_name,
          lastName: body.data.last_name,
          username: body.data.username as string,
          imageUrl: body.data.profile_image_url,
        },
      })
      return new Response('User updated')
    case 'user.deleted':
      await prisma.user.delete({
        where: {
          id: body.data.id,
        },
      })
      return new Response('User deleted')
  }
}
import type { WebhookEvent } from '@clerk/clerk-sdk-node'
import { Webhook } from 'svix'
 
import { prisma } from '@/lib/prisma'
 
export async function POST(request: Request) {
  const body = (await request.json()) as WebhookEvent
  const headers = {
    'svix-id': request.headers.get('SVIX-ID') as string,
    'svix-timestamp': request.headers.get('SVIX-TIMESTAMP') as string,
    'svix-signature': request.headers.get('SVIX-SIGNATURE') as string,
  }
 
  const webhook = new Webhook(process.env.SVIX_USER_HOOK_SIGNING_SECRET)
 
  try {
    webhook.verify(JSON.stringify(body), headers)
  } catch (error) {
    return new Response('Invalid signature', { status: 401 })
  }
 
  switch (body.type) {
    case 'user.created':
      await prisma.user.create({
        data: {
          id: body.data.id,
          firstName: body.data.first_name,
          lastName: body.data.last_name,
          imageUrl: body.data.profile_image_url,
        },
      })
      return new Response('User created')
    case 'user.updated':
      await prisma.user.update({
        where: {
          id: body.data.id,
        },
        data: {
          firstName: body.data.first_name,
          lastName: body.data.last_name,
          username: body.data.username as string,
          imageUrl: body.data.profile_image_url,
        },
      })
      return new Response('User updated')
    case 'user.deleted':
      await prisma.user.delete({
        where: {
          id: body.data.id,
        },
      })
      return new Response('User deleted')
  }
}

Make sure to add SVIX_USER_HOOK_SIGNING_SECRET to your .env file. We will do this later.

Before adding the endpoint to Clerk, we need to expose our app to the internet. We will use ngrok for this purpose. Install ngrok by running the following command:

npm i -g ngrok
npm i -g ngrok

Next, run the following command to expose your app to the internet:

ngrok http 3000
ngrok http 3000

Now, let's add our endpoint to Clerk. Follow these steps:

  1. Go to your Clerk dashboard and click on the Webhooks tab.
  2. Click on the Add Webhook button.
  3. Enter the following details:
    • Endpoint: https://<your-ngrok-url>/api/hooks/user
    • Message filtering: select user.created, user.updated, and user.deleted
  4. Click on the Create button.

Finally, add the SVIX_USER_HOOK_SIGNING_SECRET to your .env file. You can find the signing secret by clicking on the endpoint you just created in Clerk. Add the following line to your .env file:

SVIX_USER_HOOK_SIGNING_SECRET=
SVIX_USER_HOOK_SIGNING_SECRET=

That's it! Now, whenever a user is created, updated, or deleted in Clerk, the webhook will be triggered and the user will be synced with our database.