はじめに
Honoは、高速で軽量な最新のWebフレームワークです。TypeScriptファーストで設計され、シンプルなAPIを持ちながらも強力な機能を提供しています。今回は、Honoを使っていると必ず出会う「c」というパラメータ、正式には「コンテキストオブジェクト」について詳しく解説します。
コンテキストオブジェクト「c」とは何か?
Honoでルートハンドラを記述する際、以下のような形式でコールバック関数を定義します:
app.get("/books", (c) => {
return c.json(books);
});
この c
が「コンテキストオブジェクト」です。単なる引数名ではなく、リクエストとレスポンスに関する情報や機能を詰め込んだ重要なオブジェクトで、Honoの核となる部分です。
コンテキストオブジェクト「c」の主な役割
1. リクエスト情報へのアクセス
コンテキストオブジェクトを通じて、クライアントからのリクエスト情報に簡単にアクセスできます。
app.get("/books", (c) => {
// クエリパラメータの取得
const query = c.req.query();
const keyword = query.keyword;
// パスパラメータの取得(例: /books/:id のような定義の場合)
const id = c.req.param('id');
// リクエストヘッダーの取得
const userAgent = c.req.header('user-agent');
// Cookieの取得
const cookie = c.req.cookie('session');
// リクエストボディの取得(JSON)
const body = await c.req.json();
// フォームデータの取得
const formData = await c.req.parseBody();
// 生のURLやメソッドへのアクセス
const url = c.req.url;
const method = c.req.method;
// ...処理
});
2. レスポンス生成
コンテキストオブジェクトには、様々な形式のレスポンスを簡単に生成するためのメソッドが用意されています。
app.get("/example", (c) => {
// テキストレスポンス
return c.text("Hello World");
// JSONレスポンス
return c.json({ message: "Success", data: books });
// HTMLレスポンス
return c.html("<h1>Hello Hono!</h1>");
// ステータスコードの設定
return c.status(404).json({ error: "Not Found" });
// リダイレクト
return c.redirect("/login");
// ヘッダーの設定
return c.header('X-Custom-Header', 'value').json({ ok: true });
});
3. ミドルウェアデータの共有
ミドルウェア間でデータを共有するためのストレージとしても機能します。
// 認証ミドルウェア
const authMiddleware = async (c, next) => {
const token = c.req.header('Authorization');
if (token) {
// トークンを検証し、ユーザー情報を取得
const user = await verifyToken(token);
// コンテキストにユーザー情報を保存
c.set('user', user);
}
await next();
};
app.use(authMiddleware);
app.get("/profile", (c) => {
// 前のミドルウェアで設定されたユーザー情報を取得
const user = c.get('user');
if (!user) {
return c.status(401).json({ error: "Unauthorized" });
}
return c.json({ profile: user });
});
4. 環境情報へのアクセス
コンテキストを通じて実行環境の情報にもアクセスできます。
app.get("/env", (c) => {
// 現在の実行環境を取得
const runtime = c.env.runtime;
// 環境変数にアクセス(Cloudflare Workersの場合)
const apiKey = c.env.API_KEY;
return c.json({ runtime, hasApiKey: !!apiKey });
});
5. バリデーションと型安全性
Honoでは、コンテキストオブジェクトを通じてリクエストデータのバリデーションも行えます。
import { z } from 'zod';
import { zValidator } from '@hono/zod-validator';
const bookSchema = z.object({
name: z.string().min(1),
status: z.enum(["在庫あり", "貸出中", "返却済"])
});
app.post(
"/books",
zValidator('json', bookSchema),
(c) => {
// バリデーション済みのデータを取得
const data = c.req.valid('json');
// データベースに保存するロジック
const newBook = saveBook(data);
return c.status(201).json(newBook);
}
);
実際の使用例
リアルな例で見てみましょう。書籍管理APIの実装例です:
import { serve } from "@hono/node-server";
import { Hono } from "hono";
const app = new Hono();
// 書籍データの型定義
type BookManager = {
id: number;
name: string;
status: string;
};
// テストデータ
const books: BookManager[] = [
{ id: 1, name: "React入門", status: "在庫あり" },
{ id: 2, name: "Rails入門", status: "貸出中" },
{ id: 3, name: "Go入門", status: "返却済" },
];
// 書籍一覧を取得するAPI
app.get("/books", (c) => {
const query = c.req.query();
const keyword = query.keyword;
// キーワードが指定されている場合は検索を行う
if (keyword) {
return c.json(books.filter((book) => book.name.includes(keyword)));
}
// 全書籍を返す
return c.json(books);
});
// 特定の書籍を取得するAPI
app.get("/books/:id", (c) => {
const id = parseInt(c.req.param('id'));
const book = books.find(book => book.id === id);
if (!book) {
return c.status(404).json({ error: "Book not found" });
}
return c.json(book);
});
// 新しい書籍を登録するAPI
app.post("/books", async (c) => {
try {
const body = await c.req.json<Omit<BookManager, 'id'>>();
// 新しいIDを生成(実際のアプリではDBが生成することが多い)
const newId = Math.max(...books.map(book => book.id)) + 1;
const newBook: BookManager = {
id: newId,
name: body.name,
status: body.status
};
books.push(newBook);
return c.status(201).json(newBook);
} catch (e) {
return c.status(400).json({ error: "Invalid request body" });
}
});
// 書籍情報を更新するAPI
app.put("/books/:id", async (c) => {
try {
const id = parseInt(c.req.param('id'));
const body = await c.req.json<Omit<BookManager, 'id'>>();
const index = books.findIndex(book => book.id === id);
if (index === -1) {
return c.status(404).json({ error: "Book not found" });
}
books[index] = {
id,
name: body.name,
status: body.status
};
return c.json(books[index]);
} catch (e) {
return c.status(400).json({ error: "Invalid request body" });
}
});
// サーバーを起動
serve(
{
fetch: app.fetch,
port: 8080,
},
(info) => {
console.log(`Server is running on http://localhost:${info.port}`);
}
);
上記の例では、コンテキストオブジェクト c
を使って:
- クエリパラメータからキーワードを取得
- パスパラメータからIDを取得
- リクエストボディからJSON形式のデータを取得
- レスポンスとしてJSONデータや適切なステータスコードを返却
これらの操作を簡潔に実装しています。
Express/Koaとの比較
Node.jsの他のフレームワークとHonoのコンテキストオブジェクトを比較してみましょう。
Express
Expressでは、リクエストとレスポンスが分かれています:
app.get('/books', (req, res) => {
const keyword = req.query.keyword;
if (keyword) {
return res.json(books.filter((book) => book.name.includes(keyword)));
}
return res.json(books);
});
Koa
Koaでは、Honoのようにコンテキストオブジェクトがあります:
app.use(async ctx => {
const keyword = ctx.query.keyword;
if (keyword) {
ctx.body = books.filter((book) => book.name.includes(keyword));
return;
}
ctx.body = books;
});
Hono
Honoでは、コンテキストからレスポンスを直接返せます:
app.get('/books', (c) => {
const keyword = c.req.query().keyword;
if (keyword) {
return c.json(books.filter((book) => book.name.includes(keyword)));
}
return c.json(books);
});
まとめ
Honoのコンテキストオブジェクト「c」は、シンプルでありながら非常に強力です。このオブジェクトを通じて:
- リクエスト情報へのアクセス - クエリ、パラメータ、ヘッダー、ボディなど
- レスポンス生成 - テキスト、JSON、HTML、ステータスコードなど
- ミドルウェアデータの共有 - セッション情報や認証データなど
- 環境情報へのアクセス - 実行環境や環境変数
- バリデーションと型安全性 - Zodなどとの連携
これらの機能を一貫したインターフェースで提供しています。
Honoのコンテキストオブジェクトを使いこなすことで、クリーンで型安全、そして高性能なWebアプリケーションやAPIを構築できます。単なる「c」という1文字の引数の中に、Honoの設計哲学が凝縮されているのです。
参考リンク
この記事が、Honoでの開発の一助となれば幸いです。コンテキストオブジェクト「c」を理解することは、Honoマスターへの第一歩です。