Nest.js におけるモジュール開発 - Sendgrid Module
Nest.js におけるモジュール開発として、Sendgrid を利用したメール送信を担保するモジュールを作成します。
コンセプト
Nest.js 向けの Sendgrid モジュールを開発します。
コンセプトは以下のようなものになります。
- モジュールを利用する側のコードは、メール送信処理を意識する事なくメール送信処理を処理できる。
- メール送信処理にどこからでも自由にフックできるように、イベントの機構を利用する。
Sendgrid モジュールの作成
関連モジュールとして、以下をインストールします。
$ npm i @sendgrid/mail
$ npm i @nestjs/event-emitter
nest g コマンドで、モジュールを作成します。
$ nest g module sendgrid
モジュール内のファイル構成は以下のようになります。
- sendgrid/sendgrid.emitter.ts
- sendgrid/sendgrid.service.ts
- sendgrid/sendgrid.module.ts
またメール個別のテンプレート領域として、 mails フォルダを作成します。
- sendgrid/mails/hello.mail.ts
それぞれのファイルを作成してから、順にファイルの中身を見ていきましょう。
SendgridEmitter
SendgridModule では、SendgridEmitter のみを外部に公開しています。
register で SENDGRID APIKEY を受け取るために dynamic module の実装を行っています。
import { Module } from '@nestjs/common';
import {SendgridService} from "./sendgrid.service";
import {SendgridEmitter} from "./sendgrid.emitter";
@Module({
})
export class SendgridModule {
static register(apikey:string){
return {
module: SendgridModule,
providers: [
{
provide: 'SENDGRID_APIKEY',
useValue: apikey,
},
SendgridService,
SendgridEmitter
],
exports: [SendgridEmitter]
}
}
}
HelloMail
メールの本文を定義する HelloMail クラスでは、以下の 2種類を実装する必要があります。
- sendgrid.MailDataRequired 型を返却する message 関数
- イベント名として利用される static プロパティ type
import * as sendgrid from "@sendgrid/mail"
export class HelloMail{
static type: string = "mail.to"
constructor(
public to: string,
public from: string,
){}
public message():sendgrid.MailDataRequired{
return {
to: this.to,
from: this.from,
subject: "hello sample mail",
text: "and easy to do anywhere, even with Node.js",
html: "<strong>and easy to do anywhere, even with Node.js</strong>",
}
}
}
SendgridService
メールの送信処理を取り扱うのが SendgridService です。
このファイルはモジュールで exports されていないので、モジュールの利用側はこのクラスを利用することができません。
sendHelloMail などメールごとに 送信処理を記述していきます。
送信処理はイベント経由で行われるため、OnEvent デコレータでイベントリスナーを定義しています。
import {Inject} from "@nestjs/common";
import * as sendgrid from "@sendgrid/mail"
import {HelloMail} from "./mails/hello.mail";
import {OnEvent} from "@nestjs/event-emitter";
import {Injectable} from "@nestjs/common";
@Injectable()
export class SendgridService{
constructor(@Inject('SENDGRID_APIKEY') private apiKey) {
sendgrid.setApiKey(apiKey)
}
sendgrid(){
return sendgrid
}
/**
* @see https://github.com/sendgrid/sendgrid-nodejs/tree/main/packages/mail#quick-start-hello-email
* @param msg
*/
private send(msg: sendgrid.MailDataRequired){
return sendgrid.send(msg)
}
@OnEvent(HelloMail.type)
sendHelloMail(msg: HelloMail){
return this.send(msg.message())
}
}
SendgridEmitter
モジュール内で唯一外部に exports されるのが、SendgridEmitter です。
各メールメッセージを発行できるようにするために、 sendHelloMail のようなイベント発行関数を定義しています。
import {HelloMail} from "./mails/hello.mail";
import {EventEmitter2} from "@nestjs/event-emitter";
import {Injectable} from "@nestjs/common";
@Injectable()
export class SendgridEmitter{
constructor(
private event: EventEmitter2
) {
}
sendHelloMail(to,from){
return this.event.emit(
HelloMail.type,
new HelloMail(to,from)
)
}
}
モジュールを利用する
モジュールを利用する側では、以下の 3 つのモジュールを import します。
- ConfigModule.forRoot()
- EventEmitterModule.forRoot()
- SendgridModule.register(apikey)
import {Module} from '@nestjs/common';
import {SendgridModule} from "../sendgrid/sendgrid.module";
import {ConfigModule} from "@nestjs/config";
import {EventEmitterModule} from '@nestjs/event-emitter';
@Module({
imports: [
ConfigModule.forRoot(),
EventEmitterModule.forRoot(),
SendgridModule.register(process.env.SENDGRID_APIKEY),
],
controllers: [ /* ... */ ],
providers: [ /* ... */ ],
})
export class AppModule {
}
利用する側のサービスでは以下のようにして、exports されている SendgridEmitter を呼び出して処理をコールできます。
import {OnEvent} from "@nestjs/event-emitter"
import {SendgridEmitter} from "../../sendgrid/sendgrid.emitter";
import {Injectable} from "@nestjs/common";
@Injectable()
export class HogeService implements CommandRunner {
constructor(
private sendgrid: SendgridEmitter,
) {
}
async run(): Promise<void> {
this.sendgrid.sendHelloMail(
'xxxxx', // to email address
'yyyyy', // from email address
)
}
}
送信処理へのフックは OnEvent で簡単に実装できます。
HelloMail は exports されていないものの、 クラスの Static コールは export することなく呼び出すことができます。
import {OnEvent} from "@nestjs/event-emitter"
import {HelloMail} from "../../sendgrid/mails/hello.mail";
import {SendgridEmitter} from "../../sendgrid/sendgrid.emitter";
import {Injectable} from "@nestjs/common";
@Injectable()
export class HogeService implements CommandRunner {
constructor(
private sendgrid: SendgridEmitter,
) {
}
async run() {
this.sendgrid.sendHelloMail(
'xxxxx', // to email address
'yyyyy', // from email address
)
}
@OnEvent(HelloMail.type)
hogeSomeMethodName(msg: HelloMail){
console.log("mail send!")
}
}
And More
production ユースのためには、以下のような実装も検討してください。
- メール送信処理エラー時の再実行処理を実装
- メールの配信ログを記録する処理を実装
参考
Discussion