前回の続きです。立ち上げたエコーサーバーのコードの要点をざっと見ていきます。
ソースコードはこちらです。
https://github.com/bathtimefish/line-botserver-example-nestjs/tree/v1
main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as fs from 'fs';
import { config } from 'dotenv';
function getOptions() {
return {
httpsOptions: {
key: fs.readFileSync(process.env.SSL_KEY_PATH),
cert: fs.readFileSync(process.env.SSL_CERT_PATH),
},
};
}
async function bootstrap() {
let options = {};
if (process.env.SSL_KEY_PATH && process.env.SSL_CERT_PATH) {
options = getOptions();
}
const app = await NestFactory.create(AppModule, options);
await app.listen(3000);
}
config();
bootstrap();
ここではデフォルトで生成されたコードにhttpsサーバー化の設定を追加しています。LINE PlatformからのWebhookを受けるためにはhttpsサーバーである必要があるからです。
NestFactory.create(AppModule, options);
でサーバーのインスタンスを作成し、app.listen(3000);
でポート3000でBotサーバーを起動しています。AppModule
はBotサーバーを構成するモジュールで後述します。第二引数のoptions
にはgetOptions()
で取得したLet's Encryptの証明書ファイルのパス設定が含まれています。NestJSではhttpsOptionsに証明書のパスを設定することでWebサーバーをhttpsサーバーとして作成することができます。
app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MessagingModule } from './messaging/messaging.module';
@Module({
imports: [MessagingModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
@Module()
デコレーターで注釈されるNestJSのModuleこのModuleによってサーバーアプリケーションの構造が簡潔に定義できるようになっています。ここではimports
にMessagingAPIを扱うModuleをインポートしています。imports
は他のModuleをimportして本Moduleに統合することができるので複数のModuleを構造的に開発することができます。
controllers
にはこのModuleで動作させるControllerのリストを設定します。Controllerは@Controller()
で注釈されるリクエスト/レスポンスを制御する機能を定義します。providers
にはController等で共通的に使われる機能を@Injectable()
によって注釈されるクラスにより提供します。
上記コードのcontrollers
とproviders
はNestJSの初期プロジェクト設定時のままで変更しておらず、imports: [MessagingModule],
を追加したのみです。つまり今回カスタマイズしたLIME Messaging APIのエコーサーバーの全機能はMessagingModule
に入っていることになります。では、MessagingModule
を見ていきましょう。
messaging.module.ts
import { Module } from '@nestjs/common';
import { MessagingController } from './messaging.controller';
import { MessagingService } from './messaging.service';
@Module({
controllers: [MessagingController],
providers: [MessagingService],
exports: [MessagingService],
})
export class MessagingModule {}
今回カスタマイズした部分はほぼsrc/messagingに収まっています。それ以外は上記のmain.ts
をhttpsの設定を加えただけです。
messaging.module.ts
はNestJSのModuleなのでapp.module.ts
の構造と全く同じです。異なる点は、このコードではimports
を使用しておらずexports
で読み込んだプロバイダを公開しています。
messaging.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { LineMessagingApiDto } from './messaging.dto';
import { MessagingService } from './messaging.service';
@Controller('messaging')
export class MessagingController {
constructor(private readonly messagingService: MessagingService) {}
@Get()
getMessages() {
return this.messagingService.getMessage();
}
@Post()
postMessages(@Body() body: LineMessagingApiDto) {
return this.messagingService.dispachMessage(body);
}
}
MessagingAPIのロジックを定義するControllerです。@Controller('messaging')
によって、APIのパスが/messaging
となります。リクエストメソッドごとに定義されるクラスメソッドは@Get()
、@Post()
といったアノテーションによって非常に簡潔に定義できます。各メソッド内の機能は後述するmessaging.service.ts
内のProviderで提供されています。ProviderはControllerのクラスに注入されているためthis.messagingService.getMessage();
のように自クラスの機能のように利用できます。
messaging.service.ts
import { Injectable } from '@nestjs/common';
import type { ILineMessagingApi, ISimpleMessage } from './messaging.interface';
import { messagingApi } from '@line/bot-sdk';
import type { ClientConfig } from '@line/bot-sdk';
const getLineBotClient = () => {
const clientConfig: ClientConfig = {
channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
channelSecret: process.env.CHANNEL_SECRET,
};
return new messagingApi.MessagingApiClient(clientConfig);
};
@Injectable()
export class MessagingService {
getMessage(): ISimpleMessage {
return { message: 'This is a LINE Bot Server powered by NestJS' };
}
async dispachMessage(body: ILineMessagingApi): Promise<void> {
const client = getLineBotClient();
const event = body.events[0];
let textMessage: { type: string; text: string };
switch (event.message.type) {
case 'text':
textMessage = {
type: 'text',
text: event.message.text,
};
break;
default:
textMessage = {
type: 'text',
text: 'Server receinved an invalid message type',
};
}
await client.replyMessage({
replyToken: event.replyToken,
messages: [textMessage],
});
}
}
messaging.service.ts
にはMessaging APIを利用するエコーサーバーのロジックを含むメソッドが記述されています。ここに定義されているgetMessage()
やdispatchMessage()
をController側に記述することもできますが、上記のようにProviderとして提供することで、messaging.controller.ts
以外のControllerでこれらの機能を利用したくなった場合にすぐ提供することができます。
dispachMessage()
がLINE PlatformからWebhookを受信したときに動作するメソッドです。ここでは簡単のためTextEventMessageを受信したときのみ、テキストの内容をそのまま返信するようにしています。
await client.replyMessage({
replyToken: event.replyToken,
messages: [textMessage],
});
で、Message Eventに含まれるreplyToken
とテキストメッセージを設定してメッセージを送信したクライアントに対してメッセージを返信しています。
おわりに
NestJSで作成したシンプルなLINE Botサーバーのコードをざっと見ていきました。NestJSは大規模なWebサーバーを効率よく開発/メンテナンスするために大変優れた構造を持っています。LINE Botを開発保守している上で、NestJSは良い選択肢の一つになるんじゃないでしょうか。