Honoでサービス公開RTA
はじめに
こんにちは。シュートです。
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.
前提
-
create-hono
からデプロイまでをどのくらいの時間でできるかということをやってみるため、「サービス公開」などとは言っていますがインターフェースも機能も作り込みません - Honoやその周辺技術の詳細には触れません
- Cloudflareのアカウントが必要です
使用技術
- Hono
- Cloudflare Workers
- Cloudflare Pages
- D1データベース
やっていくこと
- シンプルな文字列を返すだけのAPI
- 文字が表示されているだけの静的ページ
- データベースを使ったToDoアプリ
- ↑にbasic認証をつける
やっていく
それではここからRTAスタート。
シンプルな文字列を返すだけのAPI
シンプルに文字列を返してくれるだけのAPIを作成します。
まずは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は以下のようになっていると思います。
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を返すように変更して、デプロイしてみます。
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をあててみました。
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データベースを使えるようにします。
今回は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ファイルが生成されるので、作成したいテーブルの情報を入力してそれを反映させます。
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を保存
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認証をつけてあげます。
ドキュメントにしたがって修正します。
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株式会社のエンジニアを中心に更新していくPublicationです。 NEでは、「コマースに熱狂を。」をパーパスに掲げ、ECやその周辺領域の事業に取り組んでいます。 Homepage: ne-inc.jp/
Discussion