Use Pusher to Implement Real-Time Notifications
Pusher makes it easy to add pub-sub functionality to your web apps, allowing you to implement chat, notifications, and more.
Sam Magura
Most rich web applications have real-time features, in which the server notifies the frontend that an event or update has occurred. Pushing from the server is necessary to implement many common use cases, such as chat, in-app notifications, and live updates to data displays like charts.
Real-time features have traditionally been implemented using a web socket connection between the server and the web browser. This approach generally works well, but it adds complexity when you have a pool of servers, since each web socket connection must be owned by a specific server. In this case, relying on an external service to implement real-time features can simplify your architecture.
This post will demonstrate how to use one such service, Pusher . With Pusher, your server publishes an event to Pusher, and your web or mobile client subscribes to receive events via the Pusher client SDK. The event is routed through Pusher's infrastructure, so you don't have to worry about scaling your own resources.
Secure your secrets conveniently
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.
What We're Building
To demonstrate how to use Pusher, we'll implement real-time in-app notifications with Pusher in a Next.js app. Our app will display a form that allows you to send a notification. The backend will then broadcast this notification to all browsers that have the app open.
The Pusher server SDK requires a secret API key, which we will manage securely via the Zero secrets manager.
🔗 The full code for this example is available in the zerosecrets/examples GitHub repository.
Signing Up for Pusher
Click here to sign up for a Pusher account. Create a new application using the Channels service. Then choose the technologies and cluster for your application. Select JavaScript for the frontend, and Node.js for the backend.
This will display a getting started guide that includes your Pusher appId
, key
, secret
, and cluster
. The key
and cluster
are not secret, so we will provide them to our app via a .env
file. Go ahead and create that .env
file on your computer — we will copy it into the project in the next section.
NEXT_PUBLIC_PUSHER_KEY="YOUR_PUSHER_KEY"
NEXT_PUBLIC_PUSHER_CLUSTER="YOUR_PUSHER_CLUSTER"
The environment variables are prefixed with NEXT_PUBLIC
to indicate that they are not secret and should be made available to client-side code.
The secret
value should not be committed to the .env
file, since secrets must not be committed to git or made available to client-side code. Instead, we'll provide the secret
to the app securely via the Zero secrets manager. Log in to Zero, and create a new project. Then add a new secret that includes appId
and secret
:
Creating the Next.js App
Now, let's go ahead and create a new Next.js app to house our code:
npx create-next-app@latest pusher-app
Make sure to select the App Router at the prompt. The .env
file you created in the previous section can now be copied into the root directory of the Next.js app.
cd
into the project's directory and install the necessary dependencies:
npm install pusher pusher-js @types/pusher-js @zerosecrets/zero
pusher
is the server-side SDK, pusher-js
is the JavaScript client-side SDK, and @zerosecrets/zero
is the Zero TypeScript SDK which will be used to retrieve the Pusher secret at runtime.
Creating Notifications
Our app will simply allow the user to manually send a notification that will be broadcast to all clients. To do this, we'll write an HTML form that accepts the notification text as input. When the form is submitted, it will POST to an /api/notify
API method which will publish an event to Pusher from the server.
export default function Home() {
const [inputValue, setInputValue] = useState('')
async function submit() {
await fetch('/api/notify', {
method: 'POST',
body: JSON.stringify(inputValue),
})
}
return (
<main className="p-24">
<h2 className="text-2xl mb-4 font-bold">Send a Notification</h2>
<form
noValidate
onSubmit={(e) => {
e.preventDefault()
submit()
}}
className="mb-10"
>
<input
className="input input-bordered mr-2"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button className="btn btn-primary">Send notification</button>
</form>
<NotificationDisplay />
</main>
)
}
<NotificationDisplay />
is a component we'll implement later to display the notifications that have been received.
Initializing the Pusher Server SDK
Before implementing the notify
API method, we should initialize the Pusher SDK on the server. This will require the key
and cluster
from the .env
file, and the appId
and secret
from Zero.
The code for this will go in a new file called util/pusher.ts
. In this file, we implement the standard pattern for fetching secrets from Zero and passing them to a 3rd party SDK:
import Pusher from 'pusher'
import {zero} from '@zerosecrets/zero'
let pusher: Pusher | undefined
export async function getPusher() {
if (pusher) {
return pusher
}
if (!process.env.ZERO_TOKEN) {
throw new Error('Did you forget to set the ZERO_TOKEN environment variable?')
}
const secrets = await zero({
token: process.env.ZERO_TOKEN,
pick: ['pusher'],
}).fetch()
if (!secrets.pusher) {
throw new Error('Did not receive Pusher secret.')
}
pusher = new Pusher({
appId: secrets.pusher.app_id,
key: process.env.NEXT_PUBLIC_PUSHER_KEY!,
secret: secrets.pusher.secret,
cluster: process.env.NEXT_PUBLIC_PUSHER_CLUSTER!,
useTLS: true,
})
return pusher
}
Now, our API method can call getPusher
to get a fully-initialized Pusher client.
Implementing the API Method
In Next.js with the App Router, API methods are implemented via Route Handlers . To create the /api/notify
method, place the following code in the file app/api/notify/route.ts
:
import {getPusher} from '@/util/getPusher'
export async function POST(request: Request) {
const content = await request.json()
const pusher = await getPusher()
pusher.trigger('notifications', 'notification', content)
return Response.json(null)
}
The code here is very simple. The request content is expected to be a JSON string that contains the notification content. Then, we call getPusher
to get an instance of the Pusher server SDK. Next is the key line of code, where we trigger
an event in Pusher. Here, notifications
is the name of the channel, and notification
is the name of the event.
Subscribing to Notifications
On the frontend, the Pusher client SDK can be initialized like this, using the variables from .env
:
'use client'
import Pusher from 'pusher-js'
const pusher = new Pusher(process.env.NEXT_PUBLIC_PUSHER_KEY!, {
cluster: process.env.NEXT_PUBLIC_PUSHER_CLUSTER!,
})
Then, we can subscribe to the notifications
channel in a useEffect
. When a notification
event is received, we append its data
(the notification content) to an array in React state. We can then display this array as a list, to show the notifications that have been received.
export function NotificationDisplay() {
const [notifications, setNotifications] = useState<string[]>([])
useEffect(() => {
const channel = pusher.subscribe('notifications')
channel.bind('notification', (data: string) => {
setNotifications((n) => [...n, data])
})
return () => {
channel.unbind_all()
pusher.unsubscribe('notifications')
}
}, [])
return (
<div>
<h2 className="text-2xl mb-4 font-bold">Received Notifications</h2>
<ul>
{notifications.map((n) => (
<li key={n}>{n}</li>
))}
</ul>
</div>
)
}
To run the app, provide your Zero token as an environment variable:
ZERO_TOKEN='YOUR_ZERO_TOKEN' npm run dev
Here's what my app looks like:
To test that it's really working, open multiple browser tabs to the same page, and verify that each notification appears in every tab, not just the one that created the notification.
Next Steps
This post showed the basic usage of Pusher in a Next.js application, using Zero to securely manage the Pusher secret key. To build this type of functionality into a real app, you'll need to get more creative with the channel names provided to Pusher. In my demo project, each notification is sent to all clients, but in a real application, each user should receive completely personalized notifications. To implement this, you would likely include the user's ID in the name of the channel. Then, the backend needs to know which users should receive a given notification so it can publish to the appropriate channels.
Other articles
Integrating Auth0 with Next.js for Authentication
Quickly set up authentication in a Next.js app with Auth0 so you don't have to code it yourself.
Integrating Netlify with Zero for Secure Secrets Management
Use the Zero Netlify integration to easily sync your secrets to any Netlify site.
Secure your secrets
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.