<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Evan Shortiss</title>
    <description>The latest articles on DEV Community by Evan Shortiss (@evanatneon).</description>
    <link>https://dev.to/evanatneon</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1276371%2F6b4633eb-00a5-42e6-8bff-4f29df993325.png</url>
      <title>DEV Community: Evan Shortiss</title>
      <link>https://dev.to/evanatneon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/evanatneon"/>
    <language>en</language>
    <item>
      <title>Next.js authentication using Clerk, Drizzle ORM, and Neon</title>
      <dc:creator>Evan Shortiss</dc:creator>
      <pubDate>Mon, 01 Apr 2024 18:11:47 +0000</pubDate>
      <link>https://dev.to/neon-postgres/nextjs-authentication-using-clerk-drizzle-orm-and-neon-95d</link>
      <guid>https://dev.to/neon-postgres/nextjs-authentication-using-clerk-drizzle-orm-and-neon-95d</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NyRuyrsf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/neon-auth-clerk-1-1-1024x538.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NyRuyrsf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/neon-auth-clerk-1-1-1024x538.jpg" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Building an effective authentication and authorization system into your application is as equally fraught with challenges as managing database infrastructure. As the old wisdom says, never “roll your own” regarding authentication and authorization. &lt;/p&gt;

&lt;p&gt;In addition to the effort required to develop the login, logout, and social provider integration, you must consider compliance with standards such as SOC 2 and HIPAA, multi-factor authentication, and user management. &lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://clerk.com/" rel="noopener noreferrer"&gt;Clerk&lt;/a&gt; enables you to seamlessly add advanced authentication and user management features to your application and comply with data security regulations by storing personally identifiable information in their compliant infrastructure. &lt;/p&gt;

&lt;p&gt;In this article, you’ll learn how to use Clerk to add authentication to a &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; application and how to use Clerk’s Next.js helpers to obtain user details in your components. You’ll also learn to use &lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;Drizzle ORM&lt;/a&gt; to store non-PII data in Neon’s serverless Postgres.&lt;/p&gt;

&lt;p&gt;A complete example application with instructions for local development and deployment on Vercel can be found on GitHub at &lt;a href="https://github.com/evanshortiss/neon-clerk-drizzle-nextjs" rel="noopener noreferrer"&gt;evanshortiss/neon-clerk-drizzle-nextjs&lt;/a&gt;. A live preview of the sample application is hosted on Vercel; try it out at &lt;a href="https://neon-clerk-drizzle-nextjs.vercel.app/" rel="noopener noreferrer"&gt;neon-clerk-drizzle-nextjs.vercel.app&lt;/a&gt; and vote for your favorite periodic element!&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution Architecture
&lt;/h2&gt;

&lt;p&gt;Before diving into the implementation, let’s review the high-level architecture of this application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wOlgNRCN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/clerk-solution-arch-1024x567.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wOlgNRCN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/clerk-solution-arch-1024x567.jpg" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vercel will provide serverless hosting for your Next.js application. The Next.js application will direct your users to Clerk for authentication. Clerk will redirect users to your application once they’ve completed an authentication flow using a supported provider such as Google or Discord &lt;a href="https://oauth.net/2/" rel="noopener noreferrer"&gt;OAuth&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Once a user is signed in, they can access protected pages and routes in your application, and you can &lt;a href="https://clerk.com/docs/references/nextjs/read-session-data" rel="noopener noreferrer"&gt;read their session data&lt;/a&gt; as needed to obtain the user ID to query or associate data with the authenticated user in your Neon Postgres database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sign Up to Neon and Configure Postgres
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://console.neon.tech/signup" rel="noopener noreferrer"&gt;Sign up for Neon&lt;/a&gt; and create a project. This project will contain the Postgres database and a &lt;code&gt;user_messages&lt;/code&gt; table that you will use to follow along with the rest of this article.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enter a project name.&lt;/li&gt;
&lt;li&gt;Use the default database name of &lt;code&gt;neondb&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Choose the region closest to the location where your application will be deployed.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Create project&lt;/strong&gt; button.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You’ll instantly be provided with a connection string you can use to connect to your serverless Postgres database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GUV6NGzi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/neon-dashboard-1024x547.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GUV6NGzi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/neon-dashboard-1024x547.jpg" alt="Neon Project Dashboard at console.neon.tech" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sign Up to Clerk and Configure Application Sign In
&lt;/h2&gt;

&lt;p&gt;Visit &lt;a href="https://dashboard.clerk.com" rel="noopener noreferrer"&gt;dashboard.clerk.com&lt;/a&gt; and sign up, or sign in if you’re an existing user. Create a new application and enable some of the available sign-in options. You can see that I’ve enabled Discord and Google as sign-in options for my application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oj-Zfapb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/clerk-sign-up-1024x569.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oj-Zfapb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/clerk-sign-up-1024x569.jpg" alt="Clerk sign up page" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the application has been created on Clerk, you’ll find your API keys on the &lt;strong&gt;Home&lt;/strong&gt; screen of your application in the Clerk dashboard. Specifically, you’ll need the API keys listed in the Next.js section soon!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gnZg_3a5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/clerk-dashboard-1024x570.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gnZg_3a5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/clerk-dashboard-1024x570.jpg" alt="Clerk Dashboard showing Next.js environment variables" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Neon’s Serverless Driver with Next.js and Drizzle ORM
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create a Next.js Project and Install Dependencies
&lt;/h3&gt;

&lt;p&gt;Create a Next.js application in your development environment. This requires Node.js v18 or newer to be installed in your development environment. This article assumes you use the following options when creating your Next.js application using &lt;a href="https://nextjs.org/docs/pages/api-reference/create-next-app" rel="noopener noreferrer"&gt;create-next-app&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@14.1 neon-clerk-next &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--typescript&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--eslint&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--tailwind&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--use-npm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--app&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--src-dir&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--import-alias&lt;/span&gt; &lt;span class="s2"&gt;"@/*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your Next.js application has been created, change to the project directory in your terminal, then add the &lt;a href="https://github.com/neondatabase/serverless/" rel="noopener noreferrer"&gt;Neon serverless driver&lt;/a&gt; and Drizzle ORM to your project’s dependencies using npm or your preferred package manager.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @neondatabase/serverless drizzle-orm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a Schema and Database Connection
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;em&gt;src/app/db/schema.ts&lt;/em&gt; and define a &lt;code&gt;user_messages&lt;/code&gt; schema using the types provided by Drizzle ORM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: src/app/db/schema.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pgTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-orm/pg-core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * This table stores quotes submitted by users.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pgTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This will be the user ID provided by Clerk&lt;/span&gt;
  &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;createTs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create_ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;defaultNow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use Neon’s serverless driver with Drizzle ORM, create a file named &lt;em&gt;src/app/db/index.ts&lt;/em&gt; and add the following code. Exporting the Drizzle instance from a file means it’s created on application startup and exported as a &lt;a href="https://en.wikipedia.org/wiki/Singleton_pattern" rel="noopener noreferrer"&gt;singleton&lt;/a&gt; that other modules can import and reuse to execute &lt;a href="https://en.wikipedia.org/wiki/Typesafe" rel="noopener noreferrer"&gt;typesafe&lt;/a&gt; SQL queries against your Postgres database hosted by Neon.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: src/app/db/index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;neon&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@neondatabase/serverless&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;drizzle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-orm/neon-http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserMessages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DATABASE_URL must be a Neon postgres connection string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;neon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;drizzle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserMessages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a file named &lt;em&gt;.env.local&lt;/em&gt; in the root of the Next.js project directory and add your &lt;a href="https://neon.tech/docs/connect/connect-from-any-app" rel="noopener noreferrer"&gt;database URL from the Neon Console&lt;/a&gt; as an environment variable named DATABASE_URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Copy this from your project dashboard on https://console.neon.tech&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresql://user:pass@ep-adj-noun-12345.us-east-2.aws.neon.tech/mydatabase?sslmode&lt;span class="o"&gt;=&lt;/span&gt;require&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start your Application
&lt;/h3&gt;

&lt;p&gt;Use the npm run dev command to start your Next.js application in development mode and confirm it’s available at &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;. You won’t be prompted to authenticate since you haven’t added the Clerk middleware to your application yet – you’ll take care of that soon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9uU34Ruj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/next-local-dev-1024x591.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9uU34Ruj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/next-local-dev-1024x591.jpg" alt="A Next.js application running at localhost:3000" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate and Apply Database Migrations using Drizzle Kit
&lt;/h2&gt;

&lt;p&gt;Before using Drizzle ORM to perform database operations in your application, you’ll need to generate and apply schema migrations to your database. &lt;a href="https://orm.drizzle.team/kit-docs/overview" rel="noopener noreferrer"&gt;Drizzle Kit&lt;/a&gt; streamlines this process by detecting changes in your &lt;em&gt;schema.ts&lt;/em&gt; file and generating the necessary SQL migrations to apply to your database.&lt;/p&gt;

&lt;p&gt;To get started, add Drizzle Kit and &lt;a href="https://www.npmjs.com/package/dotenv" rel="noopener noreferrer"&gt;dotenv&lt;/a&gt; to your project’s development dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i drizzle-kit dotenv &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a file named &lt;em&gt;drizzle.config.ts&lt;/em&gt; at the root of your repository and add the following configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-kit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Read the .env file if it exists, or a file specified by the&lt;/span&gt;
&lt;span class="c1"&gt;// dotenv_config_path parameter that's passed to Node.js&lt;/span&gt;
&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DATABASE_URL not found in environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/app/db/schema.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/app/db/migrations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dbCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can use the generate command from the Drizzle Kit CLI to generate migration files. Run the following command to generate migrations for your user schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx drizzle-kit generate:pg &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;dotenv_config_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'.env.local'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new &lt;em&gt;src/app/db/migrations&lt;/em&gt; folder will be created, and an SQL migration file will be created that contains a &lt;code&gt;CREATE TABLE&lt;/code&gt; statement for the &lt;code&gt;user_messages&lt;/code&gt; table. You can read more about &lt;a href="https://orm.drizzle.team/kit-docs/overview" rel="noopener noreferrer"&gt;migrations in the Drizzle documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Use the push command from the Drizzle Kit CLI to apply the migrations to your Postgres database on Neon. The following command runs the &lt;code&gt;push&lt;/code&gt; command from Drizzle Kit and sets the &lt;a href="https://www.npmjs.com/package/dotenv" rel="noopener noreferrer"&gt;config path for dotenv&lt;/a&gt; to load the &lt;code&gt;DATABASE_URL&lt;/code&gt; from your &lt;em&gt;.env.local&lt;/em&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx drizzle-kit push:pg &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;dotenv_config_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'.env.local'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit the &lt;strong&gt;Tables&lt;/strong&gt; view in the Neon Console, and your new &lt;code&gt;user_messages&lt;/code&gt; table will be listed once the push command has finished.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PgF-ObLj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/neon-tables-view-1024x559.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PgF-ObLj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/neon-tables-view-1024x559.jpg" alt="Table view in console.neon.tech" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Authentication to your Next.js Application using Clerk
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://nextjs.org/docs/app/building-your-application/authentication" rel="noopener noreferrer"&gt;Next.js documentation&lt;/a&gt; provides a comprehensive overview of authentication, session management, and authorization with sample code. This requires writing a non-trivial amount of code and middleware. Since you’re using Clerk, it will handle this complexity for you!&lt;/p&gt;

