āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/clerk/clerk-docs/guides/development/integrations/databases/neon ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
<TutorialHero beforeYouStart={[ { title: "Set up a Clerk application", link: "/docs/getting-started/quickstart/setup-clerk", icon: "clerk", }, { title: "Set up a Neon project in the Neon console", link: "https://console.neon.tech", icon: "cog-6-teeth", } ]} />
This tutorial demonstrates how to integrate Neon Postgres with Clerk in a Next.js application, using drizzle-orm and drizzle-kit to interact with the database. The tutorial guides you through setting up a simple application that enables users to add, view, and delete messages using Server Actions and Middleware with Clerk.
npx create-next-app clerk-neon-example --typescript --eslint --tailwind --use-npm --no-src-dir --app --import-alias "@/*"
cd clerk-neon-example
npm install @neondatabase/serverless
npm install drizzle-orm --legacy-peer-deps
npm install -D drizzle-kit
Follow the Next.js quickstart to integrate the Clerk Next.js SDK into your application.
To ensure that only authenticated users can access your application, modify clerkMiddleware to require authentication for every route.
import { clerkMiddleware } from '@clerk/nextjs/server'
export default clerkMiddleware(async (auth) => {
await auth.protect()
})
export const config = {
matcher: [
// 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)(.*)',
],
}
Add the Neon connection string to your project's environment variables. You can find the Neon connection string in the Neon console - see the Neon docs for more information.
Your environment variable file should have the following values:
DATABASE_URL=NEON_DB_CONNECTION_STRING
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY={{pub_key}}
CLERK_SECRET_KEY={{secret}}
Inside the app/, create a db/ directory.
Create a schema.ts file in the db/ directory that defines the database schema. The schema will include a table called user_messages with the columns user_id, create_ts, and message.The user_id column will be used to store the user's Clerk ID.
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(),
})
Create an index.ts file in the db directory to set up the database connection.
import { loadEnvConfig } from '@next/env'
import { neon } from '@neondatabase/serverless'
import { drizzle } from 'drizzle-orm/neon-http'
import { UserMessages } from './schema'
loadEnvConfig(process.cwd())
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 },
})
To load the schema into the database, create a drizzle.config.ts file at the root of your project and add the following configuration:
import { defineConfig } from 'drizzle-kit'
import { loadEnvConfig } from '@next/env'
loadEnvConfig(process.cwd())
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL must be a Neon postgres connection string')
}
export default defineConfig({
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL,
},
schema: './app/db/schema.ts',
})
Run the following command to push the schema to the database:
npx drizzle-kit push
To handle form submissions for adding and deleting user messages, create two Server Actions in app/actions.ts. Use Clerk's auth() helper to obtain the user ID, which will be used to interact with the database.
'use server'
import { auth } from '@clerk/nextjs/server'
import { UserMessages } from './db/schema'
import { db } from './db'
import { eq } from 'drizzle-orm'
export async function createUserMessage(formData: FormData) {
const { isAuthenticated, userId } = await auth()
if (!isAuthenticated) throw new Error('User not found')
const message = formData.get('message') as string
await db.insert(UserMessages).values({
user_id: userId,
message,
})
}
export async function deleteUserMessage() {
const { isAuthenticated, userId } = await auth()
if (!isAuthenticated) throw new Error('User not found')
await db.delete(UserMessages).where(eq(UserMessages.user_id, userId))
}
In your app/page.tsx file, add the following code to create the UI for the home page. If a message exists, the user can view and delete it; otherwise, they can add a new message.
To retrieve the user's messages, use Clerk's auth() helper to obtain the user's ID. Then, use this ID to query the database for the user's messages.
To enable the user to delete or add a message, use the deleteUserMessage() and createUserMessage() actions created in the previous step.
import { createUserMessage, deleteUserMessage } from './actions'
import { db } from './db'
import { auth } from '@clerk/nextjs/server'
export default async function Home() {
const { isAuthenticated, userId } = await auth()
if (!isAuthenticated) throw new Error('User not found')
const existingMessage = await db.query.UserMessages.findFirst({
where: (messages, { eq }) => eq(messages.user_id, userId),
})
return (
<main>
<h1>Neon + Clerk Example</h1>
{existingMessage ? (
<div>
<p>{existingMessage.message}</p>
<form action={deleteUserMessage}>
<button>Delete Message</button>
</form>
</div>
) : (
<form action={createUserMessage}>
<input type="text" name="message" placeholder="Enter a message" />
<button>Save Message</button>
</form>
)}
</main>
)
}
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.
</Steps>
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā