diff --git a/.github/workflows/lychee.yml b/.github/workflows/lychee.yml index 4cf1c94aea..ca2971fe40 100644 --- a/.github/workflows/lychee.yml +++ b/.github/workflows/lychee.yml @@ -37,6 +37,7 @@ jobs: with: # prisma.io/extensions ships with PR #8232; drop this exclude once it is live. # /compare/neon, /compare/planetscale, and /compare/supabase ship with PR #8516; drop that exclude once they are live. + # dashboard.clerk.com/~/ resolves to the signed-in user's application and returns 404 to anonymous requests. args: >- --cache --cache-exclude-status 429,500,502,503,504 @@ -58,6 +59,7 @@ jobs: --exclude 'https://app-deploy-demo.prisma.io' --exclude '^https://www\.prisma\.io/extensions' --exclude '^https://www\.prisma\.io/compare/(neon|planetscale|supabase)$' + --exclude '^https://dashboard\.clerk\.com/~/' --accept 200,201,204,301,302,303,307,308,403,429 ${{ steps.changed-docs.outputs.all_changed_files }} output: lychee/out.md @@ -93,6 +95,7 @@ jobs: --exclude 'https://app-deploy-demo.prisma.io' --exclude '^https://www\.prisma\.io/extensions' --exclude '^https://www\.prisma\.io/compare/(neon|planetscale|supabase)$' + --exclude '^https://dashboard\.clerk\.com/~/' --accept 200,201,204,301,302,303,307,308,403,429 ${{ steps.changed-docs.outputs.all_changed_files }} output: lychee/out-retry.md diff --git a/apps/docs/content/docs/guides/authentication/clerk/nextjs.mdx b/apps/docs/content/docs/guides/authentication/clerk/nextjs.mdx index c391a6aa16..c45ce46675 100644 --- a/apps/docs/content/docs/guides/authentication/clerk/nextjs.mdx +++ b/apps/docs/content/docs/guides/authentication/clerk/nextjs.mdx @@ -1,69 +1,134 @@ --- title: Clerk (with Next.js) -description: Learn how to use Prisma ORM in a Next.js app with Clerk Auth +description: 'Add Clerk authentication to a Next.js app, sync users to Prisma Postgres with a webhook, build a posts API, and deploy to Prisma Compute.' image: /img/guides/prisma-clerk-nextjs-cover.png url: /guides/authentication/clerk/nextjs -metaTitle: How to use Prisma ORM and Prisma Postgres with Clerk Auth and Next.js -metaDescription: Learn how to use Prisma ORM in a Next.js app with Clerk Auth +metaTitle: How to use Prisma ORM with Clerk and Next.js +metaDescription: 'Wire Clerk into a Next.js 16 app on Prisma ORM, store users in Prisma Postgres through a Clerk webhook, expose an authenticated posts API, and deploy to Prisma Compute.' --- -:::note[This guide uses Prisma ORM 7] +## Introduction -The commands and code on this page target Prisma ORM 7, which remains fully supported. To start a new Prisma ORM 8 project, see [Create a new app with PostgreSQL](/prisma-orm/quickstart/postgresql); to add Prisma ORM 8 to an existing app, see [Add Prisma ORM to an existing PostgreSQL database](/prisma-orm/add-to-existing-project/postgresql). A Prisma ORM 8 version of this guide is planned. +[Clerk](https://clerk.com/) is an auth provider: it handles sign-up, sign-in, user management, and webhooks. -::: +In this guide you wire Clerk into a new [Next.js](https://nextjs.org/) app, keep a copy of each Clerk user in a [Prisma Postgres](/postgres) database, expose a small posts API that only signed-in users can call, and deploy the app to [Prisma Compute](/compute). Every command below was run end to end against a live database. -## Introduction +:::note[Using Prisma ORM 7?] -[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. +Prisma ORM 8 is a [release candidate](/orm/release-status). Prisma ORM 7 remains fully supported; the Prisma ORM 7 version of this guide is at [/guides/v7/authentication/clerk/nextjs](/guides/v7/authentication/clerk/nextjs). -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 -- [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) +- [Node.js](https://nodejs.org) 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer (24 recommended) +- A PostgreSQL connection string, or nothing at all: `npx create-db@latest` creates a [Prisma Postgres](/postgres) database for you +- A [Clerk account](https://dashboard.clerk.com/sign-up) +- [ngrok](https://ngrok.com/docs/getting-started) installed and signed in (`ngrok config add-authtoken`), or any other tool that gives your local server a public URL, so Clerk can reach your webhook while you develop -## 1. Set up your project +## 1. Scaffold the project -Create the app: +Create a Next.js app with Prisma ORM already wired in. The `next` template generates the app, installs dependencies, and sets up the files that Prisma Compute deploys later. `--no-deploy` skips the scaffold's offer to deploy right away; you deploy in step 8: ```npm -npx create-next-app@latest clerk-nextjs-prisma +npm create prisma@latest -- clerk-nextjs-prisma --template next --provider postgres --no-deploy +cd clerk-nextjs-prisma ``` -It will prompt you to customize your setup. Choose the defaults: +The scaffold asks three questions, and this guide answers them as follows: write your models in Prisma Schema Language, the `.prisma` syntax you know from Prisma ORM 7 (Prisma ORM 8 calls the file the contract, and step 2 edits it); use npm, to match the commands on this page; and install the Prisma agent skills for the coding agents you use, or none. -:::info +Next, create a Prisma Postgres database with `create-db`. It needs no account and prints a connection string and a claim URL; the database is deleted after 24 hours unless you open the claim URL, which adds it to your Prisma Data Platform account: -- _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` +```npm +npx create-db@latest +``` -::: +Export the connection string in the shell you work in. The Prisma CLI, and the `npm run` scripts in `package.json` that call it, read the environment variable, not `.env`: -Navigate to the project directory: +```bash +export DATABASE_URL="" +``` + +## 2. Define the contract + +### 2.1. Replace the models + +The contract holds only models: the database connection lives in `prisma.config.ts`, and there are no `datasource` or `generator` blocks. The template ships demo models with seeded users. Replace the whole file with a `User` model that stores the Clerk user id next to the email, and a `Post` model that belongs to a user. Keep the `// use prisma-8` line: every Prisma ORM 8 contract file starts with it. + +In Prisma ORM 8, `DateTime` reads and writes `Temporal.Instant` objects. `TimestamptzString` is the same timestamp with time zone column read and written as a plain string, which is enough for a timestamp you only sort by. + +```prisma title="src/prisma/contract.prisma" +// use prisma-8 + +model User { + id Int @id @default(autoincrement()) + clerkId String @unique + email String @unique + name String? + posts Post[] +} + +model Post { + id Int @id @default(autoincrement()) + title String + content String? + author User @relation(fields: [authorId], references: [id]) + authorId Int + createdAt TimestamptzString @default(now()) +} +``` + +The template's seed helpers refer to fields that no longer exist, so delete them: ```bash -cd clerk-nextjs-prisma +rm src/prisma/seed.ts src/prisma/users.ts ``` -## 2. Set up Clerk +### 2.2. Emit the contract -### 2.1. Create a new Clerk application +Emitting the contract replaces `prisma generate`: it writes the types your queries are checked against into `src/prisma/`: -[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`. +```npm +npm run contract:emit +``` -:::info +### 2.3. Plan the migration -For this guide, the Google, Github, and Email sign in options will be used. +The template also ships a migration for its demo models. Delete it and plan one that creates your models. `migration plan` writes the migration file without touching the database; the deploy in step 8 builds the production database by replaying `migrations/`, so it needs this file: -::: +```bash +rm -rf migrations +``` + +```npm +npx prisma migration plan --name init +``` + +```json no-copy +"dir": "migrations/app/20261019T0955_init" +``` + +### 2.4. Create the tables + +Now create the tables in the database from step 1. `db:init` applies the contract directly and records in the database which contract it matches (the output calls this signing). It does not replay the migration you just planned; that file is for the deploy: + +```npm +npm run db:init +``` + +```text no-copy +"summary": "Applied 6 operation(s) across 1 space(s), database signed" +``` + +If `db:init` stops with `Connection terminated unexpectedly`, a database you just created is still starting; wait a few seconds and run it again. The command is safe to repeat. + +## 3. Set up Clerk + +### 3.1. Create a Clerk application + +[Sign in](https://dashboard.clerk.com/sign-in) to Clerk and create an application, or open one you already have. On the application's overview page, select **Next.js** and the **Manual setup** tab: it shows the install command and your API keys. + +Under **Configure** > **User & authentication** > **Email**, turn on **Sign-up with email**, **Require email address**, and **Sign-in with email**. The sign-up form only shows the fields an application requires, so without **Require email address** the form offers social sign-in buttons only. Install the Clerk Next.js SDK: @@ -71,109 +136,60 @@ Install the Clerk Next.js SDK: npm install @clerk/nextjs ``` -Copy your Clerk keys and paste them into **.env** in the root of your project: +Create a `.env.local` file in the project root and paste your keys from the dashboard. Next.js loads this file in development and at build time; the Prisma CLI does not, which is why `DATABASE_URL` stays exported in your shell. Use `.env.local` rather than `.env`: with `output: "standalone"`, `next build` copies `.env` into the build output that step 8 uploads, and `.env.local` stays out of it: + +```bash title=".env.local" +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= +CLERK_SECRET_KEY= +``` + +The template's `.gitignore` lists only `.env`, so add the new file to it: -```bash title=".env" -# Clerk # [!code ++] -NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= # [!code ++] -CLERK_SECRET_KEY= # [!code ++] +```bash title=".gitignore" +.env.local # [!code ++] ``` -### 2.2. Protect routes with Clerk middleware +### 3.2. Add `clerkMiddleware()` as the Next.js proxy -The `clerkMiddleware` helper enables authentication and is where you'll configure your protected routes. +`clerkMiddleware()` reads the Clerk session on every request so that `auth()` and `currentUser()` work in your routes. Next.js 16 runs this kind of code from a `proxy.ts` file (the file was called `middleware.ts` in Next.js 15 and older). The template puts the app in `src/`, so create the file there: -Create a `middleware.ts` file in the root directory of your project: +```ts title="src/proxy.ts" +import { clerkMiddleware } from "@clerk/nextjs/server"; + +export default clerkMiddleware(); -```tsx title="middleware.ts" showLineNumbers -import { clerkMiddleware } from "@clerk/nextjs/server"; // [!code ++] -// [!code ++] -export default clerkMiddleware(); // [!code ++] -// [!code ++] export const config = { - // [!code ++] matcher: [ - // [!code ++] - "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", // [!code ++] - "/(api|trpc)(.*)", // [!code ++] - ], // [!code ++] -}; // [!code ++] + // Skip Next.js internals and all static files, unless found in search params + "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", + // Always run for API routes + "/(api|trpc)(.*)", + // Always run for Clerk-specific frontend API routes + "/__clerk/(.*)", + ], +}; ``` -### 2.3. Add Clerk UI to your layout - -Next, you'll need to wrap your app in the `ClerkProvider` component to make authentication globally available. +:::note -In your `layout.tsx` file, add the `ClerkProvider` component: +On Next.js 15 or older, name the file `middleware.ts` instead. The code is the same; only the file name changes. -```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"], -}); +`clerkMiddleware()` leaves every route public, and the routes in this guide check for a user themselves, which is where Clerk recommends the check lives. -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); +### 3.3. Add the Clerk provider and sign-in buttons -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; +Replace `src/app/layout.tsx` so that `ClerkProvider` wraps the app and a header shows sign-in, sign-up, and user buttons. `Show` renders its children only when the condition in `when` holds, so the header shows the sign-in and sign-up buttons to visitors and the user button to signed-in users; it replaces the `SignedIn` and `SignedOut` components from earlier Clerk versions. `mode="modal"` opens the sign-in and sign-up forms on your page instead of redirecting to Clerk's hosted pages: -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {" "} - // [!code ++] - - - {children} - - - // [!code ++] - ); -} -``` - -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 +```tsx title="src/app/layout.tsx" import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { ClerkProvider, Show, SignInButton, SignUpButton, UserButton } from "@clerk/nextjs"; 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", + title: "clerk-nextjs-prisma", + description: "Generated by create-prisma", }; export default function RootLayout({ @@ -182,661 +198,364 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - - - // [!code ++] + + + +
+ + + + + + + +
{children} - - -
+
+ + ); } - -const Navbar = () => { - // [!code ++] - return ( - // [!code ++] -
- {" "} - // [!code ++] - - {" "} - // [!code ++] - // [!code ++] - // [!code ++] - {" "} - // [!code ++] - - {" "} - // [!code ++] - // [!code ++] - {" "} - // [!code ++] -
// [!code ++] - ); // [!code ++] -}; // [!code ++] -``` - -## 3. Install and configure Prisma - -### 3.1. Install dependencies - -To get started with Prisma, you'll need to install a few dependencies: - -```npm -npm install prisma@prev tsx @types/pg --save-dev ``` -```npm -npm install @prisma/client@7 @prisma/adapter-pg dotenv pg -``` +Append styles for the header, and for the post form you build in step 5, to the template's stylesheet: -:::info +```css title="src/app/globals.css" +.navbar { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 1rem; + height: 4rem; + padding: 0 1rem; +} -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](/orm/v7/core-concepts/supported-databases/database-drivers). +.post-form { + display: grid; + gap: 0.5rem; + margin-bottom: 1.5rem; +} -::: +.post-form input, +.post-form textarea, +.post-form button { + padding: 0.5rem; + border: 1px solid #e5e5e5; + border-radius: 0.375rem; + font: inherit; +} +``` -Once installed, initialize Prisma in your project: +## 4. Build a posts API -```npm -npx prisma init --output ../app/generated/prisma -``` +Create a route that stores a post for the signed-in user. `auth()` returns the Clerk user id from the session; a request without one gets `401`. The route then looks up the matching database user, so a post always points at a `User` row: -:::info +```ts title="src/app/api/posts/route.ts" +import { auth } from "@clerk/nextjs/server"; +import { db } from "@/src/prisma/db"; -`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. +export async function POST(req: Request) { + const { userId: clerkId } = await auth(); + if (!clerkId) return new Response("Unauthorized", { status: 401 }); -::: -This will create: + const { title, content } = await req.json(); -- A `prisma/` directory with a `schema.prisma` file -- A local `DATABASE_URL` in `.env` + const user = await db.orm.public.User.where({ clerkId }).first(); + if (!user) return new Response("User not found", { status: 404 }); -Create a Prisma Postgres database and replace the generated `DATABASE_URL` in your `.env` file with the `postgres://...` connection string from the CLI output: + const post = await db.orm.public.Post.create({ + title, + content, + authorId: user.id, + }); -```npm -npx create-db + return Response.json(post, { status: 201 }); +} ``` -### 3.2. Define your Prisma Schema - -In the `prisma/schema.prisma` file, add the following models: +`db` is the Prisma ORM client the template created in `src/prisma/db.ts`, and the template maps `@/` to the project root, so the import path is `@/src/prisma/db`. `db.orm` is the ORM query API, and on PostgreSQL every model sits under its schema, so the `User` table is `db.orm.public.User`. `.first()` returns one matching record or `null`, and `.create()` takes the fields directly and returns the inserted row. [Reading data](/orm/fundamentals/reading-data) and [Writing data](/orm/fundamentals/writing-data) cover the rest of the query API. -```prisma title="prisma/schema.prisma" showLineNumbers -generator client { - provider = "prisma-client" - output = "../app/generated/prisma" -} +## 5. Add a post form -datasource db { - provider = "postgresql" -} +Create a client component with a title field, a content field, and a submit button. On submit it calls the posts API and, if the API accepted the post, clears the fields and reloads the page so the new post appears in the list; a rejected post keeps the draft in the fields: -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 ++] -``` +```tsx title="src/app/components/PostInputs.tsx" +"use client"; -:::note +import { useState } from "react"; -You'll need to install the `dotenv` package: +export default function PostInputs() { + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); -```npm -npm install dotenv -``` + 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; -Now, run the following command to create the database tables and generate the Prisma Client: + setTitle(""); + setContent(""); + location.reload(); + } -```npm -npx prisma migrate dev --name init + return ( +
+ setTitle(e.target.value)} + /> +