&lt;p&gt;To start, install Clerk’s Next.js package and add it to your Next.js project’s dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @clerk/nextjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update your &lt;em&gt;layout.tsx&lt;/em&gt; file to use the &lt;a href="https://clerk.com/docs/components/clerk-provider" rel="noopener noreferrer"&gt;&lt;code&gt;ClerkProvider&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://clerk.com/docs/components/user/user-button" rel="noopener noreferrer"&gt;&lt;code&gt;UserButton&lt;/code&gt;&lt;/a&gt; from the &lt;code&gt;@clerk/nextjs package&lt;/code&gt;. The &lt;code&gt;ClerkProvider&lt;/code&gt; provides access to the current session and user context. The &lt;code&gt;UserButton&lt;/code&gt; renders a component that shows the user’s profile picture, provides access to account management, and a sign-out button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nb"&gt;Readonly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ClerkProvider&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;inter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p-4 bg-white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserButton&lt;/span&gt; &lt;span class="nx"&gt;showName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/UserButton&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/html&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ClerkProvider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a file named &lt;em&gt;src/middleware.ts&lt;/em&gt;, and add the &lt;code&gt;authMiddleware&lt;/code&gt; provided by Clerk as shown in the following snippet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authMiddleware&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;authMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Routes that should be accessible without signing in can&lt;/span&gt;
  &lt;span class="c1"&gt;// be defined as strings in this array, e.g, your home page&lt;/span&gt;
  &lt;span class="na"&gt;publicRoutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Protects all routes, including api/trpc:&lt;/span&gt;
  &lt;span class="c1"&gt;// https://clerk.com/docs/references/nextjs/auth-middleware&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/((?!.+&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.[&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;w]+$|_next).*)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/(api|trpc)(.*)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, add the API keys provided in Next.js format on the Clerk dashboard to your &lt;em&gt;.env.local&lt;/em&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Copy these from https://dashboard.clerk.com/&lt;/span&gt;
&lt;span class="nv"&gt;NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pk_test_youruniquevalue
&lt;span class="nv"&gt;CLERK_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk_test_youruniquevalue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start your application using the npm run dev command, and visit &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in your web browser. If you’re prompted to sign in to your application, everything is going as planned!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XgQwIHlO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/clerk-app-signin-1024x557.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XgQwIHlO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/clerk-app-signin-1024x557.jpg" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Store Data in Postgres associated with a Clerk User ID
&lt;/h2&gt;

