Skip to main content
Prisma Documentation Docs

Search documentation

Type to search this documentation.

On this pageOverview

Clerk (with Astro)

Clerk 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 Astro app and persist users in a Prisma Postgres database. You can find a complete example of this guide on GitHub.

Create a new Astro project:

title="bun"
bun create astro@latest
pnpm
pnpm create astro@latest
yarn
yarn create astro@latest
npm
npm create astro@latest

It will prompt you to customize your setup. Choose the defaults:

Navigate into the newly created project directory:

cd <your-project-name>

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.

Install the Clerk Astro SDK and Node adapter:

bun add @clerk/astro @astrojs/node
Bash
pnpm add @clerk/astro @astrojs/node
Bash
yarn add @clerk/astro @astrojs/node
Bash
npm install @clerk/astro @astrojs/node

In the Clerk Dashboard, navigate to the API keys page. In the Quick Copy section, copy your Clerk Publishable and Secret Keys. Paste your keys into .env in the root of your project:

.env
PUBLIC_CLERK_PUBLISHABLE_KEY=<your-publishable-key>
CLERK_SECRET_KEY=<your-secret-key>

In the Clerk Dashboard, navigate to the API keys page. In the Quick Copy section, copy your Clerk Publishable and Secret Keys. Paste your keys into .env in the root of your project:

title=".env"
PUBLIC_CLERK_PUBLISHABLE_KEY=<your-publishable-key>

CLERK_SECRET_KEY=<your-secret-key>

Astro needs to be configured for server-side rendering (SSR) with the Node adapter to work with Clerk. Update your astro.config.mjs file to include the Clerk integration and enable SSR:

title="astro.config.mjs"
import { defineConfig } from "astro/config";

import node from "@astrojs/node"; 

import clerk from "@clerk/astro"; 

export default defineConfig({

  integrations: [clerk()], 

  adapter: node({ mode: "standalone" }), 

  output: "server", 

});

The clerkMiddleware helper enables authentication across your entire application. Create a middleware.ts file in the src directory:

title="src/middleware.ts"
import { clerkMiddleware } from "@clerk/astro/server";

export const onRequest = clerkMiddleware();

Update your src/pages/index.astro file to import the Clerk authentication components:

title="src/pages/index.astro"
--import {SignedIn,SignedOut,UserButton,SignInButton,} from "@clerk/astro/components";--<html lang="en">  <head>    <meta charset="utf-8" />    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />    <meta name="viewport" content="width=device-width" />    <meta name="generator" content="{Astro.generator}" />    <title>Astro</title>  </head>  <body></body></html>

Now add a header with conditional rendering to show sign-in buttons for unauthenticated users and a user button for authenticated users:

title="src/pages/index.astro"
--import {SignedIn,SignedOut,UserButton,SignInButton,} from "@clerk/astro/components";--<html lang="en">  <head>    <meta charset="utf-8" />    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />    <meta name="viewport" content="width=device-width" />    <meta name="generator" content="{Astro.generator}" />    <title>Astro</title>  </head>  <body>    <header>      <SignedOut> // [!code ++] <SignInButton mode="modal" /> // [!code ++] </SignedOut> // [!code      ++] <SignedIn> // [!code ++] <UserButton /> // [!code ++] </SignedIn>    </header>  </body></html>

To get started with Prisma, you'll need to install a few dependencies:

title="bun"
bun add prisma@prev tsx @types/pg --dev
pnpm
pnpm add prisma@prev tsx @types/pg --save-dev
yarn
yarn add prisma@prev tsx @types/pg --dev
npm
npm install prisma@prev tsx @types/pg --save-dev
bun add @prisma/client@7 @prisma/adapter-pg dotenv pg
Bash
pnpm add @prisma/client@7 @prisma/adapter-pg dotenv pg
Bash
yarn add @prisma/client@7 @prisma/adapter-pg dotenv pg
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.

Once installed, initialize Prisma in your project:

Once installed, initialize Prisma in your project:

bunx --bun prisma init
Bash
pnpm prisma init
Bash
yarn prisma init
Bash
npx prisma init

