Creating a configurable Webhook module for a NestJS application
In this article, I will describe the creation of two NestJS modules with different configuration methods: a utility module and a business module with its own database.
There is a lot of text and code here, who is too lazy to read, they can just look at the changes in the project code: https://github.com/nestjs-mod/nestjs-mod-fullstack/compare/460257364bb4ce8e23fe761fbc9ca7462bc89b61..ec8de9d574a6dbcef3c3339e876ce156a3974aae
1. We come up with and paint a feature
Before implementing a certain functionality, you first need to write out everything that it should be able to do and describe entities with components that we know about this functionality.
Description
When developing a highly isolated NestJS
module, we integrate with other modules either through the module configuration, or through a broker or queue.
In my practice, the main part of integrations is the publication of events for which we are already writing handlers in the integration layer of two functional modules.
And what if you need to provide access to these events to external sites, then you will have to write additional logic for registering such sites and access verification logic.
This webhook module provides access to application and module events and has CRUD
operations for managing webhooks.
Module Characteristics
- The name is WebhookModule;
- Scalability - the module does not have the ability to scale, it can only work in a monolith;
- Is it possible to call
forFeature
- no, since events and handlers are thrown through theforRoot
options; - Does it have its own database - yes, the module comes with migrations and a
Prisma
scheme for generating a client to work with the database; - Multitenancy - yes, since the site can work according to the B2B scheme and each new business is a new externalTenantId;
- Softdelet - no, the softdelet will be connected separately after the completion of the entire project and only where it is really needed, in the future a module will appear to enable and disable database auditing and it will be possible to look at the history of changes by record ids;
- Other owners of records in the table except externalTenantId - no, the record is common to the entire externalTenantId.
Tables
WebhookUser
- table with module usersid:uuid
- identifier;externalTenantId:uuid
- company identifier;externalUserId:uuid
is the user ID of the authorization server;UserRole:WebhookRole
- user role;createdAt:Date
- date of creation;updatedAt:Date
is the date of the change.
Webhook
- table with webhooksid:uuid
is the identifier;eventName:string(512)
is the unique name of the event;endpoint:string(512)
is the remote site to which thePOST
request will be sent;enabled:boolean
- is the webhook active;headers?:JSON
- headers that will be transmitted when sending to a remote site;requestTimeout?:number
- do I need to wait for a response from a remote site and the maximum number of milliseconds to wait (by default 5 seconds);externalTenantId:uuid
- company ID;createdBy:uuid
- who created the record;UpdatedBy:uuid
- who updated the record;createdAt:Date
- date of creation;updatedAt:Date
is the date of the change.
WebhookLog
- table with the history of webhook callsid:uuid
- identifier;request:JSON
- request to the remote site;ResponseStatus:string(20)
- response status of the remote site;response?:JSON
- response of the remote site;webhookStatus:WebhookStatus
- status;webhookId:uuid
- webhook ID;externalTenantId:uuid
- company ID;createdAt:Date
- date of creation;updatedAt:Date
is the date of the change.
Dictionaries
WebhookRole
- dictionary of roles- `Admin' - can read/create/change/delete any entities of any externalTenantId;
- `User' - can read/create/change/delete any entities of your externalTenantId only.
WebhookStatus
- dictionary of query statusesPending
- in the queue;Process
- in processing;Success
- successful call;Error
- the call returned an error;Timeout
- the call did not have time to work out.
Who can work with the module
- Admin -
REST
-a request that has the externalUserId=ID_USER property in theRequest
, which has the Admin role, has full access to allCRUD
operations (WebhookController
- at the url/api/webhook
); - User -
REST
-a request that has the externalUserId=ID_USER property in theRequest
, which has the User role, has full access to allCRUD
operations, but only within its externalTenantId.