&lt;p&gt;The final step in the process is to add some interactivity to your application. You will let users store their favorite quote in your Neon Postgres database. This will involve defining &lt;a href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations" rel="noopener noreferrer"&gt;Next.js Server Actions&lt;/a&gt; and a &lt;a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components" rel="noopener noreferrer"&gt;Server Component&lt;/a&gt; that uses them.&lt;br&gt;&lt;br&gt;
Start by creating a file named &lt;em&gt;src/app/actions.ts&lt;/em&gt; and add the following code to define &lt;code&gt;createUserMessage&lt;/code&gt; and &lt;code&gt;deleteUserMessage&lt;/code&gt; actions. Both actions obtain the user ID from the current session using &lt;a href="https://clerk.com/docs/references/nextjs/current-user" rel="noopener noreferrer"&gt;Clerk’s currentUser helper&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@clerk/nextjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserMessages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./db/schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;eq&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-orm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createUserMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UserMessages&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;message&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deleteUserMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UserMessages&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UserMessages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following code to &lt;em&gt;src/app/page.tsx&lt;/em&gt; to create a minimalist web page that uses your actions. This enables users to create or delete a quote associated with their user ID. If you recall the schema you defined earlier using Drizzle ORM, it uses the user ID as the primary key. This means each user can store a single quote.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createUserMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deleteUserMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./actions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUserMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UserMessages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findFirst&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;eq&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getUserMessage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;existingMessage&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-2/3 text-center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-3xl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;existingMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;deleteUserMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full rounded px-8 pt-6 pb-8 mb-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full text-center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Delete Quote&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bg-[#00E699] transition-colors hover:bg-[#00e5BF] text-gray-800 font-semibold py-2 px-4 rounded focus:outline-none cursor-pointer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;createUserMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shadow-md w-2/3 rounded px-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mb-6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mistakes are the portals of discovery - James Joyce&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-center appearance-none border rounded w-full p-3 text-gray-700 leading-tight focus:outline-none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full text-center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Save Message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bg-[#00E699] cursor-pointer transition-colors hover:bg-[#00e5BF] text-gray-800 font-semibold py-2 px-4 rounded focus:outline-none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;



  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex -mt-16 min-h-screen flex-col align-center justify-center items-center px-24&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-2xl pb-6 text-gray-400&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;existingMessage&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your quote is wonderful...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save an inspiring quote for yourself...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start your application using &lt;code&gt;npm run dev&lt;/code&gt;, visit &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;, and sign in using one of your chosen providers. You will be redirected to your application after signing in. The application displays a web page that contains an input field where you can submit a quote to associate with your user account and a lovely header with your username and profile picture powered by Clerk’s &lt;code&gt;UserButton&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Rzkc-MOG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/next-app-with-auth-1024x607.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Rzkc-MOG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/next-app-with-auth-1024x607.jpg" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Type in your favorite quote, for example, “To infinity and beyond!” by Buzz Lightyear. Click the &lt;strong&gt;Save Quote&lt;/strong&gt; button or press Enter to save your quote. When you visit &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in the future, the quote associated with your user ID will be displayed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mRZiWhzB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/next-app-quote-1024x591.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mRZiWhzB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/03/next-app-quote-1024x591.jpg" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Congratulations, you’ve built a Next.js application that integrates with Clerk for user management and authentication and uses Neon’s serverless Postgres as its database.&lt;/p&gt;

&lt;p&gt;Now that you’ve got an application up and running, you should visit &lt;a href="https://clerk.com/docs/quickstarts/nextjs#next-steps" rel="noopener noreferrer"&gt;Clerk’s Next.js documentation&lt;/a&gt; to learn how to deploy your application to production. You can use Neon’s Vercel Integration to manage development and preview database branches when you deploy your Next.js application on Vercel, which uses Neon’s serverless Postgres. &lt;/p&gt;

&lt;p&gt;We would love to get your feedback. Follow us on &lt;a href="https://twitter.com/neondatabase" rel="noopener noreferrer"&gt;X&lt;/a&gt;, join us on &lt;a href="https://neon.tech/discord" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;, and let us know how we can help you build the next generation of applications.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>clerk</category>
      <category>security</category>
      <category>drizzle</category>
    </item>
    <item>
      <title>Build a Real-time Materialized View from Postgres Changes using Confluent’s ksqlDB</title>
      <dc:creator>Evan Shortiss</dc:creator>
      <pubDate>Wed, 28 Feb 2024 17:33:42 +0000</pubDate>
      <link>https://dev.to/neon-postgres/build-a-real-time-materialized-view-from-postgres-changes-using-confluents-ksqldb-1if8</link>
      <guid>https://dev.to/neon-postgres/build-a-real-time-materialized-view-from-postgres-changes-using-confluents-ksqldb-1if8</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fneon-ksqldb-materialized-views-1024x576.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fneon-ksqldb-materialized-views-1024x576.jpeg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Neon’s support for Postgres’ logical replication features opens up a variety of interesting use cases, including for real-time streaming architectures based on &lt;a href="https://neon.tech/blog/change-data-capture-with-serverless-postgres#why-cdc-matters" rel="noopener noreferrer"&gt;change data capture&lt;/a&gt;. We previously demonstrated how to use &lt;a href="https://dev.to/neon-postgres/fan-out-from-postgres-with-change-data-capture-using-debezium-and-upstash-redis-439f"&gt;Debezium to fan-out changes&lt;/a&gt; from Postgres by using Redis as a message broker. &lt;/p&gt;

&lt;p&gt;Today we’ll explore how you can leverage the &lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;Apache Kafka&lt;/a&gt; and &lt;a href="https://kafka.apache.org/documentation/#connect" rel="noopener noreferrer"&gt;Kafka Connect&lt;/a&gt; ecosystem to capture and process changes from your Neon Postgres database. Specifically, you’ll learn how to stream changes from Postgres to Apache Kafka and process those changes using &lt;a href="https://ksqldb.io/" rel="noopener noreferrer"&gt;ksqlDB&lt;/a&gt; to create a &lt;a href="https://en.wikipedia.org/wiki/Materialized_view" rel="noopener noreferrer"&gt;materialized view&lt;/a&gt; that updates in response to database changes.&lt;/p&gt;

&lt;p&gt;It’s possible to run Apache Kafka, Kafka Connect, and ksqlDB on your infrastructure; however, this guide will be using &lt;a href="https://confluent.cloud/" rel="noopener noreferrer"&gt;Confluent Cloud&lt;/a&gt; to host these components so we can focus on enabling data streaming and building a materialized view instead of managing infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Apache Kafka with Postgres for Materialized Views?
&lt;/h2&gt;

&lt;p&gt;Postgres is a mature and battle-tested database solution that &lt;a href="https://www.postgresql.org/docs/current/rules-materializedviews.html" rel="noopener noreferrer"&gt;supports materialized views&lt;/a&gt;, so why do we need messaging infrastructure like Apache Kafka to process events and create a materialized view? We already explained the importance of avoiding the &lt;a href="https://neon.tech/blog/fan-out-postgres-changes-using-debezium-and-upstash-redis#the-dual-write-problem" rel="noopener noreferrer"&gt;dual-write problem&lt;/a&gt; when integrating your application with message brokers, so let’s focus on the data streaming and performance concerns instead.&lt;/p&gt;

&lt;p&gt;As a reminder, a materialized view stores the result of a query at a specific point in time. Let’s take a look at an example. Imagine you have a write-heavy application that involves two tables represented by the following SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;player_scores&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;player_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;fk_player&lt;/span&gt;
        &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This database contains a &lt;code&gt;players&lt;/code&gt; table to store player information and a &lt;code&gt;player_scores&lt;/code&gt; table to track their scores. You might be required to create a leaderboard table that keeps track of each player’s total score (using a &lt;a href="https://www.postgresql.org/docs/15/functions-aggregate.html" rel="noopener noreferrer"&gt;SUM aggregate function&lt;/a&gt;), retain a history of these changes, and notify subscribers about changes to the leaderboard in real-time.&lt;/p&gt;

&lt;p&gt;Using a materialized view is one option for keeping track of the total scores. The following SQL would create a materialized view to achieve this functionality in Postgres:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;MATERIALIZED&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;player_total_scores&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;player_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_score&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;players&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
    &lt;span class="n"&gt;player_scores&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping the view current requires issuing a &lt;code&gt;REFRESH MATERIALIZED VIEW&lt;/code&gt; query after each insert to the &lt;code&gt;player_scores&lt;/code&gt; table. This could have significant performance implications, doesn’t retain leaderboard history, and you still need to stream the changes to downstream subscribers reliably unless you want them to poll the database for changes.&lt;/p&gt;

&lt;p&gt;A more scalable and flexible approach involves a microservices architecture that uses change data capture with logical replication to stream player data and score events to an Apache Kafka cluster for processing, as outlined in the following architecture diagram. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fpostgres-to-debezium-kafka-ksqldb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fpostgres-to-debezium-kafka-ksqldb.jpg" alt="Using logical replication with Debezium to stream changes to Apache Kafka and process the changes using ksqlDB."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An Apache Kafka cluster is a collection of brokers (or nodes) that enable parallel processing of records by downstream subscribers such as ksqlDB. Data is organized into topics in Apache Kafka, and topics are split into partitions that are replicated across the brokers in the cluster to enable high availability. The beauty of using Apache Kafka and Kafka Connect is that connectors can source events from one system and sink them to other systems, including back to Postgres, if you want! You could even place Kafka in front of Postgres to insert score events into the database in a controlled manner.&lt;/p&gt;

&lt;p&gt;Using ksqlDB with Apache Kafka enables you to process database change events stored in topics, perform aggregation operations, and keep the results of those operations in another Kafka topic to retain leaderboard history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started with Neon and Logical Replication
&lt;/h2&gt;

&lt;p&gt;To get started, &lt;a href="https://console.neon.tech/" rel="noopener noreferrer"&gt;sign up for Neon&lt;/a&gt; and create a project. This project will contain the Postgres database that holds the &lt;code&gt;players&lt;/code&gt; and &lt;code&gt;player_scores&lt;/code&gt; tables:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enter a project name.&lt;/li&gt;
&lt;li&gt;Use default database name neondb.&lt;/li&gt;
&lt;li&gt;Choose the region closest to your location.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create project&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fcreate-project-neon-1024x540.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fcreate-project-neon-1024x540.jpg" alt="Creating your first project on https://console.neon.tech"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, visit the &lt;strong&gt;Beta&lt;/strong&gt; section of the &lt;strong&gt;Project settings&lt;/strong&gt; screen and enable logical replication. Visit our documentation to view a complete set of &lt;a href="https://neon.tech/docs/guides/logical-replication-concepts#enabling-logical-replication" rel="noopener noreferrer"&gt;logical replication guides&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Flr-enable-1024x565.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Flr-enable-1024x565.png" alt="Enabling logical replication for a project on https://console.neon.tech"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use the &lt;strong&gt;SQL Editor&lt;/strong&gt; in the Neon console to create two tables in the &lt;code&gt;neondb&lt;/code&gt; database: one to hold player information and another to hold score records for players. Each row in &lt;code&gt;player_scores&lt;/code&gt; contains a foreign key referencing a player by their ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;player_scores&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;player_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;fk_player&lt;/span&gt;
        &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;a href="https://www.postgresql.org/docs/current/logical-replication-publication.html" rel="noopener noreferrer"&gt;publication&lt;/a&gt; for these two tables. The publication defines what operations on those tables are replicated to other Postgres instances or subscribers. You’ll deploy a Debezium connector on Confluent’s cloud that uses this publication to observe changes in the specified tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;PUBLICATION&lt;/span&gt; &lt;span class="n"&gt;confluent_publication&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player_scores&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;a href="https://www.postgresql.org/docs/current/logicaldecoding-explanation.html#LOGICALDECODING-REPLICATION-SLOTS" rel="noopener noreferrer"&gt;logical replication slot&lt;/a&gt; to retain and stream changes in the &lt;a href="https://www.postgresql.org/docs/current/wal-intro.html" rel="noopener noreferrer"&gt;write-ahead log (WAL)&lt;/a&gt; to subscribers. The Debezium connector on Confluent’s cloud will use this slot to consume relevant changes from the WAL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_create_logical_replication_slot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'debezium'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pgoutput'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the &lt;strong&gt;Roles&lt;/strong&gt; section of the Neon console to &lt;a href="https://neon.tech/docs/manage/roles#manage-roles-in-the-neon-console" rel="noopener noreferrer"&gt;create a new role&lt;/a&gt; named &lt;code&gt;confluent_cdc&lt;/code&gt;. Be sure to save the password for the role someplace safe since it will only be displayed once. With the role in place, grant it permissions on the public schema using the &lt;strong&gt;SQL Editor&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;confluent_cdc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;confluent_cdc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;confluent_cdc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you’ve got everything in place to start consuming changes from your &lt;code&gt;players&lt;/code&gt; and &lt;code&gt;player_scores&lt;/code&gt; tables in your Neon Postgres database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started with Apache Kafka and Debezium on Confluent Cloud
&lt;/h2&gt;

&lt;p&gt;This guide assumes you’re new to Confluent Cloud. If you’re an existing user, you can modify the steps to integrate with your existing environments and Apache Kafka cluster(s). &lt;/p&gt;

&lt;p&gt;Sign into &lt;a href="https://confluent.cloud" rel="noopener noreferrer"&gt;https://confluent.cloud&lt;/a&gt; and follow the onboarding flow to create a basic Apache Kafka cluster. Choose the region that’s closest to your Neon Postgres database region.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-cluster-created-1-1024x552.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-cluster-created-1-1024x552.png" alt="Confluent Cloud showing a basic Apache Kafka cluster."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once your cluster has been provisioned, click on it in the &lt;strong&gt;Environments&lt;/strong&gt; screen, then select the &lt;strong&gt;Connectors&lt;/strong&gt; view from the side menu on the next page. &lt;/p&gt;

&lt;p&gt;Apache Kafka on Confluent supports a &lt;a href="https://www.confluent.io/product/connectors/" rel="noopener noreferrer"&gt;plethora of connectors&lt;/a&gt;. Many of these are based on the various open-source &lt;a href="https://kafka.apache.org/documentation/#connect" rel="noopener noreferrer"&gt;Kafka Connect&lt;/a&gt; connectors. Find and select the &lt;strong&gt;Postgres CDC Source&lt;/strong&gt; connector in the list. This connector is based on the &lt;a href="https://debezium.io/" rel="noopener noreferrer"&gt;Debezium project&lt;/a&gt; we wrote about in &lt;a href="https://dev.to/neon-postgres/fan-out-from-postgres-with-change-data-capture-using-debezium-and-upstash-redis-439f"&gt;our fan-out using Debezium and Upstash Redis article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-debezium-connector-1024x555.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-debezium-connector-1024x555.jpg" alt="The Debezuim-based Postgres CDC Source in Confluent Cloud."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the &lt;strong&gt;Add Postgres CDC Source connector&lt;/strong&gt; screen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select &lt;strong&gt;Global Access&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Generate API key &amp;amp; download&lt;/strong&gt; button to generate an API key and secret. &lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Continue&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, configure the connection between the connector and your Postgres database on Neon:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Database name: &lt;code&gt;neondb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Database server name: &lt;code&gt;neon&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;SSL mode: &lt;code&gt;require&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Database hostname: Find this on the Neon console. Refer to &lt;a href="https://neon.tech/docs/connect/connect-from-any-app" rel="noopener noreferrer"&gt;our documentation&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;Database port: &lt;code&gt;5432&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Database username: &lt;code&gt;confluent_cdc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Database password: This is the password for the &lt;code&gt;confluent_cdc&lt;/code&gt; role you created earlier.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Continue&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Configure the connector properties. The first of these is the Kafka record key and value formats. Select the following options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Output Kafka record value format: &lt;code&gt;JSON_SR&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Output Kafka record key format: &lt;code&gt;JSON_SR&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;JSON_SR&lt;/code&gt; option causes change event record schemas to be registered in your Confluent Cloud environment’s &lt;a href="https://docs.confluent.io/platform/current/schema-registry/index.html" rel="noopener noreferrer"&gt;Schema Registry&lt;/a&gt;. You can think of the schemas as type information for records in your Kafka topics. These are essential to working with the change data event records, as you’ll see shortly.  &lt;/p&gt;

&lt;p&gt;Expand the advanced configuration and set the following options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slot name: &lt;code&gt;debezium&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Publication auto create mode: &lt;code&gt;disabled&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Publication name: &lt;code&gt;confluent_publication&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Tables included: &lt;code&gt;public.players,public.player_scores&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Continue&lt;/strong&gt; , accept the default values for sizing and tasks, and give your connector a name. Once finished, your connector will be shown on the &lt;strong&gt;Connectors&lt;/strong&gt; screen. Confirm that it’s marked as &lt;strong&gt;Running&lt;/strong&gt; and not in an error state. If the connector reports an error, check the configuration properties for correctness.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-connector-running-1-1024x551.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-connector-running-1-1024x551.png" alt="Confluent Cloud dashboard showing the Postgres CDC Source connector deployed and running."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Confirm Change Data Capture is Working
&lt;/h2&gt;

&lt;p&gt;Use the &lt;strong&gt;SQL Editor&lt;/strong&gt; in the Neon console to insert some players and scores into your tables using the following SQL statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Mario'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Peach'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Bowser'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Luigi'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Yoshi'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;player_scores&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return to the Confluent Cloud console and select the &lt;strong&gt;Topics&lt;/strong&gt; item from the side menu. You will see two topics that correspond to your database tables.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-topics-created-1024x577.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-topics-created-1024x577.jpg" alt="Apache Kafka topics listed in Confluent Cloud dashboard."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on either of the topics, then use the &lt;strong&gt;Messages&lt;/strong&gt; tab to view database change events captured by the Debezium connector and streamed to the topic. Each message in Kafka contains a key and value; in this case, these are the database row ID and row contents. &lt;/p&gt;

&lt;p&gt;Apache Kafka uses partitions to increase parallelism and replicates partitions across multiple nodes to increase durability. Since Kafka partitions are an ordered immutable sequence of messages, the offset represents the message position in its partition. Topics in a production Kafka environment can be split into 100 or more partitions if necessary, to enable parallel processing by as many consumers as there are partitions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-messages-in-topic-1024x571.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-messages-in-topic-1024x571.jpg" alt="Viewing messages in a topic on Confluent Cloud."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, confirm that schemas have been registered for your change records. Select the &lt;strong&gt;Schema Registry&lt;/strong&gt; from the bottom left of the side menu in the Confluent Cloud console, and confirm that schemas have been registered for the records in your topics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-schemas-created-1024x574.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-schemas-created-1024x574.jpg" alt="Viewing auto-generated schemas in the Schema Registry on Confluent Cloud."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you click on the schema entries with a “key” suffix, you notice that the schema simply contains an id property. This property corresponds to the &lt;code&gt;id&lt;/code&gt; or primary key of the database row. The schema entries with a “value” suffix correspond to the backing table’s schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Materialized View using ksqlDB
&lt;/h2&gt;

&lt;p&gt;With schemas and topics containing messages in place, you can use ksqlDB to create a materialized view that updates in response to database changes.&lt;/p&gt;

&lt;p&gt;Select ksqlDB in the side menu for your cluster to provision a new ksqlDB instance with &lt;strong&gt;Global access&lt;/strong&gt; enabled, and use the default values for sizing and configuration. The provisioning process can take a couple of minutes, so be patient.&lt;/p&gt;

&lt;p&gt;Select your ksqlDB cluster once it’s ready, then navigate to the &lt;strong&gt;Streams&lt;/strong&gt; tab and click &lt;strong&gt;Import topics as streams&lt;/strong&gt; to import your &lt;code&gt;player_scores&lt;/code&gt; and &lt;code&gt;players&lt;/code&gt; topics as &lt;a href="https://docs.ksqldb.io/en/latest/developer-guide/ksqldb-reference/create-stream/" rel="noopener noreferrer"&gt;streams&lt;/a&gt; in ksqlDB. Creating a stream from your topic allows you to perform operations such as joins or aggregations on the data contained in the underlying topic, as you’ll see in a moment. Click &lt;strong&gt;Import&lt;/strong&gt; to create the streams.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-import-streams-1024x552.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-import-streams-1024x552.jpg" alt="Importing topics as streams in ksqlDB on Confluent Cloud."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, use the &lt;strong&gt;Editor&lt;/strong&gt; tab in the ksqlDB cluster UI to create a &lt;a href="https://docs.ksqldb.io/en/latest/concepts/tables/" rel="noopener noreferrer"&gt;table&lt;/a&gt; named &lt;code&gt;player_scores&lt;/code&gt;. The table will store an &lt;a href="https://en.wikipedia.org/wiki/Aggregate_function" rel="noopener noreferrer"&gt;aggregation&lt;/a&gt; of your system’s latest state, i.e. a materialized view. In your case, it’ll represent the sum of the score events for each player. Paste the following query into the &lt;strong&gt;Editor&lt;/strong&gt; and click &lt;strong&gt;Run query&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;player_scores&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;player_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_score&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;NEONPUBLICPLAYER_SCORES&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;player_id&lt;/span&gt;
    &lt;span class="n"&gt;EMIT&lt;/span&gt; &lt;span class="n"&gt;CHANGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a table in ksqlDB that is continuously updated in response to events in the &lt;code&gt;NEONPUBLICPLAYER_SCORES&lt;/code&gt; stream. The table will contain a row for each player with their unique ID and the sum of all associated score events.&lt;/p&gt;

&lt;p&gt;Confirm the table is working as expected by selecting &lt;code&gt;PLAYER_SCORES&lt;/code&gt; under the &lt;strong&gt;Tables&lt;/strong&gt; heading and clicking &lt;strong&gt;Query table&lt;/strong&gt;. A list of records that contain the sum of player scores will be displayed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-ksqldb-table-1024x578.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fconfluent-ksqldb-table-1024x578.jpg" alt="Querying the ksqlDB and viewing results in Confluent Cloud."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Return to the Neon console and insert more data into the &lt;code&gt;player_scores&lt;/code&gt; table. The materialized view will automatically update within a few seconds to reflect the new &lt;code&gt;total_score&lt;/code&gt; for each player.&lt;/p&gt;

&lt;p&gt;The materialized view can be consumed by interacting with the &lt;a href="https://developer.confluent.io/courses/ksqldb/hands-on-interacting-with-ksqldb/#use-the-ksqldb-rest-api-with-postman" rel="noopener noreferrer"&gt;ksqlDB REST API&lt;/a&gt;. Visit the &lt;strong&gt;API Keys&lt;/strong&gt; in your cluster’s UI to create an API key to authenticate against the ksqlDB REST API, and use the settings tab in your ksqlDB cluster UI to find the cluster’s hostname. &lt;/p&gt;

&lt;p&gt;You can use the following cURL command to get a stream of changes from the table in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--http1&lt;/span&gt;.1 &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="s2"&gt;"POST"&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;$KSQLDB_HOSTNAME&lt;/span&gt;&lt;span class="s2"&gt;/query"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/vnd.ksql.v1+json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--basic&lt;/span&gt; &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$API_KEY&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$API_SECRET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;$'{
"ksql": "SELECT * FROM PLAYER_SCORES EMIT CHANGES;",
"streamsProperties": {}
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will establish a persistent connection that streams a header followed by changes as they occur in the table in real-time. You can confirm this by using the &lt;strong&gt;SQL Editor&lt;/strong&gt; in the Neon console to insert more data into the &lt;code&gt;player_scores&lt;/code&gt; table and observing the updated total scores being streamed into your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;"header"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"queryId"&lt;/span&gt;:&lt;span class="s2"&gt;"transient_PLAYER_SCORES_7047340794163641810"&lt;/span&gt;,&lt;span class="s2"&gt;"schema"&lt;/span&gt;:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;PLAYER_ID&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="s2"&gt; INTEGER, &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;TOTAL_SCORE&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="s2"&gt; DECIMAL(10, 2)"&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;,
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"row"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"columns"&lt;/span&gt;:[1,4.50]&lt;span class="o"&gt;}}&lt;/span&gt;,
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"row"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"columns"&lt;/span&gt;:[2,5.04]&lt;span class="o"&gt;}}&lt;/span&gt;,
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"row"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"columns"&lt;/span&gt;:[3,5.85]&lt;span class="o"&gt;}}&lt;/span&gt;,
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"row"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"columns"&lt;/span&gt;:[4,6.12]&lt;span class="o"&gt;}}&lt;/span&gt;,
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"row"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"columns"&lt;/span&gt;:[5,11.43]&lt;span class="o"&gt;}}&lt;/span&gt;,
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"row"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"columns"&lt;/span&gt;:[1,5.00]&lt;span class="o"&gt;}}&lt;/span&gt;,
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"row"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"columns"&lt;/span&gt;:[4,6.80]&lt;span class="o"&gt;}}&lt;/span&gt;,
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"row"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"columns"&lt;/span&gt;:[5,12.70]&lt;span class="o"&gt;}}&lt;/span&gt;,
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"row"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"columns"&lt;/span&gt;:[1,5.82]&lt;span class="o"&gt;}}&lt;/span&gt;,
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"row"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"columns"&lt;/span&gt;:[4,6.92]&lt;span class="o"&gt;}}&lt;/span&gt;&lt;span class="s1"&gt;'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This same stream of events over HTTP can be integrated into your application to enable real-time updates in a UI or to update other components in your application architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Neon’s support for Postgres’ logical replication enables change data capture, and streaming database changes to Apache Kafka for real-time processing with ksqlDB to create &lt;a href="https://developer.confluent.io/courses/data-pipelines/hands-on-joining-data-streams/" rel="noopener noreferrer"&gt;enriched data streams&lt;/a&gt; and materialized views using SQL syntax. If you’re looking for a Postgres database, &lt;a href="https://neon.tech/blog/python-django-and-neons-serverless-postgres#:~:text=sign%20up%20and%20try%20Neon" rel="noopener noreferrer"&gt;sign up and try Neon&lt;/a&gt; for free. Join us in our &lt;a href="https://neon.tech/discord" rel="noopener noreferrer"&gt;Discord server&lt;/a&gt; to share your experiences, suggestions, and challenges.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>kafka</category>
      <category>cdc</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Fan-out from Postgres with Change Data Capture using Debezium and Upstash Redis</title>
      <dc:creator>Evan Shortiss</dc:creator>
      <pubDate>Wed, 14 Feb 2024 23:32:41 +0000</pubDate>
      <link>https://dev.to/neon-postgres/fan-out-from-postgres-with-change-data-capture-using-debezium-and-upstash-redis-439f</link>
      <guid>https://dev.to/neon-postgres/fan-out-from-postgres-with-change-data-capture-using-debezium-and-upstash-redis-439f</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fneon-fan-out-1-1024x576.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fneon-fan-out-1-1024x576.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Neon now has beta support for Postgres Logical Replication. This enables teams to use &lt;a href="https://en.wikipedia.org/wiki/Change_data_capture" rel="noopener noreferrer"&gt;Change Data Capture (CDC)&lt;/a&gt; to observe database changes – such as INSERT and UPDATE operations – and stream these changes to downstream systems.&lt;/p&gt;

