Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve Neon guide, highlighting API use in Next.js #1234

Merged
merged 8 commits into from
Jul 16, 2024
Merged
194 changes: 88 additions & 106 deletions docs/integrations/databases/neon.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ description: Learn how to integrate Clerk into your Neon application.

</TutorialHero>

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

<Steps>

Expand All @@ -44,53 +44,41 @@ This tutorial demonstrates how to integrate Neon Postgres with Clerk in a Next.j

### Integrate the Next.js Clerk SDK

Follow the [Next.js quickstart](/docs/quickstarts/nextjs) to integrate the Next.js Clerk SDK into your application.
Follow the [Next.js quickstart](/docs/quickstarts/nextjs) to integrate the Clerk Next.js SDK into your application.

### Configure the Clerk middleware
### Protect your application routes

By default, [`clerkMiddleware()`](/docs/references/nextjs/clerk-middleware) 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.
To ensure that only authenticated users can access your application, modify [`clerkMiddleware`](/docs/references/nextjs/clerk-middleware) to require authentication for every route.

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

```typescript {{ filename: 'middleware.ts' }}
```typescript {{ filename: 'middleware.ts', mark: [3] }}
import { clerkMiddleware } from '@clerk/nextjs/server';

export default clerkMiddleware((auth) => {
auth().protect();
});
export default clerkMiddleware((auth) => { auth().protect() });

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

### Set your environment variables

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

1. Navigate to the [Neon console](https://console.neon.tech/).
1. In the navigation sidebar, select **Dashboard**.
1. In the **Connection Details** section, find your Neon database connection string.
1. Add the connection string to your `.env.local` file.
Add the Neon connection string to your project's environment variables. You can find the Neon connection string in the [Neon console](https://console.neon.tech/) - see the [Neon docs](https://neon.tech/docs/connect/connect-from-any-app) for more information.

The final result should resemble the following:
Your environment variable file should have the following values:

```sh {{ filename: '.env.local' }}
DATABASE_URL=NEON_DB_CONNECTION_STRING
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY={{pub_key}}
CLERK_SECRET_KEY={{secret}}
```

### Set up the application schema
### Set up the application schema and database connection

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/`, create a `db/` directory.

1. Inside the `app/` directory, create a `db` folder.
1. Inside the `db/` folder, create a `schema.ts` file and an `index.ts` file.
1. Use the tabs below to find the example code for the schema and index files.
1. 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.

<CodeBlockTabs options={["Schema", "Index"]}>
```typescript {{ filename: 'app/db/schema.ts' }}
```typescript {{ filename: 'app/db/schema.ts', mark: [4] }}
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";

export const UserMessages = pgTable("user_messages", {
Expand All @@ -100,7 +88,9 @@ export const UserMessages = pgTable("user_messages", {
});
```

```typescript {{ filename: 'app/db/index.ts' }}
1. Create an `index.ts` file in the `db` directory to set up the database connection.

```typescript {{ filename: 'app/db/index.ts' }}
import { loadEnvConfig } from "@next/env";
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
Expand All @@ -117,74 +107,102 @@ export const db = drizzle(sql, {
schema: { UserMessages },
});
```
</CodeBlockTabs>

### Generate and run database migrations
### Push the schema to the database

Use the `drizzle-kit` package to generate and run database migrations.
1. To load the schema into the database, create a `drizzle.config.ts` file at the root of your project and add the following configuration:

1. At the root of your project, create a `drizzle.config.ts` and add the following configuration:
```typescript {{ filename: 'drizzle.config.ts' }}
import { defineConfig } from "drizzle-kit";
import { loadEnvConfig } from "@next/env";
```typescript {{ filename: 'drizzle.config.ts' }}
import { defineConfig } from "drizzle-kit";
import { loadEnvConfig } from "@next/env";

loadEnvConfig(process.cwd());
loadEnvConfig(process.cwd());

if (!process.env.DATABASE_URL) {
throw new Error("DATABASE_URL must be a Neon postgres connection string");
}
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",
out: "./migrations",
});
```
1. Generate and run the database migrations by running the following commands:
```sh {{ filename: 'terminal' }}
npx drizzle-kit generate
npx drizzle-kit migrate
```
export default defineConfig({
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL,
},
schema: "./app/db/schema.ts",
});
```

### Create the UI of the home page
1. To push the schema to the database, run the following command:
```sh {{ filename: 'terminal' }}
npx drizzle-kit push
```

Add the following code to the `app/page.tsx` file to create the UI of the home page:
### Create Server Actions to handle user interactions

```tsx {{ filename: 'app/page.tsx' }}
import { createUserMessage, deleteUserMessage } from "./actions";
To handle form submissions for adding and deleting user messages, create two Server Actions in `app/actions.ts`. Use the [`auth()`](/docs/references/nextjs/auth) function from Clerk to obtain the user ID, which will be used to interact with the database.

```typescript {{ filename: 'app/actions.ts' }}
"use server";

import { auth } from "@clerk/nextjs/server";
import { UserMessages } from "./db/schema";
import { db } from "./db";
import { currentUser } from "@clerk/nextjs/server";
import { eq } from "drizzle-orm";

export async function createUserMessage(formData: FormData) {
const { userId } = auth();
if (!userId) throw new Error("User not found");

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, user.id),
const message = formData.get("message") as string;
await db.insert(UserMessages).values({
user_id: user.id,
message,
});
}

export async function deleteUserMessage() {
const { userId } = auth();
if (!userId) throw new Error("User not found");

await db.delete(UserMessages).where(eq(UserMessages.user_id, user.id));
}
```

### Create the UI for the Home Page

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()`](/docs/references/nextjs/auth) 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.

```tsx {{ filename: 'app/page.tsx' }}
import { createUserMessage, deleteUserMessage } from "./actions";
import { db } from "./db";
import { auth } from "@clerk/nextjs/server";

export default async function Home() {
const existingMessage = await getUserMessage();
const { userId } = auth();
if (!userId) throw new Error("User not found");
const existingMessage = db.query.UserMessages.findFirst({
where: (messages, { eq }) => eq(messages.user_id, userId),
});

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>
<main>
<h1>Neon + Clerk Example</h1>
{existingMessage ? (
<div className="text-center">
<p className="text-xl mb-4">{existingMessage.message}</p>
<div>
<p>{existingMessage.message}</p>
<form action={deleteUserMessage}>
<button type="submit" className="bg-red-500 text-white px-4 py-2 rounded">
<button>
Delete Message
</button>
</form>
</div>
) : (
<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">
<form action={createUserMessage}>
<input type="text" name="message" placeholder="Enter a message" />
<button>
Save Message
</button>
</form>
Expand All @@ -194,42 +212,6 @@ export default async function Home() {
}
```

### 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.
1. Paste the following code example:
```typescript {{ filename: 'app/actions.ts' }}
"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({
user_id: user.id,
message,
});
redirect("/");
}

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, user.id));
redirect("/");
}
```

### 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.
Expand Down
Loading