🔥

Honoでサービス公開RTA

2024/12/23に公開

はじめに

こんにちは。シュートです。

Honoの開発者体験が良いということを最近頻繁に耳にするので、これを期に簡単なサービスをどれだけサクッと公開できるかRTAをやってみようと思います。
あと使ってみたかったんです。

Honoがどんなものなのかはこちら。

At first, I just wanted to create a web application on Cloudflare Workers. But, there was no good framework that works on Cloudflare Workers. So, I started building Hono.

https://hono.dev/docs/concepts/motivation

前提

  • create-honoからデプロイまでをどのくらいの時間でできるかということをやってみるため、「サービス公開」などとは言っていますがインターフェースも機能も作り込みません
  • Honoやその周辺技術の詳細には触れません
  • Cloudflareのアカウントが必要です

使用技術

  • Hono
  • Cloudflare Workers
  • Cloudflare Pages
  • D1データベース

やっていくこと

  1. シンプルな文字列を返すだけのAPI
  2. 文字が表示されているだけの静的ページ
  3. データベースを使ったToDoアプリ
  4. ↑にbasic認証をつける

やっていく

それではここからRTAスタート。

シンプルな文字列を返すだけのAPI

シンプルに文字列を返してくれるだけのAPIを作成します。
まずはCloudflare Workersの環境を構築します。

公式ドキュメントに沿ってやっていきます。
https://hono.dev/docs/getting-started/cloudflare-workers

今回はせっかくなのでbunを使ってみました。

$ bunx create-hono my-app
$ cd my-app
$ bun i

Which template do you want to use? についてはまずは「cloudflare-workers」を選択します。
これで終わり。

src/index.tsは以下のようになっていると思います。

src/index.ts
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
  return c.text('Hello Hono!')
})

export default app

とりあえずこのまま実行して、指定のlocalhostにcurlすると「Hello Hono!」の文字列が返ってくることがわかります。

$ bun run dev
$ curl localhost:{ポート番号}
# → Hello Hono!

APIっぽくJSONを返すように変更して、デプロイしてみます。

src/index.ts
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
  return c.json({ greet: 'Hello Hono!' })
})

export default app
$ bun run deploy

完了するとCloudflareの画面の Workers & Pagesに、先ほど作成した「my-app」というアプリケーションが作成されていると思います。
その画面から確認できる公開されたURLに対してcurlすると、ローカルと同じようにJSONが返ってくることが確認できます。

$ curl {公開されたURL}
# → {"greet":"Hello Hono!"}

簡単ですね。

ここまでかかった時間

文字が表示されているだけの静的ページ

次にClaudflare Pagesを使って静的なページを作っていきます。
先ほどと同様。今回は「claudflare-pages」を指定します。

$ bunx create-hono my-page
$ cd my-page
$ bun i

そのままデプロイするのも味気ないので、src/index.tsxにstyleをあててみました。

src/index.tsx
import { Hono } from 'hono'
import { renderer } from './renderer'

const app = new Hono()

app.use(renderer)

app.get('/', (c) => {
  return c.render(<h1 style='color: red'>Hello!</h1>)
})

export default app

ローカルで動作を確認できたら、こちらも先ほどと同様のコマンドでデプロイします。
こちらもまた先ほどと同様にCloudflareの画面の Workers & Pagesに先ほど作成した「my-page」というアプリケーションが作成されており、そこからデプロイされた画面を確認できます。

ここまでかかった時間

データベースを使ったToDoアプリ

例に倣ってclaudflare-pagesのテンプレートを作成していきます

$ bunx create-hono my-todo
$ cd my-todo
$ bun i

ドキュメントを参考に、D1データベースを使えるようにします。
https://hono.dev/examples/prisma#d1-database

今回はtodoという名前のデータベースを作成します

# データベースの作成
$ bunx wrangler d1 create todo

すると↓のような情報が表示されるのでそれをwrangler.tomlに追記します

[[d1_databases]]
binding = "DB"
database_name = "todo"
database_id = "xxxxxxxxxxxxxxxxxxxxxx"