&lt;p&gt;We previously wrote about the &lt;a href="https://neon.tech/blog/change-data-capture-with-serverless-postgres#why-cdc-matters" rel="noopener noreferrer"&gt;benefits of CDC&lt;/a&gt; and how it enables &lt;a href="https://en.wikipedia.org/wiki/Event-driven_architecture" rel="noopener noreferrer"&gt;Event-Driven Architecture (EDA)&lt;/a&gt;. An EDA facilitates the implementation of messaging patterns, such as &lt;a href="https://en.wikipedia.org/wiki/Fan-out_(software)" rel="noopener noreferrer"&gt;fan-out&lt;/a&gt;, with your Neon Postgres database at the heart of the system.&lt;/p&gt;

&lt;p&gt;Implementing a fan-out pattern enables you to create applications composed of loosely coupled components. Downstream consumers can work individually or as groups to asynchronously process database events to update other parts of your system in real-time.&lt;/p&gt;

&lt;p&gt;For example, a user sending a message in your application might necessitate sending a push notification to other users. If the message is written to the database and, in turn, a message broker, the broker can facilitate fan-out in a one-to-many fashion to downstream consumers, one of which is repsonsible for delivery to mobile devices. But how exactly do you stream changes from Postgres to a message broker, and why can’t the application layer simply handle this task? Keep reading to find out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2F71-1024x326.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2F71-1024x326.jpg" alt="Streaming changes from Postgres to a message broker for fan-out delivery."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This guide will show you how to use &lt;a href="https://debezium.io/documentation/reference/stable/operations/debezium-server.html" rel="noopener noreferrer"&gt;Debezium Server&lt;/a&gt; with Neon’s serverless Postgres, and &lt;a href="https://redis.io/docs/data-types/streams/" rel="noopener noreferrer"&gt;Redis streams&lt;/a&gt; provided by Upstash, to enable message fan-out with at-least-once delivery semantics. A repository with a sample Node.js consumer for the data produced by Debezium to Redis streams is available on &lt;a href="https://github.com/evanshortiss/neon-debezium-redis-cdc" rel="noopener noreferrer"&gt;GitHub at evanshortiss/neon-debezium-redis-cdc&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dual Write Problem
&lt;/h2&gt;

