Open11

hono のチュートリアルをやってみる

ああるあああるあ

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(もっと他のオプションを見たいなら矢印キーを使ってね)"って書いてる。
ちゃんと読もう。

ああるあああるあ

Hello World と JSON レスポンス

チュートリアル通りのコードを書いたらいい。
特に問題なし。

あ、このスクラップは Getting Started をやっていくだけ

ああるあああるあ

リクエストとレスポンス

パスパラメータとクエリ文字列。

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 やなあ、とか思ってた。

ああるあああるあ

(続き)
上の説明を読まない不名誉をそそぐために、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() くらいしか使い道を思いつけない。。

ああるあああるあ

HTML

const View = () => {
	return (
		<html>
			<body>
				<h1>Hello Hono!</h1>
			</body>
		</html>
	);
};

app.get("/page", (c) => {
	return c.html(<View />);
});

http://localhost:3000/page

と思ったら 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>
	);
};
ああるあああるあ

(html 続き)
ドキュメント を見たらこう書いてた。

Render HTML as Content-Type:text/html.

じゃあ html を text() で返したらどうなるんだろう。
というわけでやってみる↓。

app.get("/htmltext", (c) => {
	return c.text(<View />);
});

http://localhost:3000/htmltext

そらそうなるなって感じではある。

ああるあああるあ

(html 続き2)
html ファイルを返すことはできるんだろうか?

と思って調べてみたが、Context.html() ではできないのか。
serveStatic を使うらしい。

ああるあああるあ

Middleware

basicAuth

ベーシック認証の Middleware。

app.use(
	"/admin/*",
	basicAuth({
		username: "admin",
		password: "password",
	}),
);

app.get("/admin", (c) => {
	return c.text("Admin page");
});

ああるあああるあ

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 が変わる。

ああるあああるあ

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;

あちこちにアクセスしてみる。

http://localhost:3000/static/demo/index.html

http://localhost:3000/dame

http://localhost:3000/static/hello.txt

これは動作がわかりやすい。

他のランタイム向けのモジュールにも同じ serveStatic 関数が用意されているけど、なぜだろう。
たぶんランタイムによって内部処理が異なってくるからなんだろうけど。
実装を見たら分かりそう(見ない)。