😎

Nest.js におけるモジュール開発 - Sendgrid Module

2022/03/21に公開

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 ユースのためには、以下のような実装も検討してください。

  • メール送信処理エラー時の再実行処理を実装
  • メールの配信ログを記録する処理を実装

参考

https://github.com/sendgrid/sendgrid-nodejs/tree/main/packages/mail

https://docs.nestjs.com/fundamentals/dynamic-modules

https://docs.nestjs.com/techniques/events

Discussion