&lt;p&gt;Before we dive into the solution, let’s understand why it’s recommended to use a CDC platform like Debezium to stream changes from Postgres to a message broker.&lt;/p&gt;

&lt;p&gt;Imagine a scenario where you must insert or update a record in Postgres and notify downstream systems of the write operation in real-time. The delivery requirements for such a system are typically at-least-once, i.e., you need guaranteed delivery to the event consumers. A naive approach to this problem involves &lt;a href="https://thorben-janssen.com/dual-writes/" rel="noopener noreferrer"&gt;dual writes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A dual write occurs when you alter data in two systems without ensuring consistency. The following pseudocode provides a simple illustration of the dual-write problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;insertAndPublishMessage&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;SQL&lt;/span&gt;&lt;span class="s2"&gt;`
      INSERT INTO messages (from, to, content)
      VALUES (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)
    `&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;broker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message.insert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a perfect world, this code persists data in Postgres, and once successful, it publishes an event to a message broker such as Redis streams, Apache Kafka, or Amazon Kinesis. This broker facilitates a fan-out pattern, as shown in the following architecture diagram.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2F69-1024x392.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2F69-1024x392.jpg" alt="An architecture that could suffer from inconsistent state due to dual-writes."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the database transaction fails, the prior code will throw an error and not publish a message to the message broker. However, if the database transaction succeeds but the message broker is down, you could end up with an inconsistent state because the downstream consumers are unaware of the database change(s). Changing the order of operations doesn’t help since you might inform downstream consumers of a change that fails to be committed to the database.&lt;/p&gt;

&lt;p&gt;Maybe you’re thinking that Postgres’ nifty &lt;a href="https://www.postgresql.org/docs/16/sql-listen.html" rel="noopener noreferrer"&gt;LISTEN&lt;/a&gt; and &lt;a href="https://www.postgresql.org/docs/current/sql-notify.html" rel="noopener noreferrer"&gt;NOTIFY&lt;/a&gt; capabilities offer a solution to this problem; however, those provide an at-most-once delivery mechanism, meaning sent events will be lost if downstream listeners are briefly disconnected from Postgres.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Debezium to Avoid Dual Writes
&lt;/h2&gt;

&lt;p&gt;Debezium consumes changes from Postgres’ write-ahead log (WAL) via a &lt;a href="https://www.postgresql.org/docs/current/protocol-replication.html#PROTOCOL-REPLICATION-CREATE-REPLICATION-SLOT" rel="noopener noreferrer"&gt;logical replication slot&lt;/a&gt; and streams these changes to messaging infrastructure in real-time. By consuming the WAL, only committed changes to the database are processed by Debezium and reliably forwarded to downstream message brokers, thus avoiding the dual write problem. This is illustrated in the following architectural diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2F70-1024x392.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2F70-1024x392.jpg" alt="Architecture that uses Debezium to ensure each service only writes to one other location."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Debezium tracks its progress through the WAL by storing its current WAL offset. This means you can safely restart Debezium, and it will resume streaming change events from where it last left off. If the downstream message broker is unavailable, Debezium will retry sending messages until the broker returns online.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started with Neon and Logical Replication
&lt;/h2&gt;

&lt;p&gt;To start using Debezium with Neon’s serverless Postgres, &lt;a href="https://neon.tech/docs/get-started-with-neon/signing-up" rel="noopener noreferrer"&gt;sign up for Neon&lt;/a&gt; and create a project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fcreate_first_project-1024x576.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2Fcreate_first_project-1024x576.jpeg" alt="Neon's create project UI."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have a project, enable logical replication:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select your project in the Neon console.&lt;/li&gt;
&lt;li&gt;On the Neon Dashboard, navigate to &lt;em&gt;Settings &amp;gt; Beta&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Enable&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;Confirm that you understand the changes and click &lt;strong&gt;Enable&lt;/strong&gt; again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! A message will appear stating that logical replication is enabled for your project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2FScreenshot-2024-02-07-at-12.04.34-1024x565.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2FScreenshot-2024-02-07-at-12.04.34-1024x565.png" alt="A Neon project with Logical Replication enabled."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before moving to the next section, create a table and add some data using the &lt;strong&gt;SQL Editor&lt;/strong&gt; in the Neon console by running the following SQL statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;playing_with_neon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;playing_with_neon&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; 
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Mario'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Peach'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Bowser'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Luigi'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Yoshi'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Postgres as a Debezium Data Source
&lt;/h2&gt;

&lt;p&gt;To begin, create a workspace folder in your development environment, and within it, create another folder to store your Debezium configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; debezium-neon-redis/debezium

&lt;span class="nb"&gt;cd &lt;/span&gt;debezium-neon-redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Debezium uses a &lt;a href="https://en.wikipedia.org/wiki/.properties" rel="noopener noreferrer"&gt;properties file&lt;/a&gt; to store the necessary configuration. Create a file named &lt;code&gt;application.properties&lt;/code&gt; inside the &lt;code&gt;debezium/&lt;/code&gt; folder, and add the following configuration to define Postgres as a data source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# PostgreSQL source connector properties&lt;/span&gt;
debezium.source.database.hostname&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PGHOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
debezium.source.database.port&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PGPORT&lt;/span&gt;:5432&lt;span class="k"&gt;}&lt;/span&gt;
debezium.source.database.user&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PGUSER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
debezium.source.database.password&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
debezium.source.database.dbname&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PGDATABASE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
debezium.source.database.server.name&lt;span class="o"&gt;=&lt;/span&gt;tutorial
debezium.source.snapshot.mode&lt;span class="o"&gt;=&lt;/span&gt;initial
debezium.source.plugin.name&lt;span class="o"&gt;=&lt;/span&gt;pgoutput
debezium.source.connector.class&lt;span class="o"&gt;=&lt;/span&gt;io.debezium.connector.postgresql.PostgresConnector
debezium.source.schema.whitelist&lt;span class="o"&gt;=&lt;/span&gt;public
table.include.list&lt;span class="o"&gt;=&lt;/span&gt;playing_with_neon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration instructs Debezium to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect to Postgres using the connection details defined in the &lt;code&gt;PG&lt;/code&gt; environment variables.&lt;/li&gt;
&lt;li&gt;Accept payloads in &lt;code&gt;pgoutput&lt;/code&gt; format. Neon also &lt;a href="https://neon.tech/docs/guides/logical-replication-neon#decoder-plugins" rel="noopener noreferrer"&gt;supports wal2json&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Subscribe to changes in the &lt;code&gt;playing_with_neon&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;Perform a table snapshot to inform downstream consumers of the initial state. This is useful if you’re adding Debezium to an existing application that has tables that already contain data and you’d like to process the existing rows and not just new rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, add the following lines to the &lt;code&gt;application.properties&lt;/code&gt;. Comments are provided to explain what these configuration properties do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable human readable logs&lt;/span&gt;
quarkus.log.console.json&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JSON_FMT_LOG&lt;/span&gt;:false&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Exclude the DB schema from event payloads to keep them lean&lt;/span&gt;
debezium.format.schemas.enable&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;

&lt;span class="c"&gt;# Keep track of the WAL offsets to safely resume on restarts&lt;/span&gt;
debezium.source.offset.storage.file.filename&lt;span class="o"&gt;=&lt;/span&gt;data/offsets.dat
debezium.source.offset.flush.interval.ms&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice work! You’ve enabled logical replication for a Neon serverless Postgres database and created a Debezium Server configuration to use it as a data source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Upstash Redis as a Debezium Data Sink
&lt;/h2&gt;

&lt;p&gt;When Debezium captures database changes, it needs to stream them to a destination known as a “sink”. You’ll be using a Redis instance provided by &lt;a href="https://upstash.com/" rel="noopener noreferrer"&gt;Upstash&lt;/a&gt; as a sink; however, &lt;a href="https://debezium.io/documentation/reference/stable/operations/debezium-server.html#_sink_configuration" rel="noopener noreferrer"&gt;Debezium supports various sinks&lt;/a&gt;, meaning you can stream data to your preferred messaging infrastructure.&lt;/p&gt;

&lt;p&gt;Provision a Redis instance by visiting &lt;a href="https://console.upstash.com/" rel="noopener noreferrer"&gt;console.upstash.com&lt;/a&gt;, choosing Redis, and clicking the &lt;strong&gt;Create Database&lt;/strong&gt; button. Enter the following parameters when creating your Redis instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: &lt;code&gt;neon-debezium&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Type:  &lt;code&gt;Regional&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Region: Select the region closest to your Neon Postgres database.&lt;/li&gt;
&lt;li&gt;TLS (SSL) Enabled: &lt;code&gt;Yes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Eviction: &lt;code&gt;Yes&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The form will resemble the following screenshot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2FScreenshot-2024-02-09-at-09.57.34-1024x561.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2FScreenshot-2024-02-09-at-09.57.34-1024x561.png" alt="Creating a Redis instance on Upstash."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the following entries to the &lt;code&gt;application.properties&lt;/code&gt; to define Redis as your data sink:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;debezium.sink.type&lt;span class="o"&gt;=&lt;/span&gt;redis
database.user&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REDIS_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
debezium.sink.redis.address&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REDIS_ADDRESS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
debezium.sink.redis.password&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REDIS_PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
debezium.sink.redis.wait.retry.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;debezium.sink.redis.ssl.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration will send change events to a Redis stream data structure in your Upstash Redis instance. In typical Redis fashion, all data is stored under a key. The key name will be composed of the “debezium” prefix followed by the database schema and table name, resulting in &lt;code&gt;debezium.public.playing_with_neon&lt;/code&gt; in this particular example.&lt;/p&gt;

&lt;p&gt;Check the Redis documentation for a detailed description of all &lt;a href="https://docs.redis.com/latest/rdi/installation/debezium-server-configuration/#redis-data-integration-configuration-reference" rel="noopener noreferrer"&gt;supported configuration properties for the Redis sink&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start a Debezium Server Container
&lt;/h2&gt;

&lt;p&gt;Your &lt;code&gt;application.properties&lt;/code&gt; references environment variables. It’s time to create a &lt;code&gt;.env&lt;/code&gt; file to store those variables to pass them into the Debezium Server container. You can also run Debezium on your host machine using Java and by &lt;a href="https://repo1.maven.org/maven2/io/debezium/debezium-server-dist/" rel="noopener noreferrer"&gt;downloading a Debezium Server distribution&lt;/a&gt;, but &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; and &lt;a href="https://podman-desktop.io/" rel="noopener noreferrer"&gt;Podman&lt;/a&gt; allow you to test different versions quickly without affecting your host environment.&lt;/p&gt;

&lt;p&gt;Visit the &lt;strong&gt;Dashboard&lt;/strong&gt; for your project in the Neon Console, and select the &lt;em&gt;Parameters only&lt;/em&gt; option in the &lt;strong&gt;Connection Details&lt;/strong&gt; pane. Copy the values displayed and paste them into a &lt;code&gt;.env&lt;/code&gt; file in the &lt;code&gt;debezium-neon-redis&lt;/code&gt; folder you created earlier, removing the surrounding single quotes.&lt;/p&gt;