[!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 prisma.config.ts file with your Prisma configuration
  • A .env file with a local DATABASE_URL already set

Create a Prisma Postgres database and replace the generated DATABASE_URL in your .env file with the postgres://... connection string from the CLI output:

This will create:

  • A prisma/ directory with a schema.prisma file
  • A prisma.config.ts file with your Prisma configuration
  • A .env file with a local DATABASE_URL already set

Create a Prisma Postgres database and replace the generated DATABASE_URL in your .env file with the postgres://... connection string from the CLI output:

title="bun"
bunx create-db
pnpm
pnpm dlx create-db
yarn
yarn dlx create-db
npm
npx create-db

Add a User model that will store authenticated user information from Clerk. The clerkId field uniquely links each database user to their Clerk account:

title="prisma/schema.prisma"
generator client {

  provider = "prisma-client"

  output   = "../src/generated/prisma"

}

datasource db {

  provider = "postgresql"

}

model User { 

  id      Int     @id @default(autoincrement()) 

  clerkId String  @unique

  email   String  @unique

  name    String?

} 

Run the following command to create the database tables:

title="bun"
bunx prisma migrate dev --name init
pnpm
pnpm prisma migrate dev --name init
yarn
yarn prisma migrate dev --name init
npm
npx prisma migrate dev --name init

After the migration is complete, generate the Prisma Client:

title="bun"
bunx prisma generate
pnpm
pnpm prisma generate
yarn
yarn prisma generate
npm
npx prisma generate

This generates the Prisma Client in the src/generated/prisma directory.

Create an env.d.ts file in your src directory to provide TypeScript definitions for your environment variables:

touch src/env.d.ts

Add type definitions for all the environment variables your application uses:

title="src/env.d.ts"
interface ImportMetaEnv {

  readonly DATABASE_URL: string;

  readonly CLERK_WEBHOOK_SIGNING_SECRET: string;

  readonly CLERK_SECRET_KEY: string;

  readonly PUBLIC_CLERK_PUBLISHABLE_KEY: string;

}

interface ImportMeta {

  readonly env: ImportMetaEnv;

}

In the src directory, create a lib directory and a prisma.ts file inside it:

mkdir src/lib

touch src/lib/prisma.ts

Initialize the Prisma Client with the PostgreSQL adapter:

title="src/lib/prisma.ts"
import { PrismaClient } from "../generated/prisma/client";

import { PrismaPg } from "@prisma/adapter-pg";

const adapter = new PrismaPg({

  connectionString: import.meta.env.DATABASE_URL,

});

const prisma = new PrismaClient({

  adapter,

});

export default prisma;

Webhooks allow Clerk to notify your application when events occur, such as when a user signs up. You'll create an API route to handle these webhooks and sync user data to your database.

Create the directory structure and file for the webhook endpoint:

mkdir -p src/pages/api/webhooks

touch src/pages/api/webhooks/clerk.ts

Import the necessary dependencies:

title="src/pages/api/webhooks/clerk.ts"
import { verifyWebhook } from "@clerk/astro/webhooks";

import type { APIRoute } from "astro";

import prisma from "../../../lib/prisma";

Create the POST handler that Clerk will call. The verifyWebhook function validates that the request actually comes from Clerk using the signing secret:

title="src/pages/api/webhooks/clerk.ts"
import { verifyWebhook } from "@clerk/astro/webhooks";

import type { APIRoute } from "astro";

import prisma from "../../../lib/prisma";

export const POST: APIRoute = async ({ request }) => {

  try {

    const evt = await verifyWebhook(request, {

      signingSecret: import.meta.env.CLERK_WEBHOOK_SIGNING_SECRET, 

    }); 

    const { id } = evt.data; 

    const eventType = evt.type; 

    console.log(

      `Received webhook with ID ${id} and event type of ${eventType}`, 

    ); 

  } catch (err) {

    console.error("Error verifying webhook:", err); 

    return new Response("Error verifying webhook", { status: 400 }); 

  } 

}; 

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:

title="src/pages/api/webhooks/clerk.ts"
import { verifyWebhook } from "@clerk/astro/webhooks";

import type { APIRoute } from "astro";

import prisma from "../../../lib/prisma";

export const POST: APIRoute = async ({ request }) => {

  try {

    const evt = await verifyWebhook(request, {

      signingSecret: import.meta.env.CLERK_WEBHOOK_SIGNING_SECRET,

    });

    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}`, 

        }, 

      }); 

    } 

  } 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:

title="src/pages/api/webhooks/clerk.ts"
import { verifyWebhook } from "@clerk/astro/webhooks";

import type { APIRoute } from "astro";

import prisma from "../../../lib/prisma";

export const POST: APIRoute = async ({ request }) => {

  try {

    const evt = await verifyWebhook(request, {

      signingSecret: import.meta.env.CLERK_WEBHOOK_SIGNING_SECRET,

    });

    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}`,

        },

      });

    }

    return new Response("Webhook received", { status: 200 }); 

  } catch (err) {

    console.error("Error verifying webhook:", err);

    return new Response("Error verifying webhook", { status: 400 });

  }

};

