- Notifications
You must be signed in to change notification settings - Fork26
Strongly 💪🏼 Typed Eventemitter Module For Nestjs Framework 🦁
License
nestjsx/nest-emitter
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Strongly 💪🏼 Typed Eventemitter Module ForNestjs Framework 🦁
Ever wondered if there is a way to have a strongly typed way to use event emitter names ?
Ever wondered why your event emitter is not working as intended and then realized that therewas a typo on your events name? if so, then this ones for you 😄 .
By Declaring events using a simple interface mapping event names to their payloads to get stricter versions ofemit
,on
, and other common EventEmitter APIs.
and not only that, it will work with any kind ofEventEmitter
that implementsNodeJS.Events
.
npm install nest-emitter
or
yarn add nest-emitter
As Normal ImportNestEmitterModule
into your root module(akaAppModule
)
TheNestEmitterModule#forRoot(emitter: NodeJS.Events)
takes any event emitter that implementsNodeJS.Events
.
For simplicity I will use nodejs built-in eventemitter, but of course you can use whatever you need.
// app.module.tsimport{Module}from'@nestjs/common';import{AppController}from'./app.controller';import{AppService}from'./app.service';import{NestEmitterModule}from'nest-emitter';import{EventEmitter}from'events';@Module({imports:[NestEmitterModule.forRoot(newEventEmitter())],controllers:[AppController],providers:[AppService],})exportclassAppModule{}
Now it's time to define our events, let's add two eventsone callednotification
and it's payload will be a string.and another one isnewRequest
and it's payload will be function that has one arg of typeRequest
.
// app.events.tsinterfaceAppEvents{notification:string;// as a side note: that is equivalent to// newRequest: Express.Request;newRequest:(req:Express.Request)=>void;}
After that let's bring up our secret weapon; theStrictEventEmitter
!
// app.events.tsimport{EventEmitter}from'events';import{StrictEventEmitter}from'nest-emitter';interfaceAppEvents{notification:string;newRequest:(req:Express.Request)=>void;}exporttypeMyEventEmitter=StrictEventEmitter<EventEmitter,AppEvents>;
good good, now let's use it.
👍 TIP: Keep all of your events in a separate file like
{prefix}.events.ts
.
I will use it to send a notification when we receive a request
// app.controller.tsimport{Get,Controller,Req}from'@nestjs/common';import{AppService}from'./app.service';import{InjectEventEmitter}from'nest-emitter';import{MyEventEmitter}from'app.events';@Controller()exportclassAppController{constructor(privatereadonlyappService:AppService, @InjectEventEmitter()privatereadonlyemitter:MyEventEmitter,){} @Get()root(@Req()req:Express.Request):string{this.emitter.emit('notification','new req');// this will throw an error at compile-time// as `notification` event only accepts `string`// this.emitter.emit('notification', 1234);this.emitter.emit('newRequest',req);returnthis.appService.root();}}
Did you notice@InjectEventEmitter()
? you guessed it, it's a helper decorator to get the instance of the underlying eventemitter.
now on the other side
import{Injectable,OnModuleInit}from'@nestjs/common';import{InjectEventEmitter}from'nest-emitter';import{MyEventEmitter}from'app.events';@Injectable()exportclassAppServiceimplementsOnModuleInit{constructor(@InjectEventEmitter()privatereadonlyemitter:MyEventEmitter){}onModuleInit(){this.emitter.on('notification',asyncmsg=>awaitthis.onNotification(msg));this.emitter.on('newRequest',asyncreq=>awaitthis.onRequest(req));}root():string{return'Hello World!';}privateasynconNotification(msg:string){console.log(`OnNotification:${msg}`);}privateasynconRequest(req:Express.Request){console.log(`OnRequest from:${req['ip']}`);}}
And that's it! Easy? now let's dive in.
Event records are interfaces or object types that map event names to the event's payload types. In the following example, three events are declared:
interfaceAppEvents{req:(request:Express.Request,response:Express.Response)=>void;done:void;conn:Connection;}
Each event shows one of three ways to type the event payloads:
- Function type: Parameters are the event payload. The return type is ignored.
void
: A shortcut for an event with no payload, i.e.() => void
- Anything else: A shortcut for an event with one payload, for example
(p: number) => void
can be written as justnumber
.
The default export. A generic type that takes three type parameters:
- TEmitterType: Your EventEmitter type (e.g. node's EventEmitter or socket.io socket)
- TEventRecord: A type mapping event names to event payloads
- TEmitRecord: Optionally, a similar type mapping things you can emit.
The third parameter is handy when typing web sockets where client and server can listen to and emit different events. For example, if you are using socket.io:
// create types representing the server side and client// side socketsexporttypeServerSocket=StrictEventEmitter<SocketIO.Socket,EventsFromServer,EventsFromClient>;exporttypeClientSocket=StrictEventEmitter<SocketIOClient.Socket,EventsFromClient,EventsFromServer>;// elsewhere on serverletserverSocket:ServerSocket=newSocketIO.Socket();serverSocket.on(/* only events that are sent from the client are allowed */, ...)serverSocket.emit(/* only events that are emitted from the server are allowed */, ...)// elsewhere on clientletclientSocket:ClientSocket=newSocketIOClient.Socket();clientSocket.on(/* only events that are sent from the server are allowed */, ...)clientSocket.emit(/* only events that are emitted from the client are allowed */, ...)
For more information aboutStrictEventEmitter
see@bterlson 's library
SeeCHANGELOG for more information.
You are welcome to contribute to this project, just open a PR.
See also the list ofcontributors who participated in this project.
This project is licensed under the MIT License - see theLICENSE.md file for details.