hono のチュートリアルをやってみる
![ああるあ](https://res.cloudinary.com/zenn/image/fetch/s--D7z9o9n2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0cf6dca715.jpeg)
hono のプロジェクトを作成
$ bun create hono@latest
create-hono version 0.10.0
? Target directory my-app
? Which template do you want to use?
❯ aws-lambda
bun
cloudflare-pages
cloudflare-workers
deno
fastly
lambda-edge
(Use arrow keys to reveal more choices)
テンプレートを選ぶところが注意力散漫してるとむずかしい。
スクロールできる(他のこういう作成コマンドってスクロールできるんだっけ。。)。
? Which template do you want to use?
lambda-edge
netlify
nextjs
❯ nodejs
vercel
x-basic
aws-lambda
(Use arrow keys to reveal more choices)
注意力散漫って書いた通り、"Use arrow keys to reveal more choices(もっと他のオプションを見たいなら矢印キーを使ってね)"って書いてる。
ちゃんと読もう。
![ああるあ](https://res.cloudinary.com/zenn/image/fetch/s--D7z9o9n2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0cf6dca715.jpeg)
リクエストとレスポンス
パスパラメータとクエリ文字列。
app.get('/posts/:id', (c) => {
const page = c.req.query('page')
const id = c.req.param('id')
c.header('X-Message', 'Hi!')
return c.text(`You want see ${page} of ${id}`)
})
チュートリアルのコードだと、http://localhost:3000/posts/1?page=hoge にアクセスすると
となる。
チュートリアルコードしか見ずに説明を読んでなくて、http://localhost:3000/posts/1 にアクセスして
undifined やなあ、とか思ってた。
![ああるあ](https://res.cloudinary.com/zenn/image/fetch/s--D7z9o9n2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0cf6dca715.jpeg)
(続き)
上の説明を読まない不名誉をそそぐために、HonoRequest
の他のメソッドで遊んでみる。
queries()
app.get("/queries", (c) => {
const queries = c.req.queries();
return c.text(`You want see ${queries}`);
});
http://localhost:3000/queries?p1=foo&p2=bar にアクセスして
…そそげてないっすね。またちゃんと説明を読んでない。
queries()
は複数のクエリ文字列じゃなくて、1つのクエリ文字列に複数の値があるときに使うものなので、
app.get("/queries", (c) => {
const queries = c.req.queries("p1");
return c.text(`You want see ${queries?.toString()}`);
});
routePath, path
app.get("/routePath/:id", (c) => {
return c.text(`You see route path ${c.req.routePath}`);
});
app.get("/path/:id", (c) => {
return c.text(`You see path ${c.req.path}`);
});
http://localhost:3000/routePath/1 で
http://localhost:3000/path/1 で
何がちゃうんやこの2つって思ったけど、やってみると全然違うね。
url
app.get("/very/long/url", (c) => {
return c.text(`You see ${c.req.url}`);
});
http://localhost:3000/very/long/url で
method
app.get("/method", (c) => {
return c.text(`Your method is ${c.req.method}`);
});
http://localhost:3000/method で
他にも
app.get("/method", (c) => {
return c.text(`Your method is ${c.req.method}\n`);
});
app.post("/method", (c) => {
return c.text(`Your method is ${c.req.method}\n`);
});
app.delete("/method", (c) => {
return c.text(`Your method is ${c.req.method}\n`);
});
とかして
$ curl -X GET http://localhost:3000/method
Your method is GET
$ curl -X POST http://localhost:3000/method
Your method is POST
$ curl -X DELETE http://localhost:3000/method
Your method is DELETE
…色々やったけど、param()
と query()
、queries()
くらいしか使い道を思いつけない。。
![ああるあ](https://res.cloudinary.com/zenn/image/fetch/s--D7z9o9n2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0cf6dca715.jpeg)
HTML
const View = () => {
return (
<html>
<body>
<h1>Hello Hono!</h1>
</body>
</html>
);
};
app.get("/page", (c) => {
return c.html(<View />);
});
と思ったら Biome になんか怒られていた。
src/index.tsx:54:3 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━
✖ Provide a lang attribute when using the html element.
52 │ const View = () => {
53 │ return (
> 54 │ <html>
│ ^^^^^^
55 │ <body>
56 │ <h1>Hello Hono!</h1>
ℹ Setting a lang attribute on HTML document elements configures the languageused by screen readers when no user default is specified.
<html>
じゃなくて <html lang="en">
などとしろということらしい。
というわけでこう↓
const View = () => {
return (
<html lang="en">
<body>
<h1>Hello Hono!</h1>
</body>
</html>
);
};
![ああるあ](https://res.cloudinary.com/zenn/image/fetch/s--D7z9o9n2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0cf6dca715.jpeg)
(html 続き)
ドキュメント を見たらこう書いてた。
Render HTML as Content-Type:text/html.
じゃあ html を text()
で返したらどうなるんだろう。
というわけでやってみる↓。
app.get("/htmltext", (c) => {
return c.text(<View />);
});
http://localhost:3000/htmltext で
そらそうなるなって感じではある。
![ああるあ](https://res.cloudinary.com/zenn/image/fetch/s--D7z9o9n2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0cf6dca715.jpeg)
(html 続き2)
html ファイルを返すことはできるんだろうか?
と思って調べてみたが、Context.html()
ではできないのか。
serveStatic
を使うらしい。
![ああるあ](https://res.cloudinary.com/zenn/image/fetch/s--D7z9o9n2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0cf6dca715.jpeg)
Middleware
basicAuth
ベーシック認証の Middleware。
app.use(
"/admin/*",
basicAuth({
username: "admin",
password: "password",
}),
);
app.get("/admin", (c) => {
return c.text("Admin page");
});
![ああるあ](https://res.cloudinary.com/zenn/image/fetch/s--D7z9o9n2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0cf6dca715.jpeg)
Request ID
他の Middleware を。。
app.use("*", requestId());
app.get("/requestId", (c) => {
return c.text(`Your request id is ${c.get("requestId")}`);
});
http://localhost:3000/requestId で
リロードするたびにリクエスト ID が変わる。
![ああるあ](https://res.cloudinary.com/zenn/image/fetch/s--D7z9o9n2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0cf6dca715.jpeg)
Adapter
何なんですかね、これ。
プラットフォーム依存の色々をよしなにする。。?
serveStatic
を試してみる
サンプルから favicon を抜いて(用意するのが面倒だった)
import { Hono } from "hono";
import { serveStatic } from "hono/bun";
const app = new Hono();
app.use("/static/*", serveStatic({ root: "./" }));
app.get("/", (c) => c.text("You can access: /static/hello.txt"));
app.get("*", serveStatic({ path: "./static/fallback.txt" }));
export default app;
あちこちにアクセスしてみる。
これは動作がわかりやすい。
他のランタイム向けのモジュールにも同じ serveStatic
関数が用意されているけど、なぜだろう。
たぶんランタイムによって内部処理が異なってくるからなんだろうけど。
実装を見たら分かりそう(見ない)。
![ああるあ](https://res.cloudinary.com/zenn/image/fetch/s--D7z9o9n2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0cf6dca715.jpeg)
あとは SSG 周りを触りたい