&lt;p&gt;You’ll also need to define the Redis environment variables expected by the &lt;code&gt;application.properties&lt;/code&gt;. These are displayed in your Redis instance’s &lt;strong&gt;Details&lt;/strong&gt; section on the Upstash Console.&lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;.env&lt;/code&gt; file should resemble the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Do not wrap the variable values in quotes. Docker doesn't like that...&lt;/span&gt;
&lt;span class="nv"&gt;PGHOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ep-adj-noun-a1b2c3.us-west-2.aws.neon.tech
&lt;span class="nv"&gt;PGDATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;neondb
&lt;span class="nv"&gt;PGUSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yourusername
&lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yourpassword

&lt;span class="nv"&gt;REDIS_ADDRESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;usw1-random-animal-12345.upstash.io:33324
&lt;span class="nv"&gt;REDIS_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default
&lt;span class="nv"&gt;REDIS_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;upstashredispassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before continuing, confirm your folder structure matches this sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;debezium-neon-redis
├── .env
├── debezium
│ └── application.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now start a Debezium Server container using this command from within the &lt;code&gt;debezium-neon-redis&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; debezium-server &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--env-file&lt;/span&gt; .env &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;/debezium:/debezium/conf &lt;span class="se"&gt;\&lt;/span&gt;
debezium/server:2.5.1.Final
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Debezium Server will start and print a series of logs. The logs include information about the initial snapshot being performed and should end with a line stating that Debezium is &lt;code&gt;Searching for WAL resume position&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing Changes in Redis and Consuming them with Node.js
&lt;/h2&gt;

&lt;p&gt;Return to the Neon &lt;strong&gt;SQL Editor&lt;/strong&gt; in your project and run the following &lt;code&gt;INSERT&lt;/code&gt; statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;playing_with_neon&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Yoshi'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Debezium Sever should log a message resembling &lt;code&gt;First LSN 'LSN{0/2159960}' received&lt;/code&gt;. This confirms that Debezium is successfully consuming the WAL!&lt;/p&gt;

&lt;p&gt;To confirm data is being sent to Redis:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Return to the &lt;a href="https://console.upstash.com/" rel="noopener noreferrer"&gt;console.upstash.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Select your Redis instance.&lt;/li&gt;
&lt;li&gt;Navigate to the &lt;em&gt;Data Browser&lt;/em&gt; tab.&lt;/li&gt;
&lt;li&gt;Set the filter to &lt;em&gt;All Types&lt;/em&gt; or &lt;em&gt;Stream&lt;/em&gt;, and search for &lt;code&gt;debezium*&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should see a key named &lt;code&gt;debezium.public.playing_with_neon&lt;/code&gt;. This stream contains the 5 initial rows you inserted in your database with matching timestamps. The new row you inserted a moment ago will also be there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2FScreenshot-2024-02-09-at-10.31.18-1024x574.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fneondatabase.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F02%2FScreenshot-2024-02-09-at-10.31.18-1024x574.png" alt="Events in the Redis stream data structure, as seen in the Upstash Data Browser."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can now use a Redis client and the &lt;a href="http://xreadgroup" rel="noopener noreferrer"&gt;XREADGROUP command&lt;/a&gt; to have consumer groups process messages in parallel as they arrive to the stream. Messages should be acknowledged using the &lt;a href="https://redis.io/commands/xack/" rel="noopener noreferrer"&gt;XACK command&lt;/a&gt;, since this enables you to restart consumers and have them resume processing where they last left off. &lt;/p&gt;

&lt;p&gt;A simple Node.js program to process messages as part of a consumer group would be similar to this, but with better error handling in a production codebase, of course. A complete example can be found in &lt;a href="https://github.com/evanshortiss/neon-debezium-redis-cdc" rel="noopener noreferrer"&gt;this repository on GitHub&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REDIS_STREAMS_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debezium.public.playing_with_neon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REDIS_GROUP_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pwn-lr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Remember to change this value for each replica of this&lt;/span&gt;
&lt;span class="c1"&gt;// program that you're running. It must be stable value&lt;/span&gt;
&lt;span class="c1"&gt;// across restarts!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REDIS_CONSUMER_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;consumer-0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lr-demo-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis-stack:6379&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// The ID of '0' here makes sure the group consumes messages from&lt;/span&gt;
  &lt;span class="c1"&gt;// the beginning of the stream. Use '$' if you want to ignore old&lt;/span&gt;
  &lt;span class="c1"&gt;// messages that were created prior to the group's creation time&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xGroupCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;REDIS_STREAMS_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;REDIS_GROUP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;readFromStream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;readFromStream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xReadGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;REDIS_GROUP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;REDIS_CONSUMER_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REDIS_STREAMS_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;BLOCK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Add message processing logic here...&lt;/span&gt;

        &lt;span class="c1"&gt;// ...then ack the message!&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xAck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;REDIS_STREAMS_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;REDIS_GROUP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Immediately queue the next xReadGroup call&lt;/span&gt;
    &lt;span class="nf"&gt;setImmediate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readFromStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Neon’s support for Postres’ Logical Replication enables development teams to leverage Event-Driven Architectures to create robust, real-time applications with Postgres at the core. Change Data Capture platforms and technologies like Debezium provide low-code open-source solutions to reliably stream changes from Postgres to messaging systems such as Redis for further processing and analysis. If you’re looking for a Postgres database, &lt;a href="https://neon.tech/blog/python-django-and-neons-serverless-postgres#:~:text=sign%20up%20and%20try%20Neon" rel="noopener noreferrer"&gt;sign up and try Neon&lt;/a&gt; for free. Join us in our &lt;a href="https://neon.tech/discord" rel="noopener noreferrer"&gt;Discord server&lt;/a&gt; to share your experiences, suggestions, and challenges.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>debezium</category>
      <category>cdc</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Using Neon’s Auto-Suspend with Long-Running Applications</title>
      <dc:creator>Evan Shortiss</dc:creator>
      <pubDate>Wed, 24 Jan 2024 23:27:01 +0000</pubDate>
      <link>https://dev.to/neon-postgres/using-neons-auto-suspend-with-long-running-applications-20pc</link>
      <guid>https://dev.to/neon-postgres/using-neons-auto-suspend-with-long-running-applications-20pc</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BQyr6rWZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/01/neon-long-running-applications-1-1024x576.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BQyr6rWZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/01/neon-long-running-applications-1-1024x576.jpg" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We’re Neon, a cloud-native serverless Postgres solution. With Neon, your Postgres databases and environments are just one click away. You can still benefit from serverless Postgres if your application isn’t serverless. &lt;a href="https://console.neon.tech" rel="noopener noreferrer"&gt;Try using Neon’s serverless Postgres&lt;/a&gt; with your long-running applications today.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We refer to Neon as serverless Postgres because it can &lt;a href="https://neon.tech/docs/guides/auto-suspend-guide" rel="noopener noreferrer"&gt;auto-suspend&lt;/a&gt; your database when it’s not actively serving queries and &lt;a href="https://neon.tech/docs/introduction/autoscaling" rel="noopener noreferrer"&gt;autoscale&lt;/a&gt; when it is. Neon’s serverless Postgres pairs perfectly with applications deployed in serverless environments such as Cloudflare Workers or Vercel, thanks to our &lt;a href="https://github.com/neondatabase/serverless/" rel="noopener noreferrer"&gt;serverless driver&lt;/a&gt; and support for &lt;a href="https://github.com/neondatabase/neon/tree/main/proxy#sql-over-http" rel="noopener noreferrer"&gt;SQL over HTTP&lt;/a&gt;. Your database should be able to scale to zero when it’s not processing queries – just like your serverless applications. This can result in savings not just for production workloads but also for development and staging environments.&lt;/p&gt;

&lt;p&gt;If you have a traditional long-running application, sometimes called “serverfull”, and you’re interested in trying Neon, you’ll be glad to know that Neon is compatible with those applications too. After all, Neon is just Postgres. These application servers, usually running MVC-type frameworks like Ruby on Rails and Django, can take advantage of Neon’s auto-suspend to reduce database costs and utilization during off-peak times, just like serverless applications. In the case of a long-running application, auto-suspend will inevitably sever any connections between your application and the database. When your application attempts to reconnect and issue new queries, Neon will restart your Postgres database to serve them.&lt;/p&gt;

&lt;p&gt;This post will illustrate configuring your Postgres driver to handle auto-suspend using &lt;a href="https://node-postgres.com/" rel="noopener noreferrer"&gt;node-postgres&lt;/a&gt;, how to improve performance using client-side pooling, and how to put it all together in an application that uses &lt;a href="https://htmx.org/" rel="noopener noreferrer"&gt;HTMX&lt;/a&gt;, &lt;a href="https://fastify.dev/" rel="noopener noreferrer"&gt;Fastify&lt;/a&gt;, and &lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;Drizzle ORM&lt;/a&gt;. Not a Node.js developer? Don’t stop reading! The concepts discussed in this post apply to other Postgres drivers and runtimes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Neon’s Auto-Suspend Feature
&lt;/h2&gt;

&lt;p&gt;How does auto-suspend work anyway? Let’s look into it so you can better configure your applications and environments to handle auto-suspend gracefully. Doing so will enable you to take advantage of cost savings and make your application more resilient to connection errors.&lt;/p&gt;

&lt;p&gt;As the name suggests, Neon’s auto-suspend feature will suspend database instances if no activity has been detected within the &lt;a href="https://neon.tech/docs/guides/auto-suspend-guide#configure-autosuspend-for-a-compute-endpoint" rel="noopener noreferrer"&gt;configured auto-suspend window&lt;/a&gt; for a given compute. Auto-suspend works even if clients are connected to the database, but only under certain circumstances. Since Neon is open-source, you can see exactly how this feature works by looking at files such as &lt;a href="https://github.com/neondatabase/neon/blob/main/compute_tools/src/monitor.rs" rel="noopener noreferrer"&gt;compute_tools/src/monitor.rs&lt;/a&gt; in the &lt;a href="https://github.com/neondatabase/neon" rel="noopener noreferrer"&gt;neondatabase/neon&lt;/a&gt; repository on GitHub.&lt;/p&gt;

