Run Novu on a Local Machine

Bayo ABayo A
7 min read

Any good product these days must have some way to send notifications to users across different channels. As an engineer, building and managing the infrastructure required can be a time-consuming endeavour, especially if we have complex scenarios and multiple notification channels. In this article, we will learn how to deploy Novu, an open-source notification system, to solve this problem.

What Is Novu?

Novu is an open-source notification system that is used to send notifications over multiple channels, such as Chat, Email, SMS, In-App, and Push.

One great feature of Novu is that it allows non-technical people to create and manage Notification workflows. This means the engineering team is not bogged down with always making the smallest changes when required.

Another great feature of Novu is the <Inbox /> component for web apps that enables real-time notifications. With a few lines of code, we get an Inbox that is real-time and reactive.

The easiest way to get started with using Novu is to sign up for a plan and start integrating right away.

To have more control over the usage and data, let's deploy and configure our own version of Novu.

Deploying Using Docker

This post serves to complement the official Novu documentation, including some tips and tricks to make deployment easier on a local machine.

This article uses Windows OS for our deployment, but the commands are the same for Linux, with some slight modifications.

First, we need to make sure Docker and docker-compose are installed.

Next, let's pull the code into a folder:

> git clone --depth 1 https://github.com/novuhq/novu

Let's open the docker/community folder and create a copy of the default environment variables:

> cd novu/docker/community

> copy .env.example .env
# For Linux, use cp instead of copy

This will create a copy of the .env.example and copy it into .env file.

We need to change some configurations in our .env file to make our deployment more secure. The following settings should be changed:
JWT_SECRET
STORE_ENCRYPTION_KEY must be 32 characters long.
NOVU_SECRET_KEY
MONGO_INITDB_ROOT_USERNAME
MONGO_INITDB_ROOT_PASSWORD

We can use the Guid for the secret keys. Let's use PowerShell to generate Guid:

PS> New-Guid

This command will generate a 32-character Guid for us to use. We can generate as much as we need for our configuration.
For the STORE_ENCRYPTION_KEY, we must remove the dashes (-) from the generated Guid.

Once we have our configuration set, let's start running the app in Docker detached mode:

> docker compose up -d

If, while running docker compose, we get an error that indicates that a port is in use, we can change to a different port in our .env file.

For example, if we get an error that port 3000 is in use, we must change all references to port 3000 in the .env file. We can change it to 3001.

We can then view the app on http://localhost:4200, create an account, sign in, and complete the onboarding.

Following the on-screen instructions will lead to creating an account on Novu, which is great. In our case, we want to set up all our services locally.

If, after onboarding, the app does not redirect to the workflows, we use http://localhost:4200/workflows/create to navigate to the page to create new workflows.

Setting up the Bridge Application and Local Studio

After onboarding, we need to set up our Bridge Application and Local Studio.

The Bridge Application is the application where workflows are written. Consider workflows as definitions of how notifications are processed.

The Local Studio provides a way to define detailed workflows programmatically or using the Visual Editor. This makes it easy to define, test, and refine workflows before moving them to production.

Setting up the Bridge Application

To initialize our Bridge Application, we first need to get an API key from our application. We can get our API Key from our application dashboard or via the URL http://localhost:4200/api-keys.

Let's create the Bridge Application:

> npx novu@latest init --secret-key=<secret key> --api-url=<api-url>

Here, the <secret key> is the API key we get from our dashboard. The <api-url> is the deployed API URL, including the port number as defined by API_ROOT_URL in our .env file. In this case, http://localhost:3000.

We then install the dependencies and run the application:

> npm install

# Run the bridge application
> npm run dev

Let's verify that our bridge is working using the URL http://localhost:4000/api/novu. We should get a JSON response that should indicate that the server is running:

{
  "status": "ok",
  "sdkVersion": "2.6.6",
  "frameworkVersion": "2024-06-26",
  "discovered": {
    "workflows": 1,
    "steps": 2
  }
}

Using the Bridge Application URL as Bridge URL

We also need to set up to use the Bridge URL as a tunnel instead of the auto-generated one.

If using Linux, we can just run the following command:

> npx novu@latest dev -d <application_url> -p <bridge_application_port>

In this case, <application_url> is http://localhost:4200 and <bridge_application_port> is 4000.

But since we are using Windows, we need to make changes to our docker-compose.yml file for our Novu application.

Let's stop our Docker instance:

> docker compose down

Then we add the following configuration to each service in our docker-compose.yml file:

extra_hosts:
    - "host.docker.internal:host-gateway"

We must add the configuration to the api, web, worker, and ws services in the docker-compose.yml file:

