[Deno] FreshでLINE Messaging API SDKを使ってみる

# はじめに

[前回](https://qiita.com/bathtimefish/items/cecf83f749de9b95c51a)のおわりにDenoのフレームワークでLINE Messaging API SDKを使ってみようと書いていたので、[Fresh](https://fresh.deno.dev/)でやってみようと思います。

FreshはDenoのフルスタックWeb FrameworkでSSRを基本としてIslands ArchitectureなどモダンなアーキテクチャをサポートしているユニークなFrameworkです。個人的にはなんとなくDenoでのNext.js的な立ち位置を目指しているのかなと思っています。昨年安定版の1.0がリリースされました。

基本的にはSSRなWebアプリを開発できるFrameworkですが[カスタムRoute](https://fresh.deno.dev/docs/concepts/routes)を作成することもできるのでこれでLINE Bot Serverを開発することもできそうです。今回はこのアプローチでやってみます。

# やってみる

完成形はこちらです。

https://github.com/bathtimefish/fresh-linebot-example/tree/main

## プロジェクトを作成する

[Create a project](https://fresh.deno.dev/docs/getting-started/create-a-project)を参考に新規プロジェクトを作成します

```shell
$ deno run -A -r https://fresh.deno.dev

 🍋 Fresh: The next-gen web framework. 

Project Name: fresh-linebot
Let's set up your new Fresh project.

Do you want to use a styling library? [y/N] 
Do you use VS Code? [y/N] Y
The manifest has been generated for 5 routes and 1 islands.

Project initialized!

Enter your project directory using cd fresh-linebot.
Run deno task start to start the project. CTRL-C to stop.

Stuck? Join our Discord https://discord.gg/deno 

Happy hacking! 🦕

$ cd fresh-linebot
$ ls
README.md	deno.json	fresh.config.ts	islands		routes
components	dev.ts
```

## VSCodeを設定する

[前回記事のVSCodeの設定](https://qiita.com/bathtimefish/items/cecf83f749de9b95c51a#vscode%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)を追記しておきます。

## リクエストハンドラをカスタマイズする

Freshでは`routes`以下にファイルを作成すれば自動的にルーターが設定されるようになっています。
デフォルトでは`./routes/api/joke.ts`というサンプルコードが記載されています。`/api/joke`にアクセスするとランダムにジョークをレスポンスしてくれるようです。

![スクリーンショット 2024-02-19 17.29.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/8d2e2d8a-f77a-0080-cd73-c7defa685153.png)

　ためしにこのままWeb Serverを起動して

```shell
deno task start
```

APIを呼ぶとジョークが返ってきました

```shell
$ curl http://localhost:8000/api/joke
An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.% 
```

このhandlerにLINE Messaging API SDKを組み込んでカスタムしていったらよさげですが、このままだとすべてのRequest Methodを受け付けることになるので、WebhooksのPOSTメソッドのみ受け付けるようにしてみます。

```/routes/api/joke.ts
import type { Handlers, FreshContext } from "$fresh/server.ts";

// Jokes courtesy of https://punsandoneliners.com/randomness/programmer-jokes/
const JOKES = [
  "Why do Java developers often wear glasses? They can't C#.",
  "A SQL query walks into a bar, goes up to two tables and says “can I join you?”",
  "Wasn't hard to crack Forrest Gump's password. 1forrest1.",
  "I love pressing the F5 key. It's refreshing.",
  "Called IT support and a chap from Australia came to fix my network connection.  I asked “Do you come from a LAN down under?”",
  "There are 10 types of people in the world. Those who understand binary and those who don't.",
  "Why are assembly programmers often wet? They work below C level.",
  "My favourite computer based band is the Black IPs.",
  "What programme do you use to predict the music tastes of former US presidential candidates? An Al Gore Rhythm.",
  "An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.",
];

export const handler: Handlers =  {
  async POST(_req: Request, _ctx: FreshContext): Promise<Response> {
    console.log(await _req.json());
    const randomIndex = Math.floor(Math.random() * JOKES.length);
    const body = JOKES[randomIndex];
    return new Response(body);
  }
};
```

```shell
$ curl http://localhost:8000/api/joke
$ curl -X POST -H "Content-Type: application/json" -d '{"hello":"world"}' localhost:8000/api/joke
Wasn't hard to crack Forrest Gump's password. 1forrest1.% 
```

1度目のGETリクエストでは何も返ってきませんが、2度目のPOSTリクエストでジョークが返ってきています。

```shell
Watcher File change detected! Restarting!

 🍋 Fresh ready 
    Local: http://localhost:8000/

{ hello: "world" }
```

Web Serverを起動しているコンソールでは`console.log(await _req.json());`の結果としてリクエストボディが出力されています。

[前回のシンプルなBot Server](https://qiita.com/bathtimefish/items/cecf83f749de9b95c51a#%E3%82%B7%E3%83%B3%E3%83%97%E3%83%AB%E3%81%AAbot-server%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B)を参考に`/routes/api/messaging.ts`を作成します。

```/routes/api/messaging.ts
import type { Handlers, FreshContext } from "$fresh/server.ts";
import { messagingApi, MessageEvent } from "npm:@line/bot-sdk@8.4.0";
import type { ClientConfig } from "npm:@line/bot-sdk@8.4.0";

const config: ClientConfig = {
    channelAccessToken: "YOUR_CHANNEL_ACCESS_TOKEN",
    channelSecret: "YOUR_CHANNEL_SECRET",
};
const client = new messagingApi.MessagingApiClient(config);

export const handler: Handlers =  {
  async POST(_req: Request, _ctx: FreshContext): Promise<Response> {
    const body = await _req.json() 
    const event: MessageEvent = body.events[0]
    await client.replyMessage({
      replyToken: event.replyToken,
      messages: [
        {
          type: "text",
          text: "Hello, I'm Fresh.",
        },
      ],
    });
    return new Response(null, { status: 204 });
  }
};
```

`YOUR_CHANNEL_ACCESS_TOKEN`, `YOUR_CHANNEL_SECRET`はLINEチャンネルのアクセストークンとチャンネルシークレットに読み替えてください。

TLS Serverにするため `fresh.config.ts` を以下のように更新します

```fresh.config.ts
import { defineConfig } from "$fresh/server.ts";

const keyPath = "/[YOUR_KEY_PATH]/privkey.pem";
const certPath = "/[YOUR_CERT_PATH]/fullchain.pem";
const key = await Deno.readTextFile(keyPath);
const cert = await Deno.readTextFile(certPath);

export default defineConfig({
  key,
  cert,
});
```

`/[YOUR_KEY_PATH]/privkey.pem`, `/[YOUR_CERT_PATH]/fullchain.pem`は各SSL証明書ファイルへのパスに読み替えてください。

これだけで完了です。

## 実行してみる

[LINE BotサーバーをNestJSで開発する その1](https://qiita.com/bathtimefish/items/e6c151632ad1e9ca71ed)と同様にEC2を立ち上げてDenoをインストールし、Let's Encriptの証明書を取得した後、上記のパス設定等を更新して実行します。

```shell
deno task start
```

LINEアプリからBotに対してメッセージを送信すると`Hello, I'm Fresh.`と返ってきます。

![Screenshot_20240221-133018.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/9b830708-07af-5711-87fd-9a4e811e9234.png)

# おわりに

DenoのフルスタックWeb FrameworkであるFreshを使ってLINE Bot Serverを作ってみました。Freshはルーティングの自動生成など開発者がアプリケーションロジックに集中できるような機能が揃っていて高速にWebアプリケーションを開発できます。

個人的にはDenoのおかげでTypeScriptのトランスパイル環境を準備する手間や開発中にビルドのオーバーヘッドがないのは非常に楽だなと感じています。前回と今回の検証でLINE Bot Server開発もDenoに移行していいかもしないなと感じました。