From 34b980fc3a725b7d9f4aba458999955b89ec7a56 Mon Sep 17 00:00:00 2001 From: Jeff Escalante Date: Thu, 6 Mar 2025 15:58:47 -0500 Subject: [PATCH 1/2] add clarification to clerk as IDP docs page --- docs/advanced-usage/clerk-idp.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/advanced-usage/clerk-idp.mdx b/docs/advanced-usage/clerk-idp.mdx index 0fc1d12f5d..c42fccdaf2 100644 --- a/docs/advanced-usage/clerk-idp.mdx +++ b/docs/advanced-usage/clerk-idp.mdx @@ -12,6 +12,8 @@ Clerk can be configured as an OAuth 2.0 and OpenID Connect (OIDC) provider to fa You can use Clerk as an Identity Provider (IdP) if you want your users to authenticate to a third party site or a tool using their credentials from your application. **This is not the same as supporting an OAuth provider, such as Google, in your application. If you want your users to be able to sign in to your application with an OAuth provider, see the [dedicated guide](/docs/authentication/social-connections/oauth).** +It's important to note that this feature only currently supports the initial authentication flow from your application to the third party site or tool. The OAuth token returned at the end of the flow is not a [Clerk session JWT](/docs/how-clerk-works/overview#session-token) and cannot be used to make authenticated requests from third-party applications to Clerk-protected API endpoints in your app. If you are interested in Clerk adding support for resource access via OAuth tokens, please [add your feedback to this feature request](https://feedback.clerk.com/roadmap/c05eb8be-ae4e-4b7c-ab1e-e0eaf6b4b38a). + ## How it works Clerk is the OAuth 2.0 and OIDC provider for your application. The "client" is the third party site or tool that you want your users to authenticate to. From 2de444a7b437d4ff0d4f385cd1b32e4169b7b38f Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Fri, 7 Mar 2025 09:41:28 -0500 Subject: [PATCH 2/2] docs review --- docs/advanced-usage/clerk-idp.mdx | 6 +- docs/drizzle.mdx | 924 ++++++++++++++++++++++++++++++ 2 files changed, 927 insertions(+), 3 deletions(-) create mode 100644 docs/drizzle.mdx diff --git a/docs/advanced-usage/clerk-idp.mdx b/docs/advanced-usage/clerk-idp.mdx index c42fccdaf2..38e1c64ad2 100644 --- a/docs/advanced-usage/clerk-idp.mdx +++ b/docs/advanced-usage/clerk-idp.mdx @@ -12,11 +12,11 @@ Clerk can be configured as an OAuth 2.0 and OpenID Connect (OIDC) provider to fa You can use Clerk as an Identity Provider (IdP) if you want your users to authenticate to a third party site or a tool using their credentials from your application. **This is not the same as supporting an OAuth provider, such as Google, in your application. If you want your users to be able to sign in to your application with an OAuth provider, see the [dedicated guide](/docs/authentication/social-connections/oauth).** -It's important to note that this feature only currently supports the initial authentication flow from your application to the third party site or tool. The OAuth token returned at the end of the flow is not a [Clerk session JWT](/docs/how-clerk-works/overview#session-token) and cannot be used to make authenticated requests from third-party applications to Clerk-protected API endpoints in your app. If you are interested in Clerk adding support for resource access via OAuth tokens, please [add your feedback to this feature request](https://feedback.clerk.com/roadmap/c05eb8be-ae4e-4b7c-ab1e-e0eaf6b4b38a). +This feature only currently supports the initial authentication flow from your application to the third party site or tool. The OAuth token returned at the end of the flow is not a [Clerk session JWT](/docs/how-clerk-works/overview#session-token) and cannot be used to make authenticated requests from third-party applications to Clerk-protected API endpoints in your app. If you are interested in Clerk adding support for resource access via OAuth tokens, please [add your feedback to this feature request](https://feedback.clerk.com/roadmap/c05eb8be-ae4e-4b7c-ab1e-e0eaf6b4b38a). -## How it works +## Configure Clerk as an IdP -Clerk is the OAuth 2.0 and OIDC provider for your application. The "client" is the third party site or tool that you want your users to authenticate to. +Clerk is the OAuth 2.0 and OIDC Identity Provider (IdP) for your application. The "client" is the third party site or tool that you want your users to authenticate to. In order to make your Clerk instance operate as a provider, create an OAuth application in the Clerk Dashboard. Then, configure the client to work with your Clerk instance, using the necessary data from your Clerk OAuth application. diff --git a/docs/drizzle.mdx b/docs/drizzle.mdx new file mode 100644 index 0000000000..b856e1d7ba --- /dev/null +++ b/docs/drizzle.mdx @@ -0,0 +1,924 @@ +In this tutorial, you'll build a Next.js App Router app with Clerk, Drizzle, tRPC, and many other modern and popular technologies. The app will be a simple blog app that allows you to create and display posts. + +The tech stack you'll use: + +- Next.js App Router +- Clerk (Authentication) +- Drizzle (Database ORM) +- Vercel (Deploying your app and creating your database) +- Neon (Postgres database) +- tRPC (Type-safe API endpoint wrapper) +- Tanstack Query (Data fetching and caching) +- Zod (Schema validation) +- Tailwind (Styling your app) + +First, you'll create a Next.js App Router app with Clerk. Then, you'll get your app up and running using Prisma. To do this, you'll create a Neon database in Vercel, deploy your app to Vercel, and build functionality in your app to use Prisma to create and display posts. You can stop here, or you can continue on to add tRPC and zod to your app for enhanced type-safety. You'll set up your tRPC server and create endpoints/procedures for your queries and mutations. Then you'll set up your tRPC client and replace the Prisma queries and mutations with the tRPC procedures using Tanstack Query. Lastly, you'll learn how to create protected procedures using Clerk's authentication context. + +> Check out the finished product in Clerk's demo repository: +> [https://github.com/clerk/clerk-nextjs-trpc](https://github.com/clerk/clerk-nextjs-trpc) + +## Create a Next.js + Clerk app + +To create a Next.js app with Clerk, follow the [quickstart in the Clerk Docs](https://clerk.com/docs/quickstarts/nextjs). + +Or, you can clone the [Clerk repository](https://github.com/clerk/clerk-nextjs-app-quickstart), which is the result of following the quickstart: + +``` +gh repo clone clerk/clerk-nextjs-app-quickstart +``` + +### Create a Clerk application + +The Clerk quickstart gets you started with Clerk in keyless mode, which allows you to try Clerk's authentication features in your app without having to create a Clerk account. You will want to create a Clerk account and an application in the [Clerk Dashboard](https://dashboard.clerk.com). The Clerk Dashboard is where you, as the application owner, can manage your application's settings, users, and organizations. For example, if you want to enable phone number authentication, multi-factor authentication, social providers like Google, delete users, or create organizations, you can do all of this and more in the Clerk Dashboard. + +### Set your Clerk API keys + +You need to set your Clerk API keys in your app so that your app can use the configuration settings that you set in the Clerk Dashboard. + +1. In the Clerk Dashboard, navigate to the [**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page. +1. In the **Quick Copy** section, copy your Clerk Publishable and Secret Keys. +1. In your `.env` file, set the `CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` environment variables to the values you copied from the Clerk Dashboard. + +```env +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY={{pub_key}} +CLERK_SECRET_KEY={{secret_key}} +``` + +## Install dependencies and test your app + +While developing, it's best practice to keep your project running so that you can test your changes as you work. So, let's make sure the app is working as expected. + +1. Run the following commands to install the dependencies and start the development server: + ```bash + npm install + npm run dev + ``` +1. Visit your app at `http://localhost:3000`. It should render a new Next.js app, but with a "Sign in" and "Sign up" button in the top right corner. + ![The development instance running.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8prf7etz7hzixahc4ch.png) +1. Select the "Sign in" button. You should be redirected to your Clerk [Account Portal sign-in](https://clerk.com/docs/account-portal/overview#sign-in) page, which renders Clerk's [``](https://clerk.com/docs/components/sign-in) component. The `` component will look different depending on the configuration of your Clerk instance. + ![A Clerk Account Portal sign-in page.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hqmyyifqbnwmo8usydcr.png) +1. Sign in to your Clerk application. +1. You should be redirected back to your app, where you should see Clerk's [``](https://clerk.com/docs/components/user/user-button) component in the top right corner. + +## Install Drizzle + +Run the following commands to install Drizzle and the `dotenv` package to load environment variables: + +```bash +npm install drizzle-orm drizzle-kit dotenv +``` + +## Configure Drizzle + +You must configure Drizzle to connect to your database, and tell Drizzle where everything in your app is with a configuration file. + +1. Create a `db` directory in the root of your project. +1. In the `db` directory, create a `drizzle.ts` file with the following code: + ```ts + import { config } from 'dotenv' + import { drizzle } from 'drizzle-orm/neon-http' + + config({ path: '.env' }) + + export const db = drizzle(process.env.DATABASE_URL!) + ``` +1. At the root of your app, create a `drizzle.config.ts` file with the following code: + ```ts + import { config } from 'dotenv' + import { defineConfig } from 'drizzle-kit' + + config({ path: '.env' }) + + export default defineConfig({ + schema: './db/schema.ts', + out: './migrations', + dialect: 'postgresql', + dbCredentials: { + url: process.env.DATABASE_URL!, + }, + }) + ``` + +## Install `@vercel/postgres` + +Because you'll be using Vercel to create your database, you must install the `@vercel/postgres` package to connect to your database. + +```bash +npm i @vercel/postgres +``` + +## Deploy to Vercel + +Before you can create a database using Vercel, you first need to deploy your app to Vercel. + +1. Create a repository on GitHub for your app. If you're not sure how to do this, follow the [GitHub docs](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository). +1. Go to [Vercel](https://vercel.com) and add a new project. While going through the process, select the **Environment Variables** dropdown, and add your Clerk Publishable and Secret Keys. + ![Vercel dashboard showing where to input environment variables](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7niyxtxctruxdgcpq9db.png) +1. Select **Deploy** to deploy your app to Vercel. +1. Select the **Settings** tab. +1. In the left sidenav, select **Functions**. +1. Under **Function Region**, there should be a tag next to one of the continents. Select the continent where the tag is, and the dropdown will reveal what regions on Vercel's network that your Vercel Functions will execute in. Take note of the region. Keep the Vercel dashboard open. + ![Vercel dashboard with an arrow pointing to a tag that says "iad1", and an arrow pointing to a highlighted element that says "Washington, D.C., USA (EAST) - us-east-1 - iad1"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y69ozc9l7d812g9nj75u.png) + +## Spin up a database + +1. While still in Vercel's dashboard, select the **Storage** tab. +1. Select **Create Database**. +1. Select **Neon** as the database provider and select **Continue**. +1. Select the **Region** dropdown and select the region you noted earlier. You want your database's region to match your Vercel Functions region for optimal performance. +1. Select **Continue**. +1. When connecting to the database, select **Advanced options** and under **Environment Variables Prefix**, enter “DATABASE” so that the environment variable is “DATABASE\_URL”. Then select **Connect**. +1. Copy the environment variables and add them to your `.env` file. They should look something like this: + +```env +# Recommended for most uses +DATABASE_URL=*** + +# For uses requiring a connection without pgbouncer +DATABASE_URL_UNPOOLED=*** + +# Parameters for constructing your own connection string +PGHOST=*** +PGHOST_UNPOOLED=*** +PGUSER=*** +PGDATABASE=*** +PGPASSWORD=*** + +# Parameters for Vercel Postgres Templates +POSTGRES_URL=*** +POSTGRES_URL_NON_POOLING=*** +POSTGRES_USER=*** +POSTGRES_HOST=*** +POSTGRES_PASSWORD=*** +POSTGRES_DATABASE=*** +POSTGRES_URL_NO_SSL=*** +POSTGRES_PRISMA_URL=*** +``` + +## Update your Vercel environment variables + +When you add new environment variables to your `.env` file, don't forget to update your Vercel environment variables. + +1. In Vercel's dashboard, select the **Settings** tab. +1. In the left sidenav, select **Environment Variables**. +1. Add the new environment variables to your Vercel environment variables. You don't have to copy and paste them one by one; you can copy the entire block of code and paste it into Vercel. +1. Select **Save**. + +## Create a database model + +1. In the `db` directory, create a `schema.ts` file with the following code: + ```ts + import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core' + + export const posts = sqliteTable('posts', { + id: integer('id').primaryKey(), + title: text('title'), + content: text('content'), + authorId: integer('author_id'), + }) + ``` + +## Update your database schema + +To generate migrations using drizzle-kit, run the following command: + +```bash +npx drizzle-kit generate +``` + +{/* TODO: Rewrite this section. */} + +These migrations are stored in the drizzle/migrations directory, as specified in your drizzle.config.ts. This directory will contain the SQL files necessary to update your database schema and a meta folder for storing snapshots of the schema at different migration stages. + +To run the migrations, run the following command: + +```bash +npx drizzle-kit migrate +``` + +## Set up Prisma Client + +Now it's time to set up the Prisma Client and connect it to your database. + +Run the following command to create a new `lib` directory and add a `prisma.ts` file to it. + +```bash +mkdir -p lib && touch lib/prisma.ts +``` + +Now, add the following code to your `lib/prisma.ts` file: + +```ts +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient() + +const globalForPrisma = global as unknown as { prisma: typeof prisma } + +if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma + +export default prisma +``` + +This file creates a Prisma Client and attaches it to the global object so that only one instance of the client is created in your application. This helps resolve issues with hot reloading that can occur when using Prisma ORM with Next.js in development mode. + +### Query your database + +Now that all of the set up is complete, it's time to start building out your app! + +Let's start with your homepage. Add the following code to your `app/page.tsx` file: + +```tsx +import prisma from '@/lib/prisma' // Import the Prisma Client +import Link from 'next/link' + +export default async function Page() { + const posts = await prisma.post.findMany() // Query the `Post` model for all posts + + // Display the posts on the homepage + return ( +
+

Posts

+
    + {posts.map((post) => ( +
  • + + {post.title} + by {post.authorId} + +
  • + ))} +
+ + Create New Post + +
+ ) +} +``` + +This code fetches all posts from your database and displays them on the homepage, showing the title and author ID for each post. It uses the [`prisma.post.findMany()`](https://www.prisma.io/docs/orm/reference/prisma-client-reference?utm_source=docs#findmany) method, which is a Prisma Client method that retrieves all records from the database. + +That shows how to query for all records, but how do you query for a single record? + +### Query a single record + +Let's add a page that displays a single post. + +1. In `app/`, create a new `posts` directory. +1. In `posts/`, create an `[id]` directory. +1. In `posts/[id]`, add a `page.tsx` file. + +Add the following code to your `app/posts/[id]/page.tsx` file: + +```tsx +import prisma from '@/lib/prisma' +import { use } from 'react' + +export default async function Post({ params }: { params: Promise<{ id: string }> }) { + // Params are wrapped in a promise, so we need to unwrap them using React's `use()` hook + const unwrappedParams = use(params) + const { id } = unwrappedParams + const post = await prisma.post.findUnique({ + where: { id: parseInt(id) }, + }) + + if (!post) { + return ( +
+
No post found.
+
+ ) + } + + return ( +
+ {post && ( +
+

{post.title}

+

by {post.authorId}

+
+ {post.content || 'No content available.'} +
+
+ )} +
+ ) +} +``` + +This code uses the URL parameters to get the post's ID, and then fetches it from your database and displays it on the page, showing the title, author ID, and content. It uses the [`prisma.post.findUnique()`](https://www.prisma.io/docs/orm/reference/prisma-client-reference?utm_source=docs#findunique) method, which is a Prisma Client method that retrieves a single record from the database. + +Test the page by navigating to a post's URL. For example, `http://localhost:3000/posts/1`. For now, it should show a "No post found" message because you haven't created any posts yet. Let's add a way to create posts. + +### Create a new post + +1. In `app/posts/`, create a new `create` directory. +1. In `posts/create`, add a `page.tsx` file. + +Add the following code to your `app/posts/create/page.tsx` file: + +```tsx +import Form from 'next/form' +import prisma from '@/lib/prisma' +import { redirect } from 'next/navigation' +import { SignInButton, useAuth } from '@clerk/nextjs' +import { revalidatePath } from 'next/cache' +import { auth } from '@clerk/nextjs/server' + +export default async function NewPost() { + const { userId } = await auth() + + // Protect this page from unauthenticated users + if (!userId) { + return ( +
+

You must be signed in to create a post.

+ + + +
+ ) + } + + async function createPost(formData: FormData) { + 'use server' + + // Type check + if (!userId) return + + const title = formData.get('title') as string + const content = formData.get('content') as string + + await prisma.post.create({ + data: { + title, + content, + authorId: userId, + }, + }) + + revalidatePath('/') + redirect('/') + } + + return ( +
+

Create New Post

+
+
+ + +
+
+ +