Skip to content

Commit

Permalink
improve Neon guide, highlighting API use in Next.js (#1234)
Browse files Browse the repository at this point in the history
Co-authored-by: Alexis Aguilar <[email protected]>
  • Loading branch information
kevinzunigacuellar and alexisintech authored Jul 16, 2024
1 parent 94ee6c8 commit 8baaa7e
Showing 1 changed file with 88 additions and 106 deletions.
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

0 comments on commit 8baaa7e

Please sign in to comment.