#code omitted for brevity
web:
    image: 'ghcr.io/novuhq/novu/web:2.1.1'
    depends_on:
      - api
      - worker
    container_name: web
    restart: unless-stopped
    logging:
      driver: 'json-file'
      options:
        max-size: '50m'
        max-file: '5'
    environment:
      REACT_APP_API_URL: ${API_ROOT_URL}
      REACT_APP_ENVIRONMENT: ${NODE_ENV}
      REACT_APP_WIDGET_EMBED_PATH: ${WIDGET_EMBED_PATH}
      REACT_APP_IS_SELF_HOSTED: 'true'
      REACT_APP_WS_URL: ${REACT_APP_WS_URL}
    ports:
      - 4200:4200
    command: ['/bin/sh', '-c', 'pnpm run envsetup:docker && pnpm run start:static:build']
    extra_hosts:
      - "host.docker.internal:host-gateway"
#code omitted for brevity

As a last step, let's add our Novu API URL to the .env.local of our bridge application:

NOVU_API_URL=<api_url>

In this case, our <api_url> is http://localhost:3000. This will help prevent issues with the local studio authenticating against the API URL.

The .env.local also contains values for secret key, application identifier, and subscriber ID. We can change these values if we do a key rotation or want to test with a different subscriber.

Finally, let's run the bridge application again:

> npx novu@latest dev -d <dashboard_url> -p <bridge_port> -t <tunnel_url>

Here, the <dashboard url> is http://localhost:4200, the <bridge_application_port> is 4000 and the <tunnel url> is http://host.docker.internal:4000.

The Local Studio will open on http://localhost:2022. It will also authenticate against the main app running on http://localhost:4200.

Using Local Studio to Create Workflows

It is important to note that to start creating workflows in the Local Studio, we have to do so programmatically in code.

First, let's create a new workflow folder named event-notification in the workflows folder in our Bridge Application folder. The folder is located in app/novu/workflows/event-notification.

Next, let's create a workflow named eventNotification in the workflow.ts file:

import { workflow } from "@novu/framework";
import { payloadSchema } from "../welcome-onboarding-email/schemas";

export const eventNotification = workflow(
  "event-notification",
  async ({ step, payload }) => {
    await step.inApp("In-App Step", async () => {
      return {
        subject: payload.inAppSubject,
        body: payload.inAppBody,
        avatar: payload.inAppAvatar,
      };
    });
  },
  {
    payloadSchema,
  }
);

Then we go to /app/api/novu and add our new workflow to routes.ts file:

import { serve } from "@novu/framework/next";
import { welcomeOnboardingEmail } from "../../novu/workflows";
import { eventNotification } from "../..//novu/workflows/event-notification/workflow";

export const { GET, POST, OPTIONS } = serve({
  workflows: [welcomeOnboardingEmail, eventNotification],
});

We can then view the new workflow on the workflows page of the Local Studio.

Syncing Changes From Local Studio to Novu Application

Once we make changes to our workflow on the Local Studio, we need to sync it to the main app running on https://localhost:4200:

> npx novu@latest sync --bridge-url <bridge_url> --secret-key <secret_key> --api-url <api_url>

Here, the <bridge_url> is http://host.docker.internal:4000/api/novu, and the <api_url> is http://localhost:3000. The <secret_key> is the key we get from the dashboard of our Novu application.

Promoting a Workflow From Development to Production

After developing a workflow using the Local Studio and syncing it to the Novu application, the workflow will show up on the development environment on our Novu dashboard.

To make the workflow available on the production environment, we must promote it.

To do this, we use the URL http://localhost:4200/changes to see all the workflow changes and promote the workflows to production.

Tip: The <Inbox /> component is currently undergoing a lot of updates. If it's throwing a 400 error, use the npm version 3.1.0.

Conclusion

In the article, we learned how to deploy and configure Novu, an open-source notifications system using Docker. We also learned how to use the Local Studio to create workflows, sync the workflows to the Novu app, and promote workflows from development to production.

Resources

  1. https://docs.novu.co/community/self-hosting-novu/deploy-with-docker

  2. https://github.com/novuhq/novu/issues/6653

  3. https://github.com/novuhq/novu/issues/6710

List of URLs:

  1. Dashboard URL: http://localhost:4200

  2. Bridge Application URL: http://localhost:4000/

  3. Bridge API URL: http://localhost:4000/api/novu

  4. Bridge Tunnel URL: http://host.docker.internal:4000/api/novu

  5. Local Studio URL: http://localhost:2022/studio

  6. API URL: http://localhost:3000/v1

  7. Websocket URL: http://localhost:3002

0
Subscribe to my newsletter

Read articles from Bayo A directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Bayo A
Bayo A

Software engineer with a passion for learning and trying out new tech.