# Clerk (with Next.js) (Prisma ORM v7) (/docs/guides/v7/authentication/clerk/nextjs) > For the complete Prisma documentation index, see [llms.txt](https://www.prisma.io/docs/llms.txt). A markdown version of any docs page is available by appending `.md` to its URL. Learn how to use Prisma ORM in a Next.js app with Clerk Auth Location: Guides > v7 > Authentication > Clerk > Clerk (with Next.js) ## Introduction [#introduction] [Clerk](https://clerk.com/) is a drop-in auth provider that handles sign-up, sign-in, user management, and webhooks so you don't have to. In this guide you'll wire Clerk into a brand-new [Next.js](https://nextjs.org/) app, persist users in a [Prisma Postgres](https://prisma.io/postgres) database, and expose a tiny posts API. You can find a complete example of this guide on [GitHub](https://github.com/prisma/prisma-examples/tree/latest/orm/clerk-nextjs). ## Prerequisites [#prerequisites] * [Node.js 20.19+ on 20.x, 22.12+ on 22.x, or 24+](https://nodejs.org) * [Clerk account](https://clerk.com) * [ngrok account](https://ngrok.com) ## 1. Set up your project [#1-set-up-your-project] Create the app: #### bun ```bash bunx create-next-app@latest clerk-nextjs-prisma ``` #### pnpm ```bash pnpm dlx create-next-app@latest clerk-nextjs-prisma ``` #### yarn ```bash yarn dlx create-next-app@latest clerk-nextjs-prisma ``` #### npm ```bash npx create-next-app@latest clerk-nextjs-prisma ``` It will prompt you to customize your setup. Choose the defaults: > [!NOTE] > * *Would you like to use TypeScript?* `Yes` > * *Would you like to use ESLint?* `Yes` > * *Would you like to use Tailwind CSS?* `Yes` > * *Would you like your code inside a `src/` directory?* `No` > * *Would you like to use App Router?* (recommended) `Yes` > * *Would you like to use Turbopack for `next dev`?* `Yes` > * *Would you like to customize the import alias (`@/*` by default)?* `No` Navigate to the project directory: ```bash cd clerk-nextjs-prisma ``` ## 2. Set up Clerk [#2-set-up-clerk] ### 2.1. Create a new Clerk application [#21-create-a-new-clerk-application] [Sign in](https://dashboard.clerk.com/sign-in) to Clerk and navigate to the home page. From there, press the `Create Application` button to create a new application. Enter a title, select your sign-in options, and click `Create Application`. > [!NOTE] > For this guide, the Google, Github, and Email sign in options will be used. Install the Clerk Next.js SDK. This guide uses the `SignedIn` and `SignedOut` components, which exist in `@clerk/nextjs` 6; version 7 replaces them with `Show`, so pin the major version: #### bun ```bash bun add @clerk/nextjs@6 ``` #### pnpm ```bash pnpm add @clerk/nextjs@6 ``` #### yarn ```bash yarn add @clerk/nextjs@6 ``` #### npm ```bash npm install @clerk/nextjs@6 ``` Copy your Clerk keys and paste them into **.env** in the root of your project: ```bash title=".env" # Clerk # [!code ++] NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= # [!code ++] CLERK_SECRET_KEY= # [!code ++] ``` ### 2.2. Protect routes with Clerk middleware [#22-protect-routes-with-clerk-middleware] The `clerkMiddleware` helper enables authentication and is where you'll configure your protected routes. Create a `middleware.ts` file in the root directory of your project: ```tsx title="middleware.ts" showLineNumbers import { clerkMiddleware } from "@clerk/nextjs/server"; // [!code ++] export default clerkMiddleware(); // [!code ++] export const config = { matcher: [ "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", // [!code ++] "/(api|trpc)(.*)", // [!code ++] ], // [!code ++] }; ``` ### 2.3. Add Clerk UI to your layout [#23-add-clerk-ui-to-your-layout] Next, you'll need to wrap your app in the `ClerkProvider` component to make authentication globally available. In your `layout.tsx` file, add the `ClerkProvider` component: ```tsx title="app/layout.tsx" showLineNumbers import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { ClerkProvider } from "@clerk/nextjs"; // [!code ++] const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], }); const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], }); export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( {children} ); } ``` Create a `Navbar` component which will be used to display the Sign In and Sign Up buttons as well as the User Button once a user is signed in: ```tsx title="app/layout.tsx" showLineNumbers import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { ClerkProvider, UserButton, // [!code ++] SignInButton, // [!code ++] SignUpButton, // [!code ++] SignedOut, // [!code ++] SignedIn, // [!code ++] } from "@clerk/nextjs"; const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], }); const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], }); export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( {children} ); } const Navbar = () => { return (
); }; ``` ## 3. Install and configure Prisma [#3-install-and-configure-prisma] ### 3.1. Install dependencies [#31-install-dependencies] To get started with Prisma, you'll need to install a few dependencies: #### bun ```bash bun add prisma@prev tsx @types/pg --dev ``` #### pnpm ```bash pnpm add prisma@prev tsx @types/pg --save-dev ``` #### yarn ```bash yarn add prisma@prev tsx @types/pg --dev ``` #### npm ```bash npm install prisma@prev tsx @types/pg --save-dev ``` #### bun ```bash bun add @prisma/client@7 @prisma/adapter-pg dotenv pg ``` #### pnpm ```bash pnpm add @prisma/client@7 @prisma/adapter-pg dotenv pg ``` #### yarn ```bash yarn add @prisma/client@7 @prisma/adapter-pg dotenv pg ``` #### npm ```bash npm install @prisma/client@7 @prisma/adapter-pg dotenv pg ``` > [!NOTE] > If you are using a different database provider (MySQL, SQL Server, SQLite), install the corresponding driver adapter package instead of `@prisma/adapter-pg`. For more information, see [Database drivers](https://www.prisma.io/docs/orm/v7/core-concepts/supported-databases/database-drivers). Once installed, initialize Prisma in your project: #### bun ```bash bunx --bun prisma init --output ../app/generated/prisma ``` #### pnpm ```bash pnpm prisma init --output ../app/generated/prisma ``` #### yarn ```bash yarn prisma init --output ../app/generated/prisma ``` #### npm ```bash npx prisma init --output ../app/generated/prisma ``` > [!NOTE] > `prisma init` creates the Prisma scaffolding and a local `DATABASE_URL`. In the next step, you will create a Prisma Postgres database and replace that value with a direct `postgres://...` connection string. This will create: * A `prisma/` directory with a `schema.prisma` file * A local `DATABASE_URL` in `.env` Create a Prisma Postgres database and replace the generated `DATABASE_URL` in your `.env` file with the `postgres://...` connection string from the CLI output: #### bun ```bash bunx create-db ``` #### pnpm ```bash pnpm dlx create-db ``` #### yarn ```bash yarn dlx create-db ``` #### npm ```bash npx create-db ``` ### 3.2. Define your Prisma Schema [#32-define-your-prisma-schema] In the `prisma/schema.prisma` file, add the following models: ```prisma title="prisma/schema.prisma" showLineNumbers generator client { provider = "prisma-client" output = "../app/generated/prisma" } datasource db { provider = "postgresql" } model User { // [!code ++] id Int @id @default(autoincrement()) // [!code ++] clerkId String @unique // [!code ++] email String @unique // [!code ++] name String? // [!code ++] posts Post[] // [!code ++] } // [!code ++] // [!code ++] model Post { // [!code ++] id Int @id @default(autoincrement()) // [!code ++] title String // [!code ++] content String? // [!code ++] published Boolean @default(false) // [!code ++] authorId Int // [!code ++] author User @relation(fields: [authorId], references: [id]) // [!code ++] createdAt DateTime @default(now()) // [!code ++] } // [!code ++] ``` This will create two models: `User` and `Post`, with a one-to-many relationship between them. Create a `prisma.config.ts` file to configure Prisma: ```typescript title="prisma.config.ts" showLineNumbers import "dotenv/config"; // [!code ++] import { defineConfig, env } from "prisma/config"; // [!code ++] // [!code ++] export default defineConfig({ // [!code ++] schema: "prisma/schema.prisma", // [!code ++] migrations: { // [!code ++] path: "prisma/migrations", // [!code ++] }, // [!code ++] datasource: { // [!code ++] url: env("DATABASE_URL"), // [!code ++] }, // [!code ++] }); // [!code ++] ``` > [!NOTE] > You'll need to install the `dotenv` package: > > > > > #### bun > ```bash > bun add dotenv > ``` > > > #### pnpm > ```bash > pnpm add dotenv > ``` > > > #### yarn > ```bash > yarn add dotenv > ``` > > > #### npm > ```bash > npm install dotenv > ``` > > Now, run the following command to create the database tables and generate the Prisma Client: #### bun ```bash bunx prisma migrate dev --name init ``` #### pnpm ```bash pnpm prisma migrate dev --name init ``` #### yarn ```bash yarn prisma migrate dev --name init ``` #### npm ```bash npx prisma migrate dev --name init ``` #### bun ```bash bunx prisma generate ``` #### pnpm ```bash pnpm prisma generate ``` #### yarn ```bash yarn prisma generate ``` #### npm ```bash npx prisma generate ``` > [!WARNING] > It is recommended that you add `/app/generated/prisma` to your `.gitignore` file. ### 3.3. Create a reusable Prisma Client [#33-create-a-reusable-prisma-client] In the root directory, create a `lib` directory and a `prisma.ts` file inside it: ```tsx title="lib/prisma.ts" showLineNumbers import { PrismaClient } from "../app/generated/prisma/client"; // [!code ++] import { PrismaPg } from "@prisma/adapter-pg"; // [!code ++] const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL!, // [!code ++] }); const globalForPrisma = global as unknown as { prisma: PrismaClient; // [!code ++] }; const prisma = globalForPrisma.prisma || new PrismaClient({ adapter, // [!code ++] }); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; // [!code ++] export default prisma; // [!code ++] ``` ## 4. Wire Clerk to the database [#4-wire-clerk-to-the-database] ### 4.1. Create a Clerk webhook endpoint [#41-create-a-clerk-webhook-endpoint] Create a new API route at `app/api/webhooks/clerk/route.ts`: Import the necessary dependencies: ```tsx title="app/api/webhooks/clerk/route.ts" showLineNumbers import { verifyWebhook } from "@clerk/nextjs/webhooks"; // [!code ++] import { NextRequest } from "next/server"; // [!code ++] import prisma from "@/lib/prisma"; // [!code ++] ``` Create the `POST` method that Clerk will call and verify the webhook: ```tsx title="app/api/webhooks/clerk/route.ts" showLineNumbers import { verifyWebhook } from "@clerk/nextjs/webhooks"; import { NextRequest } from "next/server"; import prisma from "@/lib/prisma"; export async function POST(req: NextRequest) { try { const evt = await verifyWebhook(req); // [!code ++] const { id } = evt.data; // [!code ++] const eventType = evt.type; // [!code ++] console.log( `Received webhook with ID ${id} and event type of ${eventType}`, ); } catch (err) { console.error("Error verifying webhook:", err); // [!code ++] return new Response("Error verifying webhook", { status: 400 }); // [!code ++] } } ``` When a new user is created, they need to be stored in the database. You'll do that by checking if the event type is `user.created` and then using Prisma's `upsert` method to create a new user if they don't exist: ```tsx title="app/api/webhooks/clerk/route.ts" showLineNumbers import { verifyWebhook } from "@clerk/nextjs/webhooks"; import { NextRequest } from "next/server"; import prisma from "@/lib/prisma"; export async function POST(req: NextRequest) { try { const evt = await verifyWebhook(req); const { id } = evt.data; const eventType = evt.type; console.log(`Received webhook with ID ${id} and event type of ${eventType}`); if (eventType === "user.created") { const { id, email_addresses, first_name, last_name } = evt.data; // [!code ++] await prisma.user.upsert({ where: { clerkId: id }, // [!code ++] update: {}, // [!code ++] create: { clerkId: id, // [!code ++] email: email_addresses[0].email_address, // [!code ++] name: [first_name, last_name].filter(Boolean).join(" ") || null, // [!code ++] }, }); } } catch (err) { console.error("Error verifying webhook:", err); return new Response("Error verifying webhook", { status: 400 }); } } ``` Finally, return a response to Clerk to confirm the webhook was received: ```tsx title="app/api/webhooks/clerk/route.ts" showLineNumbers import { verifyWebhook } from "@clerk/nextjs/webhooks"; import { NextRequest } from "next/server"; import prisma from "@/lib/prisma"; export async function POST(req: NextRequest) { try { const evt = await verifyWebhook(req); const { id } = evt.data; const eventType = evt.type; console.log(`Received webhook with ID ${id} and event type of ${eventType}`); if (eventType === "user.created") { const { id, email_addresses, first_name, last_name } = evt.data; await prisma.user.upsert({ where: { clerkId: id }, update: {}, create: { clerkId: id, email: email_addresses[0].email_address, name: [first_name, last_name].filter(Boolean).join(" ") || null, }, }); } return new Response("Webhook received", { status: 200 }); // [!code ++] } catch (err) { console.error("Error verifying webhook:", err); return new Response("Error verifying webhook", { status: 400 }); } } ``` ### 4.2. Expose your local app for webhooks [#42-expose-your-local-app-for-webhooks] You'll need to expose your local app for webhooks with [ngrok](https://ngrok.com/). This will allow Clerk to reach your `/api/webhooks/clerk` route to push events like `user.created`. Install ngrok and expose your local app: #### bun ```bash bun add --global ngrok ngrok http 3000 ``` #### pnpm ```bash pnpm add --global ngrok ngrok http 3000 ``` #### yarn ```bash yarn global add ngrok ngrok http 3000 ``` #### npm ```bash npm install --global ngrok ngrok http 3000 ``` Copy the ngrok `Forwarding URL`. This will be used to set the webhook URL in Clerk. Navigate to the ***Webhooks*** section of your Clerk application located near the bottom of the ***Configure*** tab under ***Developers***. Click ***Add Endpoint*** and paste the ngrok URL into the ***Endpoint URL*** field and add `/api/webhooks/clerk` to the end of the URL. It should look similar to this: ```text https://a60b-99-42-62-240.ngrok-free.app/api/webhooks/clerk ``` Copy the ***Signing Secret*** and add it to your `.env` file: ```bash title=".env" # Prisma DATABASE_URL= # Clerk NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= CLERK_SECRET_KEY= CLERK_WEBHOOK_SIGNING_SECRET= # [!code ++] ``` On the home page, press Sign Up and create an account using any of the sign-up options Open Prisma Studio and you should see a user record. #### bun ```bash bunx prisma studio ``` #### pnpm ```bash pnpm prisma studio ``` #### yarn ```bash yarn prisma studio ``` #### npm ```bash npx prisma studio ``` > [!NOTE] > If you don't see a user record, there are a few things to check: > > * Delete your user from the Users tab in Clerk and try again. > * Check your ngrok URL and ensure it's correct *(it will change everytime you restart ngrok)*. > * Check your Clerk webhook is pointing to the correct ngrok URL. > * Make sure you've added `/api/webhooks/clerk` to the end of the URL. ## 5. Build a posts API [#5-build-a-posts-api] To create posts under a user, you'll need to create a new API route at `app/api/posts/route.ts`: Start by importing the necessary dependencies: ```tsx title="app/api/posts/route.ts" showLineNumbers import { auth } from "@clerk/nextjs/server"; // [!code ++] import prisma from "@/lib/prisma"; // [!code ++] ``` Get the `clerkId` of the authenticated user. If there's no user, return a `401` Unauthorized response: ```tsx title="app/api/posts/route.ts" showLineNumbers import { auth } from "@clerk/nextjs/server"; import prisma from "@/lib/prisma"; export async function POST(req: Request) { const { userId: clerkId } = await auth(); // [!code ++] if (!clerkId) return new Response("Unauthorized", { status: 401 }); // [!code ++] } ``` Match the Clerk user to a user in the database. If none is found, return a `404` Not Found response: ```tsx title="app/api/posts/route.ts" showLineNumbers import { auth } from "@clerk/nextjs/server"; import prisma from "@/lib/prisma"; export async function POST(req: Request) { const { userId: clerkId } = await auth(); if (!clerkId) return new Response("Unauthorized", { status: 401 }); const user = await prisma.user.findUnique({ where: { clerkId }, // [!code ++] }); if (!user) return new Response("User not found", { status: 404 }); // [!code ++] } ``` Destructure the title and content from the incoming request and create a post. Once done, return a `201` Created response: ```tsx title="app/api/posts/route.ts" showLineNumbers import { auth } from "@clerk/nextjs/server"; import prisma from "@/lib/prisma"; export async function POST(req: Request) { const { userId: clerkId } = await auth(); if (!clerkId) return new Response("Unauthorized", { status: 401 }); const { title, content } = await req.json(); // [!code ++] const user = await prisma.user.findUnique({ where: { clerkId }, }); if (!user) return new Response("User not found", { status: 404 }); const post = await prisma.post.create({ data: { title, // [!code ++] content, // [!code ++] authorId: user.id, // [!code ++] }, }); return new Response(JSON.stringify(post), { status: 201 }); // [!code ++] } ``` ## 6. Add a Post creation UI [#6-add-a-post-creation-ui] In `/app`, create a `/components` directory and a `PostInputs.tsx` file inside it: ```tsx title="app/components/PostInputs.tsx" showLineNumbers "use client"; // [!code ++] import { useState } from "react"; // [!code ++] export default function PostInputs() { const [title, setTitle] = useState(""); // [!code ++] const [content, setContent] = useState(""); // [!code ++] } ``` This component uses `"use client"` to ensure the component is rendered on the client. The title and content are stored in their own `useState` hooks. Create a function that will be called when a form is submitted: ```tsx title="app/components/PostInputs.tsx" showLineNumbers "use client"; import { useState } from "react"; export default function PostInputs() { const [title, setTitle] = useState(""); const [content, setContent] = useState(""); async function createPost(e: React.FormEvent) { e.preventDefault(); if (!title || !content) return; // [!code ++] const res = await fetch("/api/posts", { method: "POST", // [!code ++] headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title, content }), }); if (!res.ok) return; setTitle(""); setContent(""); location.reload(); } } ``` You'll be using a form to create a post and call the `POST` route you created earlier: ```tsx title="app/components/PostInputs.tsx" showLineNumbers "use client"; import { useState } from "react"; export default function PostInputs() { const [title, setTitle] = useState(""); const [content, setContent] = useState(""); async function createPost(e: React.FormEvent) { e.preventDefault(); if (!title || !content) return; const res = await fetch("/api/posts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title, content }), }); if (!res.ok) return; setTitle(""); setContent(""); location.reload(); } return (
setTitle(e.target.value)} className="w-full p-2 border border-zinc-800 rounded" // [!code ++] />