次にテーブルの作成です。
taskというテーブルを作成することにします。

# マイグレーションファイルの作成
$ bunx wrangler d1 migrations create todo create_task_table

SQLファイルが生成されるので、作成したいテーブルの情報を入力してそれを反映させます。

migrations/0001_create_task_table.sql
CREATE TABLE "task" (
    "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    "content" TEXT NOT NULL
);
# リモートのDBにマイグレーションの実行
$ bunx wrangler d1 migrations apply todo --remote

これでマイグレーションは完了です。
Cloudflareの「ストレージとデータベース」からデータベースが作成されていることが確認できます。

これでデータベースの準備はできたので画面から以下のことができるように修正していきます。

  • taskテーブルに保存されたTodo一覧の表示
  • taskテーブルにtodoを保存
src/index.tsx
import { Hono } from 'hono'
import { renderer } from './renderer'

type Bindings = {
  DB: D1Database
}

const app = new Hono<{ Bindings: Bindings }>()

app.use(renderer)

// 一覧の表示
app.get('/', async (c) => {
  const res = await c.env.DB.prepare(
    'SELECT * FROM task'
  ).all()
  const tasks = res.results
  return c.render(
    <div>
      <h1>ToDo一覧</h1>
      <form action="/create" method="post" >
        <textarea name="content" />
        <button>投稿</button>
      </form>
      <ul>
        {tasks.map((task: any) => (
          <li>{task.content}</li>
        ))}
      </ul>
    </div>
  )
})

// ToDoの投稿
app.post('/create', async (c) => {
  const response = await c.req.parseBody()
  const content = response.content
  await c.env.DB.prepare(
    'INSERT INTO task (content) VALUES (?)'
  ).bind(content).run();
  return c.redirect('/')
})

export default app

これをデプロイするとToDoアプリLv.1のようなものができあがりです。

ここまでかかった時間

basic認証をつける

これだと誰でも使えるToDoアプリになっちゃっているので、自分しか使えないようにするためにbasic認証をつけてあげます。

ドキュメントにしたがって修正します。
https://hono.dev/docs/middleware/builtin/basic-auth

src/index.tsx
import { Hono } from 'hono'
import { basicAuth } from 'hono/basic-auth'
import { renderer } from './renderer'

type Bindings = {
  DB: D1Database
}

const app = new Hono<{ Bindings: Bindings }>()

app.use(renderer)

// basic認証の設定
app.use(
  '*', // ←全てのエンドポイントが対象
  basicAuth({
    username: 'hono', // ← 認証時のユーザーネーム
    password: 'password' // ← 認証時のパスワード
  })
)

app.get('/', async (c) => {
  const res = await c.env.DB.prepare(
    'SELECT * FROM task'
  ).all()
  const tasks = res.results
  return c.render(
    <div>
      <h1>ToDo一覧</h1>
      <form action="/create" method="post" >
        <textarea name="content" />
        <button>投稿</button>
      </form>
      <ul>
        {tasks.map((task: any) => (
          <li>{task.content}</li>
        ))}
      </ul>
    </div>
  )
})

app.post('/create', async (c) => {
  const response = await c.req.parseBody()
  const content = response.content
  await c.env.DB.prepare(
    'INSERT INTO task (content) VALUES (?)'
  ).bind(content).run();
  return c.redirect('/')
})

export default app

これだけの処理でアクセス時にはbasic認証を求められるようになりました。

デプロイして終わり。

ここまでかかった時間

終わりに

データベースを持つbasic認証付きのアプリケーションが30分そこらでデプロイまでできてしまいました。本記事を書いている方が何倍も時間がかかりました。
とにかく開発者体験がめちゃくちゃ良いフレームワークだなと感じました。
ガッツリとアプリケーションを作るならもっとちゃんと様々な考慮の必要はありますが、Cloudflare WorkersでAPIを作ったりするのはめちゃくちゃサクッとできそうです。

型安全だし国産だし、これからも応援していきたいですね。
年末年始のお供にHonoはいかがでしょうか。

NE株式会社の開発ブログ

Discussion