Deno+HonoでWebサーバを立てる ~DenoKVを添えて

# はじめに
先日、[Deno Fest](https://deno-fest-2023.deno.dev/)をオンラインで聞いて面白いセッションが多かったので、DenoやHono、DenoKVなどを触ってみたくなりました。
簡単にAPIやユニットテストを作成・CIを整備して所感を書きます。DenoもHonoも初心者なので認識が誤っている箇所が含まれる可能性があります。その点ご容赦ください。

# 作ったもの
https://github.com/Showichiro/deno-kv-hono-postman-example

# Denoについて

[公式](https://docs.deno.com/runtime/manual)の説明によると

> Deno (/ˈdiːnoʊ/, pronounced dee-no) is a JavaScript, TypeScript, and WebAssembly runtime with secure defaults and a great developer experience. It's built on V8, Rust, and Tokio.

つまり、Javascript・TypeScript・WebAssemblyのランタイムです。

Deno Festの話を聞いている限り、サーバサイド JavaScriptランタイムはNode以外にも、Deno/Bunなどが覇権を巡って争っている戦国時代のようです。

## Denoの特徴

たくさんあるようですが、[公式](https://docs.deno.com/runtime/manual)の説明によると

### TypeScriptの組み込みサポート
> Deno has built-in support for TypeScript as well. 

JavaScriptへのトランスパイルが要りません。

### デフォルトのセキュリティ

> A major feature of Deno is runtime security by default, meaning that you as the developer must explicitly allow your code to access potentially sensitive APIs like file system access, network connectivity, and access to environment variables.

ファイルやネットワークへのアクセスに対して明示的に許可しないといけません。

### URLからのライブラリの読み込み

> Deno supports loading and executing code from URLs, much as you would using a \<script\> tag in the browser. In Deno 1.x, the standard library and most third-party modules are distributed on HTTPS URLs.

以下のようにURL指定でライブラリを読み込むことができます。

```ts
import { assertEquals } from "https://deno.land/std@0.204.0/assert/mod.ts";
```

### 組み込みテストランナー

> Deno has a built-in test runner that you can use for testing JavaScript or TypeScript code.

Nodeにおけるjestなどのように、別でテストランナーを見繕う必要がありません。

などなどがあります。

また、npmもサポートされています。

# Honoについて

[公式](https://hono.dev/)によると

> Hono - [炎] means flame🔥 in Japanese - is a small, simple, and ultrafast web framework for the Edges. It works on any JavaScript runtime: Cloudflare Workers, Fastly Compute@Edge, Deno, Bun, Vercel, Netlify, Lagon, AWS Lambda, Lambda@Edge, and Node.js.

どこでも動くめっちゃ早くてコンパクトなWebフレームワークとのことです。特にWeb標準に従って実装していることを売りにしています(Web標準に従っている実装ているからどのランタイムでも動く)。

> Thanks to using only Web Standard APIs, we could make it work on Deno and Bun. "No Express for Bun?" we could answer, "No, but there is Hono" though Express works on Bun now.

使い方も簡単でexpressなどを触ったことがあればすぐに使用イメージが付くと思います。

また、今回は省いていますが、JSXサポートも進んでいるようです(React**ではない**のでクライアントサイドのことはやらない)。

# Deno KVについて

Deno組み込みのキーバリューストアです。現在(2023/11/01時点)Betaです。
APIもわかりやすいです。[キーに配列で様々な値を渡せるのが特徴](https://docs.deno.com/kv/manual/key_space#key-examples)で、TanStack Queryを彷彿とさせます。[簡易なAPI](https://docs.deno.com/kv/manual/operations)でCRUD操作が提供されています。

# いざ実践

## Deno環境構築

[公式](https://docs.deno.com/runtime/manual/)に従いDenoランタイムのインストールを行います。

```bash
curl -fsSL https://deno.land/x/install/install.sh | sh
```

パスを通すように表示されるのでパスを通します。

最後にパスが通っていることを確認します。
```bash
deno --version
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619165/35ff2d99-b795-e2f4-d5aa-4ae5aceb8f11.png)

## Honoプロジェクトの作成

Deno向けの紹介ページがあるので[そちら](https://hono.dev/getting-started/deno)の通り進めます。

```bash
deno run -A npm:create-hono my-app
```

```bash
cd my-app
```

## プロジェクト設定

vscodeを前提に進めます。

vscodeのdeno用の拡張機能(`denoland.vscode-deno`)をインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619165/f16003b0-202c-c5ce-5d36-d36e492bdbf8.png)


`.vscode/settings.json`の設定をいじります。lint/formatを設定します。

```json:.vscode/settings.json
{
  "deno.enablePaths": [
    "./"
  ],
  "deno.enable": true,
  "editor.inlayHints.enabled": "off",
  "deno.lint": true,
  "editor.formatOnSave": true,
  "[typescript]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  }
}

```

`deno.json`を作ります。今回は以下のように定義します。

```jsonc:deno.json
{
  // package.jsonのscriptsみたいなもの
  // ファイルアクセスやネットへのコネクションを許可したり、Deno KVを使うために--unstableフラグを渡しています。
  "tasks": {
    "dev": "deno run -A --unstable hello.ts",
    "test": "deno test -A --unstable"
  },
  // lockfileを作るかどうか
  "lock": false,
  // import specifiers
  "imports": {
    "hono": "https://deno.land/x/hono@v3.9.0/mod.ts",
    "ulid": "https://deno.land/x/ulid@v0.3.0/mod.ts",
    "assert": "https://deno.land/std@0.204.0/assert/mod.ts"
  }
}
```

## CRUD用のメソッドを実装する

```ts
import { monotonicFactory } from "ulid";

const kv = await Deno.openKv("kv.sqlite");

type User = {
  name: string;
  age: number;
};

const findAllUsers = async () => {
  const entries = kv.list<User>({
    prefix: ["users", "id"],
  });
  let res: Array<User & { id: string }> = [];
  for await (const entry of entries) {
    res = [...res, { ...entry.value, id: entry.key[2] as string }];
  }
  return res;
};

const findUserById = async (id: string): Promise<User | null> => {
  const { value } = await kv.get<User>(["users", "id", id]);
  return value;
};
```

## server実装

honoはexpress likeに実装することができます。

```ts:hello.ts
import { Hono } from "hono";

const app = new Hono();

app.get(
  "/users",
  async (c) => {
    const res = await findAllUsers();
    return c.json(res);
  },
).get("/users/:id", async (c) => {
  const id = c.req.param("id");
  const value = await findUserById(id);
  return value ? c.json({ ...value, id }) : c.json(notFound, 404);
});

Deno.serve(app.fetch);

export default app;
```

### 実行

```bash
deno task dev
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619165/0eb561b7-88d9-bae1-ab95-f3252a73475b.png)



## テストを書く

Denoはテストランナーが内蔵されています。[公式](https://docs.deno.com/runtime/manual/basics/testing/)の例に従って実装します。

```ts:hello.test.ts
import app from "./hello.ts";
import { assert, assertEquals, assertInstanceOf } from "assert";

type User = { id: string; name: string; age: number };

Deno.test("GET /users", async () => {
  const res = await app.request("/users", { method: "GET" });
  assertEquals(res.status, 200);
  const json = await res.json();
  assertInstanceOf(json, Array<User>);
});
```

### テストを実行する

```bash
deno task test
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619165/f9f380a7-c231-80b0-b821-e3bb5f076eb0.png)


## CI整備する

特にプロジェクト設定固有の設定はないのでgithubに出てきたものをほぼそのまま採用。

```yaml:.github/workflows/deno.yml
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

# This workflow will install Deno then run `deno lint` and `deno test`.
# For more information see: https://github.com/denoland/setup-deno

name: Deno

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Setup repo
        uses: actions/checkout@v3

      - name: Setup Deno
        # uses: denoland/setup-deno@v1
        uses: denoland/setup-deno@61fe2df320078202e33d7d5ad347e7dcfa0e8f31  # v1.1.2
        with:
          deno-version: v1.x

      # Uncomment this step to verify the use of 'deno fmt' on each commit.
      # - name: Verify formatting
      #   run: deno fmt --check

      - name: Run linter
        run: deno lint

      - name: Run tests
        run: deno task test

```

# 所感

## Denoはごちゃごちゃしてなくていい

Nodeを用いた開発あるあるの大量の設定ファイル(package.json/eslint/prettier/jest.config...)がなく、node_modulesがなくJavaScriptのトランスパイルも意識しないのでリポジトリがとてもスッキリしていて認知的な負荷が低いように感じました。特に初心者が参画する案件などで認知的な負荷が低いのは重要な気がします。ただし、この辺は実務で利用すると何かとツールが増えてくるのかもしれません。

## Honoはコンパクトな要件であれば使い勝手がよさそう

フレームワークとしてのお作法や決め事が非常に少ない印象で、学習コストが低そうです。そのうえでWeb標準に則っているため汎用性が高そうという意味で好印象を受けてました（テストを書く際に直感的に書くことができました）。一方で、シンプルゆえに、複雑な業務要件などがあるアプリ開発では、結構大変になるケースもありそうな印象を受けました（そういうケースはそもそもHonoがターゲットにしていないと思いますし）。