forked from novuhq/novu
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(framework): Add NestJS
serve
handler (novuhq#6654)
- Loading branch information
Showing
29 changed files
with
1,927 additions
and
944 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -651,6 +651,7 @@ | |
"unarchived", | ||
"Unarchived", | ||
"Unfetch", | ||
"unplugin", | ||
"Unpromoted", | ||
"unpublish", | ||
"unsub", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export * from './nest/nest.constants'; | ||
export * from './nest/nest.controller'; | ||
export * from './nest/nest.interface'; | ||
export * from './nest/nest.module'; | ||
export * from './nest/nest.register-api-path'; | ||
export * from './nest/nest.client'; | ||
export * from './nest/nest.handler'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { Injectable, Inject } from '@nestjs/common'; | ||
import type { Request, Response } from 'express'; | ||
|
||
import { NovuRequestHandler, type ServeHandlerOptions } from '../../handler'; | ||
import type { SupportedFrameworkName } from '../../types'; | ||
import { NOVU_OPTIONS } from './nest.constants'; | ||
import { NovuHandler } from './nest.handler'; | ||
|
||
export const frameworkName: SupportedFrameworkName = 'nest'; | ||
|
||
@Injectable() | ||
export class NovuClient { | ||
public novuRequestHandler: NovuRequestHandler; | ||
|
||
constructor( | ||
@Inject(NOVU_OPTIONS) private options: ServeHandlerOptions, | ||
@Inject(NovuHandler) private novuHandler: NovuHandler | ||
) { | ||
this.novuRequestHandler = new NovuRequestHandler({ | ||
frameworkName, | ||
...this.options, | ||
handler: this.novuHandler.handler, | ||
}); | ||
} | ||
|
||
public async handleRequest(req: Request, res: Response) { | ||
await this.novuRequestHandler.createHandler()(req, res); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export const REGISTER_API_PATH = 'REGISTER_API_PATH'; | ||
export { NOVU_OPTIONS } from './nest.module-definition'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { Controller, Req, Res, Inject, Get, Post, Options } from '@nestjs/common'; | ||
import { Request, Response } from 'express'; | ||
import { NovuClient } from './nest.client'; | ||
|
||
@Controller() | ||
export class NovuController { | ||
constructor(@Inject(NovuClient) private novuService: NovuClient) {} | ||
|
||
@Get() | ||
async handleGet(@Req() req: Request, @Res() res: Response) { | ||
await this.novuService.handleRequest(req, res); | ||
} | ||
|
||
@Post() | ||
async handlePost(@Req() req: Request, @Res() res: Response) { | ||
await this.novuService.handleRequest(req, res); | ||
} | ||
|
||
@Options() | ||
async handleOptions(@Req() req: Request, @Res() res: Response) { | ||
await this.novuService.handleRequest(req, res); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { type VercelRequest, type VercelResponse } from '@vercel/node'; | ||
import { Injectable } from '@nestjs/common'; | ||
import type { Request, Response } from 'express'; | ||
|
||
import { type INovuRequestHandlerOptions } from '../../handler'; | ||
import type { Either } from '../../types'; | ||
|
||
@Injectable() | ||
export class NovuHandler { | ||
public handler( | ||
incomingRequest: Either<VercelRequest, Request>, | ||
response: Either<Response, VercelResponse> | ||
): ReturnType<INovuRequestHandlerOptions['handler']> { | ||
const extractHeader = (key: string): string | null | undefined => { | ||
const header = incomingRequest.headers[key.toLowerCase()]; | ||
|
||
return Array.isArray(header) ? header[0] : header; | ||
}; | ||
|
||
return { | ||
body: () => incomingRequest.body, | ||
headers: extractHeader, | ||
method: () => incomingRequest.method || 'GET', | ||
queryString: (key) => { | ||
const qs = incomingRequest.query[key]; | ||
|
||
return Array.isArray(qs) ? qs[0] : qs; | ||
}, | ||
url: () => { | ||
// `req.hostname` can filter out port numbers; beware! | ||
const hostname = incomingRequest.headers.host || ''; | ||
|
||
const protocol = hostname?.includes('://') ? '' : `${incomingRequest.protocol || 'https'}://`; | ||
|
||
const url = new URL(incomingRequest.originalUrl || incomingRequest.url || '', `${protocol}${hostname || ''}`); | ||
|
||
return url; | ||
}, | ||
transformResponse: ({ body, headers, status }) => { | ||
Object.entries(headers).forEach(([headerName, headerValue]) => { | ||
response.setHeader(headerName, headerValue as string); | ||
}); | ||
|
||
return response.status(status).send(body); | ||
}, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import type { ServeHandlerOptions } from '../../handler'; | ||
|
||
export type NovuModuleOptions = ServeHandlerOptions & { | ||
apiPath: string; | ||
}; |
17 changes: 17 additions & 0 deletions
17
packages/framework/src/servers/nest/nest.module-definition.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { ConfigurableModuleBuilder } from '@nestjs/common'; | ||
import { NovuModuleOptions } from './nest.interface'; | ||
|
||
// use ConfigurableModuleBuilder, because building dynamic modules from scratch is painful | ||
export const { | ||
ConfigurableModuleClass: NovuBaseModule, | ||
MODULE_OPTIONS_TOKEN: NOVU_OPTIONS, | ||
OPTIONS_TYPE, | ||
ASYNC_OPTIONS_TYPE, | ||
} = new ConfigurableModuleBuilder<NovuModuleOptions>() | ||
.setClassMethodName('register') | ||
.setFactoryMethodName('createNovuModuleOptions') | ||
.setExtras((definition: NovuModuleOptions) => ({ | ||
...definition, | ||
isGlobal: true, | ||
})) | ||
.build(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { Module, Provider } from '@nestjs/common'; | ||
import { NovuClient } from './nest.client'; | ||
import { NovuController } from './nest.controller'; | ||
import { registerApiPath } from './nest.register-api-path'; | ||
import { ASYNC_OPTIONS_TYPE, NovuBaseModule, OPTIONS_TYPE } from './nest.module-definition'; | ||
import { NovuHandler } from './nest.handler'; | ||
|
||
/** | ||
* In NestJS, serve and register any declared workflows with Novu, making | ||
* them available to be triggered by events. | ||
* | ||
* @example | ||
* ```ts | ||
* import { NovuModule } from "@novu/framework/nest"; | ||
* import { myWorkflow } from "./src/novu/workflows"; // Your workflows | ||
* | ||
* @Module({ | ||
* imports: [ | ||
* // Expose the middleware on our recommended path at `/api/novu`. | ||
* NovuModule.register({ | ||
* apiPath: '/api/novu', | ||
* workflows: [myWorkflow] | ||
* }) | ||
* ] | ||
* }) | ||
* export class AppModule {} | ||
* | ||
* const app = await NestFactory.create(AppModule); | ||
* | ||
* // Important: ensure you add JSON middleware to process incoming JSON POST payloads. | ||
* app.use(express.json()); | ||
* ``` | ||
*/ | ||
@Module({}) | ||
export class NovuModule extends NovuBaseModule { | ||
/** | ||
* Register the Novu module | ||
* | ||
* @param options - The options to register the Novu module | ||
* @param customProviders - Custom providers to register. These will be merged with the default providers. | ||
* @returns The Novu module | ||
*/ | ||
static register(options: typeof OPTIONS_TYPE, customProviders?: Provider[]) { | ||
const superModule = super.register(options); | ||
|
||
superModule.controllers = [NovuController]; | ||
superModule.providers?.push(registerApiPath, NovuClient, NovuHandler, ...(customProviders || [])); | ||
superModule.exports = [NovuClient, NovuHandler]; | ||
|
||
return superModule; | ||
} | ||
|
||
/** | ||
* Register the Novu module asynchronously | ||
* | ||
* @param options - The options to register the Novu module | ||
* @param customProviders - Custom providers to register. These will be merged with the default providers. | ||
* @returns The Novu module | ||
*/ | ||
static registerAsync(options: typeof ASYNC_OPTIONS_TYPE, customProviders?: Provider[]) { | ||
const superModule = super.registerAsync(options); | ||
|
||
superModule.controllers = [NovuController]; | ||
superModule.providers?.push(registerApiPath, NovuClient, NovuHandler, ...(customProviders || [])); | ||
superModule.exports = [NovuClient, NovuHandler]; | ||
|
||
return superModule; | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
packages/framework/src/servers/nest/nest.register-api-path.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { FactoryProvider } from '@nestjs/common'; | ||
import { PATH_METADATA } from '@nestjs/common/constants'; | ||
import { NovuController } from './nest.controller'; | ||
import { REGISTER_API_PATH, NOVU_OPTIONS } from './nest.constants'; | ||
import { OPTIONS_TYPE } from './nest.module-definition'; | ||
|
||
/** | ||
* Workaround to dynamically set the path for the controller. | ||
* | ||
* A custom provider is necessary to ensure that the controller path is set during | ||
* application initialization, because NestJS does not support declaration of | ||
* paths after the application has been initialized. | ||
* | ||
* @see https://github.com/nestjs/nest/issues/1438#issuecomment-863446608 | ||
*/ | ||
export const registerApiPath: FactoryProvider = { | ||
provide: REGISTER_API_PATH, | ||
useFactory: (options: typeof OPTIONS_TYPE) => { | ||
if (!options.apiPath) { | ||
throw new Error('`apiPath` must be provided to set the controller path'); | ||
} | ||
|
||
Reflect.defineMetadata(PATH_METADATA, options.apiPath, NovuController); | ||
}, | ||
inject: [NOVU_OPTIONS], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
export type SupportedFrameworkName = 'next' | 'express' | 'nuxt' | 'h3' | 'sveltekit' | 'remix' | 'lambda'; | ||
export type SupportedFrameworkName = 'next' | 'express' | 'nuxt' | 'h3' | 'sveltekit' | 'remix' | 'lambda' | 'nest'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
NOVU_SECRET_KEY= | ||
NOVU_API_URL=https://api.novu.co |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
dist | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Novu NestJS Playground | ||
|
||
This project is a simple example of how to use Novu Framework with NestJS. | ||
|
||
## Quick start | ||
|
||
This quickstart assumes you are running this application from the Novu monorepo and have already installed the dependencies. | ||
|
||
Copy the `.env.example` file to `.env` and set the correct environment variables. | ||
|
||
```bash | ||
cp .env.example .env | ||
``` | ||
|
||
Then, run the application: | ||
|
||
```bash | ||
pnpm start | ||
``` | ||
|
||
Finally, start Novu Studio and follow the CLI instructions to start creating your NestJS notification workflows: | ||
|
||
```bash | ||
npx novu@latest dev | ||
``` | ||
|
||
## Testing | ||
|
||
```bash | ||
pnpm test | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"$schema": "https://json.schemastore.org/nest-cli", | ||
"collection": "@nestjs/schematics", | ||
"sourceRoot": "src", | ||
"compilerOptions": { | ||
"typeCheck": true, | ||
"deleteOutDir": true, | ||
"builder": { | ||
"type": "swc", | ||
"options": { | ||
"stripLeadingPaths": true | ||
} | ||
}, | ||
"assets": [ | ||
{ | ||
"include": ".env", | ||
"outDir": "dist" | ||
}, | ||
{ | ||
"include": ".env.development", | ||
"outDir": "dist" | ||
}, | ||
{ | ||
"include": ".env.test", | ||
"outDir": "dist" | ||
}, | ||
{ | ||
"include": ".env.production", | ||
"outDir": "dist" | ||
} | ||
] | ||
} | ||
} |
Oops, something went wrong.