&lt;p&gt;At the time of writing, auto-suspend is triggered when the following conditions are true:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No activity has been detected in a time period larger than the auto-suspend window.&lt;/li&gt;
&lt;li&gt;No &lt;a href="https://neon.tech/docs/guides/logical-replication-concepts#wal-senders" rel="noopener noreferrer"&gt;WAL senders&lt;/a&gt; are active. In other words, you’re not using Logical Replication.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.postgresql.org/docs/current/routine-vacuuming.html#AUTOVACUUM" rel="noopener noreferrer"&gt;Autovacuum&lt;/a&gt; is not currently running.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can test the impact of auto-suspend on a long-lived application that lacks error handling and reconnect logic using the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// filename: http-server.pg-client.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getVersion&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT version()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getVersion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Node.js application will work fine so long as it receives consistent traffic. The consistent traffic would prevent Neon’s auto-suspend from being triggered for the database specified by &lt;code&gt;DATABASE_URL&lt;/code&gt;. If auto-suspend were to suspend the database, this program would exit due to an unhandled &lt;a href="https://node-postgres.com/apis/client#error" rel="noopener noreferrer"&gt;client error event&lt;/a&gt;. Better handling of the connection lifecycle is necessary to make this application more resilient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing Connections with Client-Side Pooling
&lt;/h2&gt;

&lt;p&gt;The prior example’s &lt;code&gt;getVersion()&lt;/code&gt; code could be modified to open and close a database connection for each query. This would address the concerns around connection loss but could also introduce the following issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add tens or hundreds of milliseconds of latency overhead per request.&lt;/li&gt;
&lt;li&gt;Exhaust Postgres’ &lt;a href="https://neon.tech/docs/connect/connection-pooling#default-connection-limits" rel="noopener noreferrer"&gt;connection limits&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Neon’s &lt;a href="https://neon.tech/docs/connect/connection-pooling#enable-connection-pooling" rel="noopener noreferrer"&gt;pooler endpoint&lt;/a&gt; (based on &lt;a href="https://www.pgbouncer.org/" rel="noopener noreferrer"&gt;PgBouncer&lt;/a&gt;) and our serverless driver can provide a workaround for these issues and are especially important for serverless application architectures where many instances of your application will open connections to the database. However, you may want to continue using your existing database driver with Neon, or the &lt;a href="https://neon.tech/docs/connect/connection-pooling#connection-pooling-notes-and-limitations" rel="noopener noreferrer"&gt;limitations of PgBouncer&lt;/a&gt; might pose a problem for your long-running application. &lt;/p&gt;

&lt;p&gt;Using a client-side connection pool with your existing Postgres driver can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Control the number of open connections by reusing existing connections.&lt;/li&gt;
&lt;li&gt;Avoid the latency overhead of opening and closing connections for each query. &lt;/li&gt;
&lt;li&gt;Manage connection lifecycle on your behalf and gracefully handle Neon’s auto-suspend.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Switching to a client-side pool is often a trivial code change, as shown by the following code sample.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// filename: http-server.pg-pool.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;idleTimeoutMillis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Log connection loss errors, but do not terminate the program. The pool will create&lt;/span&gt;
&lt;span class="c1"&gt;// new connections that will start the Neon postgres compute to run future queries&lt;/span&gt;
&lt;span class="c1"&gt;// if the compute endpoiint is idle&lt;/span&gt;
&lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`pg:pool error &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. open connections &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// The pool will run this query once a connection is available&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT version()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connection pool requires minimal code to handle the loss of connectivity with a Neon Postgres compute that has auto-suspend enabled. When your application needs to run queries at some point in the future, the pool will establish one or more new connections with your database, causing it to start up. &lt;/p&gt;

&lt;p&gt;Moreover, reusing connections in the pool can significantly boost your application’s throughput. Performing a benchmark using &lt;a href="https://httpd.apache.org/docs/2.4/programs/ab.html" rel="noopener noreferrer"&gt;Apache Bench&lt;/a&gt; reveals the following performance metrics in favor of the codebase that uses a connection pool:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Total Time (100 requests)&lt;/th&gt;
&lt;th&gt;Average Latency&lt;/th&gt;
&lt;th&gt;P99 Response Time&lt;/th&gt;
&lt;th&gt;Req/Sec&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pooled&lt;/td&gt;
&lt;td&gt;0.27 seconds&lt;/td&gt;
&lt;td&gt;46ms&lt;/td&gt;
&lt;td&gt;370ms&lt;/td&gt;
&lt;td&gt;370&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unpooled&lt;/td&gt;
&lt;td&gt;3.786 seconds&lt;/td&gt;
&lt;td&gt;678ms&lt;/td&gt;
&lt;td&gt;764ms&lt;/td&gt;
&lt;td&gt;26.4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can test it yourself using the following command to issue 100 requests with a concurrency of 20 at the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ab &lt;span class="nt"&gt;-n&lt;/span&gt; 100 &lt;span class="nt"&gt;-c&lt;/span&gt; 20 &lt;span class="s1"&gt;'http://127.0.0.1:8080/'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: Results will depend on hardware resources, connection quality, pool size, proximity to your Neon Postgres database region, assigned Neon compute resources, and other factors.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Example with HTMX, Fastify, and Drizzle
&lt;/h2&gt;

&lt;p&gt;Putting these pieces together, let’s look at how this knowledge can be applied to an application built using HTMX, Fastify, and Drizzle ORM. The source code for the application is available in &lt;a href="https://github.com/evanshortiss/neon-and-long-running-servers" rel="noopener noreferrer"&gt;evanshortiss/neon-and-long-running-apps&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;p&gt;The application includes code that gracefully handles Neon’s auto-suspend. This ensures that it reconnects to the Neon Postgres database when new requests come in but tolerates losing connectivity to the database during periods of inactivity.&lt;/p&gt;

&lt;p&gt;The primary endpoint for this application uses the following code to fetch elements of the periodic table from the database and render them as an HTML page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drizzle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Elements&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/views/index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code defines an index (&lt;code&gt;GET /&lt;/code&gt;) HTTP endpoint on the Fastify server that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Invokes a &lt;a href="https://github.com/evanshortiss/neon-and-long-running-apps/blob/main/src/plugins/drizzzle.ts" rel="noopener noreferrer"&gt;custom Fastify plugin&lt;/a&gt; (&lt;code&gt;req.drizzle()&lt;/code&gt;) to obtain a Drizzle instance.&lt;/li&gt;
&lt;li&gt;Uses the Drizzle instance to query the database for all elements.&lt;/li&gt;
&lt;li&gt;Renders and returns an HTML page containing the elements to a client.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://htmx.org/attributes/hx-delete/" rel="noopener noreferrer"&gt;HTMX’s &lt;code&gt;hx-delete&lt;/code&gt;&lt;/a&gt; enables user interaction to delete database elements and dynamically update the page’s HTML in response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Czs9XlV1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/01/Screenshot-2024-01-19-at-12.35.21-1024x624.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Czs9XlV1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2024/01/Screenshot-2024-01-19-at-12.35.21-1024x624.png" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When benchmarked using the same Apache Bench command as before, hosting a single instance of this Node.js application on a lightweight dual-core VM produces the following results in favor of connection pooling.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Total Time (100 requests)&lt;/th&gt;
&lt;th&gt;Average Latency&lt;/th&gt;
&lt;th&gt;P99 Response Time&lt;/th&gt;
&lt;th&gt;Req/Sec&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pooled&lt;/td&gt;
&lt;td&gt;0.304 seconds&lt;/td&gt;
&lt;td&gt;52ms&lt;/td&gt;
&lt;td&gt;60ms&lt;/td&gt;
&lt;td&gt;329&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unpooled&lt;/td&gt;
&lt;td&gt;3.76 seconds&lt;/td&gt;
&lt;td&gt;672ms&lt;/td&gt;
&lt;td&gt;755ms&lt;/td&gt;
&lt;td&gt;26.6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note: The Node.js application was hosted in SFO. The Neon Postgres database was hosted in the US-West region and had 1 shared vCPU and 1 GB of RAM.&lt;/em&gt; &lt;em&gt;The&lt;/em&gt; &lt;em&gt;&lt;code&gt;pg.Pool&lt;/code&gt; size was set to 20&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Neon’s serverless Postgres can be used with both traditional long-running applications and serverless architectures. If you have long-running applications implementing robust connection handling, you can use Neon’s auto-suspend feature to reduce your database bill, especially for non-production environments. &lt;a href="https://console.neon.tech/signup" rel="noopener noreferrer"&gt;Sign up to try Neon&lt;/a&gt; with your existing applications, and join us on &lt;a href="https://neon.tech/discord" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; to share your experiences, suggestions, and challenges with us.&lt;/p&gt;

</description>
      <category>database</category>
      <category>serverless</category>
      <category>node</category>
    </item>
    <item>
      <title>Auth setup with Neon, Keycloak and Koyeb</title>
      <dc:creator>Evan Shortiss</dc:creator>
      <pubDate>Fri, 15 Dec 2023 19:15:41 +0000</pubDate>
      <link>https://dev.to/neon-postgres/auth-setup-with-neon-keycloak-and-koyeb-58pm</link>
      <guid>https://dev.to/neon-postgres/auth-setup-with-neon-keycloak-and-koyeb-58pm</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yiCaSySV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2023/12/neon-keycloak1-1-1024x576.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yiCaSySV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2023/12/neon-keycloak1-1-1024x576.jpg" alt="Keycloak with Neon and Koyeb" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.keycloak.org/" rel="noopener noreferrer"&gt;Keycloak&lt;/a&gt; is an open-source identity and access management solution that centralizes authentication and authorization management. It provides features such as single sign-on (SSO), two-factor authentication, social login, and user federation with LDAP or Active Directory user federation. Various identity protocols such as Open ID Connect, SAML 2.0, and OAuth 2.0 are supported, which makes integrating Keycloak with new and existing applications easy.&lt;/p&gt;

&lt;p&gt;Whether you’re a novice getting started with Keycloak or a veteran user, you’ll need a database to support your Keycloak deployment in production. Since Keycloak has first-class support for Postgres, this guide will outline a minimal setup to use Keycloak with Neon’s Postgres. All configurations and code in this guide can be found in the &lt;a href="https://github.com/neondatabase/keycloak-example" rel="noopener noreferrer"&gt;neondatabase/keycloak-example&lt;/a&gt; repository on GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Neon project and Keycloak Database
&lt;/h2&gt;

&lt;p&gt;Each Neon project includes a single Postgres instance that contains a &lt;code&gt;neondb&lt;/code&gt; database by default. Still, it’s best to create a dedicated &lt;code&gt;keycloak&lt;/code&gt; database to store Keycloak’s data. &lt;/p&gt;

&lt;p&gt;To create a project, visit the &lt;a href="https://console.neon.tech/app/projects" rel="noopener noreferrer"&gt;Neon console&lt;/a&gt;. If it’s your first time visiting the console you should be prompted to create a new project as shown. If you already have a project you’d like to use, select it from the &lt;a href="https://console.neon.tech/app/projects" rel="noopener noreferrer"&gt;projects list&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EpGys9lC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2023/12/create-project-1024x595.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EpGys9lC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2023/12/create-project-1024x595.png" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create the Keycloak database by visiting the &lt;a href="https://neon.tech/docs/get-started-with-neon/query-with-neon-sql-editor" rel="noopener noreferrer"&gt;&lt;strong&gt;SQL Editor&lt;/strong&gt;&lt;/a&gt; section of your project, and running the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;keycloak&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can verify that the &lt;code&gt;keycloak&lt;/code&gt; database was created by viewing the &lt;strong&gt;Databases&lt;/strong&gt; section of the Neon console or running the &lt;code&gt;SELECT datname FROM pg_database;&lt;/code&gt; query in the &lt;strong&gt;SQL Editor&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure a Keycloak Database User
&lt;/h2&gt;

&lt;p&gt;Instead of accessing your new &lt;code&gt;keycloak&lt;/code&gt; database using the default user with the &lt;a href="https://neon.tech/docs/manage/roles#the-neonsuperuser-role" rel="noopener noreferrer"&gt;&lt;code&gt;neon_superuser&lt;/code&gt;&lt;/a&gt; role, creating a new user named &lt;code&gt;keycloak_admin&lt;/code&gt; is best. This new user will have administrative privileges limited to the &lt;code&gt;keycloak&lt;/code&gt; database.&lt;/p&gt;

&lt;p&gt;Use the Neon SQL Editor to input the commands to create the user and assign permissions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit the &lt;strong&gt;SQL Editor&lt;/strong&gt; in your project.&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;keycloak&lt;/code&gt; database using the dropdown menu in the top-right.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Issue the following command to create a new user named &lt;code&gt;keycloak_admin&lt;/code&gt;, making sure to replace the password with a value that has 60 bits of entropy, &lt;a href="https://neon.tech/docs/manage/roles#manage-roles-with-sql" rel="noopener noreferrer"&gt;per our documentation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Create a strong password per: https://neon.tech/docs/manage/roles */&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;keycloak_admin&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'r3plac3_th1s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* Assign permissions to the `keycloak_admin` user */&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;keycloak_admin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy Keycloak on Koyeb
&lt;/h2&gt;

&lt;p&gt;Keycloak can be deployed on a machine with a JDK installed, or from a container image using Docker or Podman. Setting up infrastructure manually can be quite a chore, so instead I’ll show you how to deploy Keycloak on &lt;a href="https://koyeb.com" rel="noopener noreferrer"&gt;Koyeb&lt;/a&gt; with valid SSL certificates. If they support deploying container images, you can replace Koyeb with your preferred hosting provider.&lt;/p&gt;

&lt;p&gt;To get started, sign up for an account at &lt;a href="https://koyeb.com" rel="noopener noreferrer"&gt;koyeb.com&lt;/a&gt;. Next, click &lt;a href="https://app.koyeb.com/apps/deploy?type=docker&amp;amp;image=quay.io%2Fkeycloak%2Fkeycloak%3A23.0.1&amp;amp;name=keycloak&amp;amp;env%5BKC_DB_USERNAME%5D=&amp;amp;env%5BKC_DB_PASSWORD%5D=&amp;amp;env%5BKC_DB_URL%5D=jdbc%3Apostgresql%3A%2F%2FNEON_HOSTNAME%2Fkeycloak%3Fsslmode%3Drequire&amp;amp;env%5BKC_HOSTNAME%5D=&amp;amp;env%5BKC_HTTP_ENABLED%5D=true&amp;amp;env%5BKC_PROXY%5D=edge&amp;amp;env%5BKC_DB%5D=postgres&amp;amp;ports=8080%3Bhttp%3B%2F&amp;amp;tag=23.0.1&amp;amp;docker.image.tag=23.0.1&amp;amp;image-tag=23.0.1&amp;amp;command=start&amp;amp;env%5BKEYCLOAK_ADMIN%5D=admin&amp;amp;env%5BKEYCLOAK_ADMIN_PASSWORD%5D=" rel="noopener noreferrer"&gt;this link&lt;/a&gt; to start deploying Keycloak on Koyeb from a template with some preconfigured values.&lt;/p&gt;

&lt;p&gt;Set the following parameters for the deployment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Instance type: Eco is adequate for testing.&lt;/li&gt;
&lt;li&gt;Region: Choose the region closest to your Neon Postges database’s region.&lt;/li&gt;
&lt;li&gt;Configure the following environment variables:

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;KC_DB_URL&lt;/code&gt;: Replace the &lt;code&gt;NEON_HOSTNAME&lt;/code&gt; with your Neon database’s hostname from the &lt;strong&gt;Connection Details&lt;/strong&gt; on Neon’s console, e.g &lt;code&gt;ep-broken-hill-12345.us-east-2.aws.neon.tech&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KC_DB_USERNAME&lt;/code&gt;: &lt;code&gt;keycloak_admin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KC_DB_PASSWORD&lt;/code&gt;: The password you set for &lt;code&gt;keycloak_admin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KC_HOSTNAME&lt;/code&gt;: This must match the hostname you define when deploying the service on Koyeb. The hostname is listed at the bottom of the deploy page and is a combination of the App name and your organization name, e.g &lt;code&gt;keycloak-your-org.koyeb.app&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KEYCLOAK_ADMIN_PASSWORD&lt;/code&gt;: This will be the password you use to log in as the &lt;code&gt;admin&lt;/code&gt; user and perform administrative actions in Keycloak&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;Click the &lt;strong&gt;Deploy&lt;/strong&gt; button and you’ll be directed to a screen where you can monitor the status of your deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2p249S6C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2023/12/koyeb-deploy-1024x578.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2p249S6C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2023/12/koyeb-deploy-1024x578.png" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scroll down to the bottom of this page and wait for the logs to print a line stating that Keycloak has started. It will resemble the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keycloak 23.0.1 on JVM (powered by Quarkus 3.2.9.Final) started in 33.009s. Listening on: http://0.0.0.0:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a Keycloak Realm and Client
&lt;/h2&gt;

&lt;p&gt;Keycloak uses the concept of &lt;a href="https://www.keycloak.org/docs/latest/server_admin/#core-concepts-and-terms" rel="noopener noreferrer"&gt;realms&lt;/a&gt; to create a self-contained space where you manage users, roles, and authentication settings for one or more client applications. The &lt;code&gt;master&lt;/code&gt; realm is created by default and is used to manage other realms. A realm contains one or more clients representing applications, e.g., a Next.js application deployed on a specific domain.&lt;/p&gt;

&lt;p&gt;Return to your &lt;a href="https://app.koyeb.com/" rel="noopener noreferrer"&gt;Koyeb Apps list&lt;/a&gt; and click on the hostname underneath your Keycloak service to visit the Keycloak welcome page. Click the link to access the &lt;strong&gt;Administration Console&lt;/strong&gt; and log in using the username and password you set using &lt;code&gt;KEYCLOAK_ADMIN&lt;/code&gt; and &lt;code&gt;KEYCLOAK_ADMIN_PASSWORD&lt;/code&gt; when first deploying Keycloak.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;master&lt;/code&gt; realm will be selected by default. Use the dropdown in the top left corner to click the &lt;strong&gt;Create realm&lt;/strong&gt; button. Upload the example &lt;a href="https://github.com/neondatabase/keycloak-example/blob/main/realms/quickstart.json" rel="noopener noreferrer"&gt;quickstart.json&lt;/a&gt; realm file from the &lt;a href="https://github.com/neondatabase/keycloak-example" rel="noopener noreferrer"&gt;neondatabase/keycloak-example&lt;/a&gt; repository to create a realm. &lt;/p&gt;

&lt;p&gt;Once the realm is created you can see a sample user named &lt;code&gt;alice&lt;/code&gt;, and a client named &lt;code&gt;nextjs-application&lt;/code&gt; within the &lt;strong&gt;Users&lt;/strong&gt; and &lt;strong&gt;Clients&lt;/strong&gt; sections.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UH8hY3RW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2023/12/kc-users-1024x573.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UH8hY3RW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2023/12/kc-users-1024x573.png" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By querying some tables, you can confirm that Keycloak is using your Neon Postgres database. For example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit the &lt;strong&gt;SQL Editor&lt;/strong&gt; in your project on the &lt;a href="https://console.neon.tech/" rel="noopener noreferrer"&gt;Neon console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Run the query &lt;code&gt;SELECT * from user_entity;&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The user named &lt;code&gt;alice&lt;/code&gt; defined in the sample realm JSON should be listed in the output. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pAtBep_S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2023/12/user-alice-in-output-1024x553.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pAtBep_S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2023/12/user-alice-in-output-1024x553.png" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting a Next.js Application to Keycloak
&lt;/h2&gt;

&lt;p&gt;The repository associated with this blog post at &lt;a href="https://github.com/neondatabase/keycloak-example" rel="noopener noreferrer"&gt;neondatabase/keycloak-example&lt;/a&gt; includes a Next.js application that uses &lt;a href="https://next-auth.js.org/" rel="noopener noreferrer"&gt;NextAuth.js&lt;/a&gt; to integrate with Keycloak to perform user authentication.&lt;/p&gt;

&lt;p&gt;Clone the repository, install the dependencies, and create a &lt;code&gt;.env.local&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:neondatabase/keycloak-example.git

&lt;span class="nb"&gt;cd &lt;/span&gt;keycloak-example/next-auth-example

&lt;span class="nb"&gt;cp&lt;/span&gt; .env.local.example .env.local

npm i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit the &lt;code&gt;.env.local&lt;/code&gt; file so that the hostname of the &lt;code&gt;KEYCLOAK_ISSUER&lt;/code&gt; points to your Keycloak instance, and generate an &lt;code&gt;AUTH_SECRET&lt;/code&gt; using the instructions in the file. Start the application using the &lt;code&gt;npm run dev&lt;/code&gt; command, and visit &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; to interact with it.&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;Sign In&lt;/strong&gt; button in the top right corner to initiate the OAuth-based login flow using Keycloak. You can sign in using the username &lt;code&gt;alice&lt;/code&gt; and password &lt;code&gt;alice&lt;/code&gt;. Once logged in you can visit the different pages in the application and view the user’s session data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EDAWT1_6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2023/12/next-auth-logged-in-1-1024x573.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EDAWT1_6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://neondatabase.wpengine.com/wp-content/uploads/2023/12/next-auth-logged-in-1-1024x573.png" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion and Next Steps
&lt;/h2&gt;

&lt;p&gt;Now you know how to use Neon as your Postgres storage provider for Keycloak. Consider crafting an &lt;a href="https://www.keycloak.org/server/containers#_creating_a_customized_and_optimized_container_image" rel="noopener noreferrer"&gt;optimized Keycloak container&lt;/a&gt; for production deployment to decrease startup time, and refer to the &lt;a href="https://www.keycloak.org/server/db" rel="noopener noreferrer"&gt;Keycloak database configuration guide&lt;/a&gt; for information on connection pooling and other database configuration properties. You can also experiment with Neon’s branching and &lt;a href="https://neon.tech/docs/guides/branching-pitr" rel="noopener noreferrer"&gt;point-in-time restore&lt;/a&gt; capability to discover how to restore Keycloak data to an earlier version if needed.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>keycloak</category>
      <category>authentication</category>
    </item>
  </channel>
</rss>
