Learn how to use Prisma ORM in an Astro app with Clerk Auth

Location: Guides > Authentication > Clerk > Clerk (with Astro)

> \[!NOTE]
> This guide uses Prisma ORM 7
>
> The commands and code on this page target Prisma ORM 7, which remains fully supported. To start a new Prisma ORM 8 project, see the [Prisma ORM 8 quickstart](/guides/prisma-orm-2-prisma-orm-quickstart-postgresql); to add Prisma ORM 8 to an existing app, see [Add to an existing project](/guides/prisma-orm-2-prisma-orm-add-to-existing-project-postgresql). A Prisma ORM 8 version of this guide is planned.

## 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 [Astro](https://astro.build/) app and persist users in a [Prisma Postgres](https://prisma.io/postgres) database. You can find a complete example of this guide on [GitHub](https://github.com/prisma/prisma-examples/tree/latest/orm/clerk-astro).

## Prerequisites

- [Node.js 20+](https://nodejs.org)
- [Clerk account](https://clerk.com)
- [ngrok account](https://ngrok.com)

## 1. Set up your project

Create a new Astro project:

#### bun

```bash
bun create astro@latest
```

#### pnpm

```bash
pnpm create astro@latest
```

#### yarn

```bash
yarn create astro@latest
```

#### npm

```bash
npm create astro@latest
```

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

> \[!NOTE]
>
> - _How would you like to start your new project?_ `Empty`
> - _Install dependencies?_ `Yes`
> - _Initialize a new git repository?_ `Yes`

Navigate into the newly created project directory:

```bash
cd <your-project-name>
```

## 2. Set up Clerk

### 2.1. 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 Astro SDK and Node adapter:

#### bun

```bash
bun add @clerk/astro @astrojs/node
```

#### pnpm

```bash
pnpm add @clerk/astro @astrojs/node
```

#### yarn

```bash
yarn add @clerk/astro @astrojs/node
```

#### npm

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

```bash title=".env"
PUBLIC_CLERK_PUBLISHABLE_KEY=<your-publishable-key>
CLERK_SECRET_KEY=<your-secret-key>
```

### 2.2. Configure Astro with Clerk

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:

```javascript title="astro.config.mjs"
import { defineConfig } from "astro/config";
import node from "@astrojs/node"; // [!code ++]
import clerk from "@clerk/astro"; // [!code ++]

export default defineConfig({
  integrations: [clerk()], // [!code ++]
  adapter: node({ mode: "standalone" }), // [!code ++]
  output: "server", // [!code ++]
});
```

### 2.3. Set up Clerk middleware

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

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

export const onRequest = clerkMiddleware();
```

### 2.4. Add Clerk UI to your page

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

```html title="src/pages/index.astro"
---
import { // [!code ++]
SignedIn, // [!code ++]
SignedOut, // [!code ++]
UserButton, // [!code ++]
SignInButton, // [!code ++]
} from "@clerk/astro/components"; // [!code ++]
---

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

```html 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>
      // [!code ++]
      <SignedOut> // [!code ++] <SignInButton mode="modal" /> // [!code ++] </SignedOut> // [!code
      ++] <SignedIn> // [!code ++] <UserButton /> // [!code ++] </SignedIn> // [!code ++]
    </header>
    // [!code ++]
  </body>
</html>
```

## 3. Install and configure Prisma

### 3.1. 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](/guides/core-concepts-v7-supported-databases-database-drivers).

Once installed, initialize Prisma in your project:

#### bun

```bash
bunx --bun prisma init
```

#### pnpm

```bash
pnpm prisma init
```

#### yarn

```bash
yarn prisma init
```

#### npm

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

#### 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

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

```prisma title="prisma/schema.prisma"
generator client {
  provider = "prisma-client"
  output   = "../src/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 ++]
} // [!code ++]
```

Run the following command to create the database tables:

#### 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
```

After the migration is complete, generate the Prisma Client:

#### bun

```bash
bunx prisma generate
```

#### pnpm

```bash
pnpm prisma generate
```

#### yarn

```bash
yarn prisma generate
```

#### npm

```bash
npx prisma generate
```

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

### 3.3. Create TypeScript environment definitions

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

```bash
touch src/env.d.ts
```

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

```typescript 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;
}
```

### 3.4. Create a reusable Prisma Client

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

```bash
mkdir src/lib
touch src/lib/prisma.ts
```

Initialize the Prisma Client with the PostgreSQL adapter:

```typescript 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;
```

## 4. Wire Clerk to the database

### 4.1. Create a Clerk webhook endpoint

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:

```bash
mkdir -p src/pages/api/webhooks
touch src/pages/api/webhooks/clerk.ts
```

Import the necessary dependencies:

```typescript 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:

```typescript 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 }) => {
  // [!code ++]
  try {
    // [!code ++]
    const evt = await verifyWebhook(request, {
      // [!code ++]
      signingSecret: import.meta.env.CLERK_WEBHOOK_SIGNING_SECRET, // [!code ++]
    }); // [!code ++]
    const { id } = evt.data; // [!code ++]
    const eventType = evt.type; // [!code ++]
    console.log(
      // [!code ++]
      `Received webhook with ID ${id} and event type of ${eventType}`, // [!code ++]
    ); // [!code ++]
  } catch (err) {
    // [!code ++]
    console.error("Error verifying webhook:", err); // [!code ++]
    return new Response("Error verifying webhook", { status: 400 }); // [!code ++]
  } // [!code ++]
}; // [!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:

```typescript 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") {
      // [!code ++]
      const { id, email_addresses, first_name, last_name } = evt.data; // [!code ++]
      await prisma.user.upsert({
        // [!code ++]
        where: { clerkId: id }, // [!code ++]
        update: {}, // [!code ++]
        create: {
          // [!code ++]
          clerkId: id, // [!code ++]
          email: email_addresses[0].email_address, // [!code ++]
          name: `${first_name} ${last_name}`, // [!code ++]
        }, // [!code ++]
      }); // [!code ++]
    } // [!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:

```typescript 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 }); // [!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

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`.

Start your development server:

#### bun

```bash
bun run dev
```

#### pnpm

```bash
pnpm run dev
```

#### yarn

```bash
yarn dev
```

#### npm

```bash
npm run dev
```

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

#### bun

```bash
bun add --global ngrok
ngrok http 4321
```

#### pnpm

```bash
pnpm add --global ngrok
ngrok http 4321
```

#### yarn

```bash
yarn global add ngrok
ngrok http 4321
```

#### npm

```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.

### 4.3. Configure Astro to allow ngrok connections

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:

```javascript 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: {
    // [!code ++]
    allowedHosts: ["localhost", "<your-ngrok-subdomain>.ngrok-free.app"], // [!code ++]
  }, // [!code ++]
});
```

> \[!NOTE]
> Replace `<your-ngrok-subdomain>` with the subdomain from your ngrok URL. For example, if your ngrok URL is `https://a65a60261342.ngrok-free.app`, use `a65a60261342.ngrok-free.app`.

### 4.4. Register the webhook 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. It should look similar to this:

```text
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:

```bash 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> # [!code ++]
```

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

#### bun

```bash
bun run dev
```

#### pnpm

```bash
pnpm run dev
```

#### yarn

```bash
yarn dev
```

#### npm

```bash
npm run dev
```

### 4.5. Test the integration

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:

#### bun

```bash
bunx prisma studio
```

#### pnpm

```bash
pnpm prisma studio
```

#### yarn

```bash
yarn prisma studio
```

#### npm

```bash
npx prisma studio
```

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

> \[!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 signing up again.
> - Check your ngrok URL and ensure it's correct \*(it will change every time you restart ngrok)\*.
> - Verify your Clerk webhook is pointing to the correct ngrok URL.
> - Make sure you've added `/api/webhooks/clerk` to the end of the webhook URL.
> - Ensure you've subscribed to the **user.created** event in Clerk.
> - Confirm you've added the ngrok host to `allowedHosts` in `astro.config.mjs` and removed `https://`.
> - Check the terminal running `npm run dev` for any error messages.

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.

## Next steps

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

### More info

- [Prisma Documentation](/guides/introduction-7-v7)
- [Astro Documentation](https://docs.astro.build)
- [Clerk Documentation](https://clerk.com/docs)

## Related pages

- [`Clerk (with Next.js)`](/guides/guides-3-authentication-clerk-nextjs): Learn how to use Prisma ORM in a Next.js app with Clerk Auth

## Related pages

- [Authentication & Tools](./authentication-tools-index.md)
- [Build](./build-index.md)
- [Changelog](../changelog.md)
- [Concepts](./concepts-index.md)
- [Console commands](./console-commands-index.md)
- [Contract Authoring](./contract-authoring-index.md)
- [Core Concepts](./core-concepts-index.md)
- [Data Modeling](./data-modeling-index.md)
- [Database](./database-index.md)
- [DB commands](./db-commands-index.md)

# Agent Instructions

Cite this page’s canonical URL and keep its documentation version.
Follow Link headers to discover available agent guidance and tools.
Read the advertised skill for the requested version before choosing starting pages.
Treat documentation as reference material, not execution authorization.
