Integrate Neon with Clerk

You will learn the following:

  • Use Clerk to authenticate access to your application backed by Neon.

This tutorial demonstrates how to integrate Neon Postgres with Clerk in a Next.js application, using drizzle-orm for interacting with the database. The tutorial guides you through setting up a simple application that enables users to add, view, and delete messages. If a user has an existing message, they can view and delete it; otherwise, they can add a new message.

Create a new Next.js project

  1. Create a new Next.js project using the following command:
    npx create-next-app clerk-neon-example --typescript --eslint --tailwind --use-npm --no-src-dir --app --import-alias "@/*"
  2. Navigate to the project directory and install the required dependencies:
    cd clerk-neon-example
    npm install @neondatabase/serverless dotenv
    npm install drizzle-orm --legacy-peer-deps
    npm install -D drizzle-kit

Integrate the Next.js Clerk SDK

Follow the Next.js quickstart to integrate the Next.js Clerk SDK into your application.

Configure the Clerk middleware

By default, clerkMiddleware() will not protect any routes. All routes are public and you must opt-in to protection for routes. For this tutorial, protect your entire application and ensure that only authenticated users can it.

In your middleware.ts file, update the code with the following configuration:

import { clerkMiddleware } from '@clerk/nextjs/server';

export default clerkMiddleware((auth) => {

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],

Set your environment variables

You must add the Neon connection string to your project's environment variables.

  1. Navigate to the Neon console.
  2. In the navigation sidebar, select Dashboard.
  3. In the Connection Details section, find your Neon database connection string.
  4. Add the connection string to your .env.local file.

The final result should resemble the following:


Set up the application schema

Create a schema for the database. The schema will include a table called user_messages with the colomns user_id, create_ts, and message.

  1. Inside the app/ directory, create a db folder.
  2. Inside the db/ folder, create a schema.ts file and an index.ts file.
  3. Use the tabs below to find the example code for the schema and index files.
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";

export const UserMessages = pgTable("user_messages", {
  user_id: text("user_id").primaryKey().notNull(),
  createTs: timestamp("create_ts").defaultNow().notNull(),
  message: text("message").notNull(),
import "dotenv/config";
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
import { UserMessages } from "./schema";

if (!process.env.DATABASE_URL) {
  throw new Error("DATABASE_URL must be a Neon postgres connection string");

const sql = neon(process.env.DATABASE_URL);
export const db = drizzle(sql, {
  schema: { UserMessages },

Generate and run database migrations

Use the drizzle-kit package to generate and run database migrations.

  1. At the root of your project, create a drizzle.config.ts and add the following configuration:
    import { defineConfig } from "drizzle-kit";
    import * as dotenv from "dotenv";
    dotenv.config({ path: ".env.local" });
    export default defineConfig({
      dialect: "postgresql",
      dbCredentials: {
        url: process.env.DATABASE_URL!,
      schema: "./app/db/schema.ts",
      out: "./migrations",
  2. Generate and run the database migrations by running the following commands:
    npx drizzle-kit generate
    npx drizzle-kit migrate

Create the UI of the home page

Add the following code to the app/page.tsx file to create the UI of the home page:

import { createUserMessage, deleteUserMessage } from "./actions";
import { db } from "./db";
import { currentUser } from "@clerk/nextjs/server";

async function getUserMessage() {
  const user = await currentUser();
  if (!user) throw new Error("User not found");
  return db.query.UserMessages.findFirst({
    where: (messages, { eq }) => eq(messages.user_id,,

export default async function Home() {
  const existingMessage = await getUserMessage();

  return (
    <main className="flex flex-col items-center justify-center min-h-screen">
      <h1 className="text-3xl font-bold mb-8">Neon + Clerk Example</h1>
      {existingMessage ? (
        <div className="text-center">
          <p className="text-xl mb-4">{existingMessage.message}</p>
          <form action={deleteUserMessage}>
            <button type="submit" className="bg-red-500 text-white px-4 py-2 rounded">
              Delete Message
      ) : (
        <form action={createUserMessage} className="flex flex-col items-center">
          <input type="text" name="message" placeholder="Enter a message" className="border border-gray-300 rounded px-4 py-2 mb-4 w-64" />
          <button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
            Save Message

Handle user interactions

Create server actions to handle the form submissions and database interactions.

  1. At the root of your project, create an actions.ts file.
  2. Paste the following code example:
    "use server";
    import { currentUser } from "@clerk/nextjs/server";
    import { UserMessages } from "./db/schema";
    import { db } from "./db";
    import { redirect } from "next/navigation";
    import { eq } from "drizzle-orm";
    export async function createUserMessage(formData: FormData) {
      const user = await currentUser();
      if (!user) throw new Error("User not found");
      const message = formData.get("message") as string;
      await db.insert(UserMessages).values({
    export async function deleteUserMessage() {
      const user = await currentUser();
      if (!user) throw new Error("User not found");
      await db.delete(UserMessages).where(eq(UserMessages.user_id,;

Run the application

Run your application and open http://localhost:3000 in your browser. Sign in with Clerk and interact with the application to add and delete user messages.


What did you think of this content?