In this guide, we will add a Novu Bridge Endpoint to a NestJS application and send our first test workflow.

1

Run the Local Studio

The Local Studio is where you will build your notification workflows and craft the controls that will be exposed for your non-technical peers to maintain after your workflow is pushed to your Development or Production environments.

npx novu@latest dev

The dev command is your go-to command whenever you want to build and preview your changes before syncing to cloud. By default, it will start a secure tunnel that our durable cloud workflow engine will be able to communicate with, and the Local Studio web service listening on http://localhost:2022

2

Install Packages

npm install @novu/framework zod zod-to-json-schema

This will install the following packages

  • @novu/framework SDK Package
  • Zod (Recommended) - For end-to-end type safety for your Payload and Step Controls
3

Add the NovuModule to your application

The NovuModule is a NestJS module that registers the Novu Endpoint in your application.

The following example does not support NestJS dependency injection. If you need to @Injectable dependencies in your workflow definition, see Advanced Usage.

4

Add a Novu Secret Key Environment Variable

Add NOVU_SECRET_KEY environment variable to your .env

NOVU_SECRET_KEY=<NOVU_SECRET_KEY>
5

Create your workflow definition

Add a novu folder in your src folder as such src/novu/workflows.ts that will contain your workflow definitions.

import { workflow } from '@novu/framework';
import { z } from 'zod';

export const testWorkflow = workflow('test-workflow', async ({ step, payload }) => {
    await step.email('send-email', async (controls) => {
        return {
            subject: controls.subject,
            body: 'This is your first Novu Email ' + payload.userName,
        };
    },
    {
        controlSchema: z.object({
            subject: z.string().default('A Successful Test on Novu from {{userName}}'),
        }),
    });
}, {
    payloadSchema: z.object({
        userName: z.string().default('John Doe'),
    }),
});
6

Start your application

Start your NestJS application with the Novu Endpoint configured.

If your NestJS application is running on other than 4000 port, restart the npx novu dev command with the port:

npx novu@latest dev --port <YOUR_NESTJS_APPLICATION_PORT>
7

Test your workflow

After your application is up and running, visit the Local Studio interface that was started on http://localhost:2022 by running the npx novu dev command on the first step.

The onboarding guide will guide you to send the newly created sample workflow to your e-mail address.

8

Deploy your application

Once you have finished refining your first workflow, it’s time to sync your local changes to Novu Cloud. Novu recommends deploying your workflows similarly to how you will deploy the features that generate those notifications using your CI/CD pipeline or our CLI command.

Read more about syncing your changes to the cloud.

Next Steps

Advanced Usage (Dependency Injection)

If you need to inject dependencies into your workflow definition, you can use the registerAsync method.

Add the NovuModule using the registerAsync method to your AppModule.

src/app.module.ts
import { Module } from '@nestjs/common';
import { NovuModule } from '@novu/framework/nest';
import { NotificationService } from './notification.service';
import { UserService } from './user.service';

@Module({
  imports: [
    NovuModule.registerAsync({
      imports: [AppModule],
      useFactory: (notificationService: NotificationService) => ({
        apiPath: '/api/novu',
        workflows: [notificationService.welcomeWorkflow()],
      }),
      inject: [NotificationService],
    }),
  ],
  providers: [NotificationService, UserService],
  exports: [NotificationService],
})
export class AppModule {}

For example, you might need to inject a service that fetches the user’s name from a database. This is useful when you need to fetch data in realtime during the execution of your workflow.

An example UserService is available below with hardcoded values, but in a real-world application you might use a database or an external API to fetch the user’s name.

src/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  getUser(id: string) {
    return {
      name: 'John Doe',
      email: `john.doe.${id}@example.com`,
    };
  }
}

Finally, configure your NotificationService to use the injected UserService.

src/notification.service.ts
import { Injectable } from '@nestjs/common';
import { workflow } from '@novu/framework';
import { z } from 'zod';
import { UserService } from './user.service';

@Injectable()
export class NotificationService {
  constructor(private readonly userService: UserService) {}

  public welcomeWorkflow() {
    return workflow(
      'welcome-email',
      async ({ step, payload }) => {
        await step.email('send-email', async () => {
          const user = this.userService.getUser(payload.userId);

          return {
            subject: `Hello, ${user.name}`,
            body: `We are glad you are here!`,
          };
        });
      },
      {
        payloadSchema: z.object({
          userId: z.string(),
        }),
      }
    );
  }
}

A full example NestJS application demonstrating dependency injection is available here.