You'll need to expose your local app for webhooks with ngrok. This will allow Clerk to reach your /api/webhooks/clerk route to push events like user.created.

Start your development server:

title="bun"
bun run dev
pnpm
pnpm run dev
yarn
yarn dev
npm
npm run dev

In a separate terminal window, install ngrok globally and expose your local app:

bun add --global ngrok

ngrok http 4321
Bash
pnpm add --global ngrok
ngrok http 4321
Bash
yarn global add ngrok
ngrok http 4321
Bash
npm install --global ngrok
ngrok http 4321

Copy the ngrok Forwarding URL (e.g., https://a65a60261342.ngrok-free.app). This will be used to configure the webhook URL in Clerk.

Copy the ngrok Forwarding URL (e.g., https://a65a60261342.ngrok-free.app). This will be used to configure the webhook URL in Clerk.

Astro needs to be configured to accept connections from the ngrok domain. Update your astro.config.mjs to include the ngrok host in the allowed hosts list:

title="astro.config.mjs"
import { defineConfig } from "astro/config";

import node from "@astrojs/node";

import clerk from "@clerk/astro";

export default defineConfig({

  integrations: [clerk()],

  adapter: node({ mode: "standalone" }),

  output: "server",

  server: {

    allowedHosts: ["localhost", "<your-ngrok-subdomain>.ngrok-free.app"], 

  }, 

});

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. It should look similar to this:

https://a65a60261342.ngrok-free.app/api/webhooks/clerk

Subscribe to the user.created event by checking the box next to it under Message Filtering.

Click Create to save the webhook endpoint.

Copy the Signing Secret and add it to your .env file:

title=".env"
# Prisma

DATABASE_URL=<your-database-url>

# Clerk

PUBLIC_CLERK_PUBLISHABLE_KEY=<your-publishable-key>

CLERK_SECRET_KEY=<your-secret-key>

CLERK_WEBHOOK_SIGNING_SECRET=<your-signing-secret>

Restart your dev server to pick up the new environment variable:

title="bun"
bun run dev
pnpm
pnpm run dev
yarn
yarn dev
npm
npm run dev

Navigate to http://localhost:4321 in your browser and sign in using any of the sign-up options you configured in Clerk.

Open Prisma Studio to verify that the user was created in your database:

title="bun"
bunx prisma studio
pnpm
pnpm prisma studio
yarn
yarn prisma studio
npm
npx prisma studio

You should see a new user record with the Clerk ID, email, and name from your sign-up.

You now have an Astro application where Clerk handles authentication and every new sign-up is synced to your Prisma Postgres database through the webhook.

Now that you have a working Astro app with Clerk authentication and Prisma connected to a Prisma Postgres database, you can:

  • Add user profile management and update functionality
  • Build protected API routes that require authentication
  • Extend your schema with additional models related to users
  • Deploy to your preferred hosting platform and set your production webhook URL in Clerk
Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu