Skip to content
Go back

How to listen for database updates with Prisma

Posted on:August 17, 2024 at 05:00 PM

Prisma released a new feature that allows you to listen for database changes in a neat way that allows you to separate your code better.

It lets you to move logic like notification handling away from your business logic. I used it to send myself a Discord notification, but it could work for emails, or any other code that needs to run whenever your database is updated.

This is basically all you need:

const stream = await prisma.feedback.stream();

for await (const event of stream) {
  console.log("New event:", event);
  // do something here...
}

Not bad! I expected it to be much more complicated when I first learned about this.

Whenever an update in the database happens, you get an event like this:

New event: {
  action: 'create',
  created: {
    id: 'clz71qaoiqmnbso12xm4s',
    content: 'This is my feedback message!!!',
    evaluation: 'positive',
    createdAt: 2024-07-30T04:26:38.74Z,
    updatedAt: 2024-07-30T04:26:38.74Z,
    ownerId: 'clz82jndfgiuso23me3o'
  },
  id: '0kjansd9887sdfhjkasd98',
  modelName: 'Feedback'
}

It’s a feature that you can enable by adding the Prisma Pulse extension. This takes a few lines of code when initializing your Prisma client by extending it using withPulse():

import { remember } from "@epic-web/remember";
import { Pool, neonConfig } from "@neondatabase/serverless";
import { PrismaNeon } from "@prisma/adapter-neon";
import { PrismaClient } from "@prisma/client";
import { withPulse } from "@prisma/extension-pulse";

import ws from "ws";
import { sendDiscordMessage } from "./discord";

neonConfig.webSocketConstructor = ws;
const connectionString = `${process.env.DATABASE_URL}`;

const pool = new Pool({
  connectionString,
});
const adapter = new PrismaNeon(pool);

export const prisma = remember("prisma", () => {
  const prisma = new PrismaClient({
    adapter,
  }).$extends(
    withPulse({
      apiKey: process.env["PULSE_API_KEY"] as string,
    })
  );

  client.$connect();
  return client;
});

Note: If you’re using Neon too, you should enable Logical Replication for your database. Prisma has a guide for this here: https://www.prisma.io/docs/pulse/database-setup/neon.

I used this to listen for updates on my database whenever someone submits a feedback form:

feedback form

The Prisma schema for that looks as follows:

model Feedback {
  id         String @id @default(cuid())
  content    String
  evaluation String

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  owner   User   @relation(fields: [ownerId], references: [id], onDelete: Cascade, onUpdate: Cascade)
  ownerId String

  @@index([ownerId])
  @@index([ownerId, updatedAt])
}

The great thing about this is that it’s fully type safe! All thanks to your Prisma schema. Here is an example of listening to the updates and sending a Discord notification when it happens:

async function prismaFeedbackStream() {
  // wait for new database events
  const stream = await prisma.feedback.stream();

  const emojiMap = {
    positive: "👍",
    negative: "👎",
  };

  // for each new event
  for await (const event of stream) {
    // omly check for new table entries
    if ("created" in event) {
      const { content, evaluation } = event.created;
      const emoji = emojiMap[evaluation] || "💬";
      await sendDiscordMessage(`**${emoji} new feedback**\n\n${content}`);
    }
  }
}

prismaFeedbackStream();

The demo application is getting a lot of positive feedback as you can see 😁

feedback form

And this is how to send a Discord notification:

export async function sendDiscordMessage(message: string) {
  const payload = {
    content: message,
  };

  try {
    const response = await fetch(webhookUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
    });
  } catch (error) {
    console.error("Error sending Discord message:", error);
  }
}

Now this Discord example is pretty simple. That is intentional. In the real world it could be much more complex using the same tools.

For example, you could also have one application update the database, and have another application listen for the changes using Prisma Pulse.

If you’re writing Lambda functions, you could also keep your functions smaller by removing any sort of notification logic from the function.

At the very least it will let you decouple some code for sending notifications like Discord messages or emails. But maybe you have much more complex use-cases in mind already.

How is this possible?

Prisma connects to your Postgres database so they can check for updates. When Prisma sees something has changed, it will send an update to any listening clients connected via Prisma Pulse.

To connect Prisma to your database, you need to create a Prisma account.

Getting started with Prisma Pulse

To get started with Prisma Pulse, you can sign up for an account here https://www.prisma.io/?via=eddy (affiliate link)

If you want to use any paid offerings from Prisma, my link should give you a discount.

After you’ve created a workspace, you can create a project environment and select “Manage Pulse”

Prisma workspace environment screen

Then you’ll have to give Prisma your database connection string so that they can connect and listen for changes on your database:

Prisma Pulse setup

The dashboard should show you a notification if it was setup correctly. (If not, please check if your database supports logical replication)

Example form in Remix (using the Epic Stack)

I open sourced my example on GitHub here: https://github.com/EddyVinck/remix-prisma-feedback-form

As a base, the repository uses Kent C. Dodds’ Epic Stack which comes with Prisma pre-configured except for the Pulse part.

The following files were modified to create this feedback form and the listener:

Conclusion

I’m pretty happy with how it turned out. I can see the use-cases, especially if you have multiple separate applications connecting to the same database. The code to use Pulse itself is really straightforward and easy to understand, in my opinion. The hardest part for me was finding a suitable hosted database for the demo.

I really like how you can decouple pieces of code where you just want to insert something in a database and some other code that is supposed to send an email or something like I did with Discord.

If you’re already using Prisma, this functionality to listen for database updates might open up some possibilities for you. And if you’re not, it’s worth trying it out and seeing for yourself!

Please use my affiliate link if you’re going to try it! https://www.prisma.io/?via=eddy