プログラミング · Hono · TypeScript
Honoメソッドのタイピング練習
Hono(TypeScript)のメソッド36個を、初級・中級・上級の3つのリストで打って覚えます。app.get でのルート定義、c.req.query でのクエリの取得、c.req.parseBody でのフォームの読み取りなどを練習できます。
3リスト・36語
無料・ログインなしで練習できます。押すとこのパックをフォローし、すぐに練習が始まります。
収録している言葉
Honoメソッド(初級)
| 打つ言葉 | 意味・補足 |
|---|---|
| import { Hono } from 'hono'; const app = new Hono(); | Hono / TypeScript:対象環境(現行公式APIで共通的な機能を中心に整理。実行環境・メジャー版・adapterを固定して利用) |
| app.get('/', c => c.text('Hello, Hono!')); | GET / c.text:テキスト応答(必ずResponse等をreturn) |
| app.post('/users', c => c.json({ id: 1 }, 201)); | c.json / status:JSONとステータス(実際の保存・認証認可・入力検証は別途) |
| app.get('/page', c => c.html('<h1>Hello</h1>')); | c.html / c.body:HTML・本文(c.htmlへ未検証入力を直接連結しない) |
| app.delete('/items/:id', c => c.body(null, 204)); | get/post/put/patch/delete:メソッド別ルート(実削除には認可等が必要) |
| app.get('/users/:id', c => c.json({ id: c.req.param('id') })); | c.req.param:パス引数(文字列。型・範囲を検証) |
| app.get('/old', c => c.redirect('/new', 302)); | c.redirect:転送(未検証の転送先を使わない) |
| app.notFound(c => c.json({ error: 'not found' }, 404)); | c.notFound / app.notFound:404(特定ハンドラーからreturn c.notFound()も可能) |
| export default app; | Workers / Bun等:Fetch入口をexport(ランタイム・デプロイ設定に応じた入口。Nodeはadapter等が必要) |
| npm install hono @hono/node-server | 導入・実行:開発コマンド(Node.jsの例。型検査はtsc等で別途) |
| npm install -D typescript tsx @types/node | 導入・実行:開発コマンド(Node.jsの例。型検査はtsc等で別途) |
| npx tsx src/index.ts | 導入・実行:開発コマンド(Node.jsの例。型検査はtsc等で別途) |
Honoメソッド(中級)
| 打つ言葉 | 意味・補足 |
|---|---|
| app.get('/search', c => c.json({ q: c.req.query('q'), tags: c.req.queries('tag') })); | c.req.query / queries:クエリ取得(queryは単一、queriesは複数値) |
| const users = new Hono(); users.get('/', c => c.json([])); app.route('/users', users); | app.route:サブアプリをマウント(子のルート定義・登録順を確認) |
| const api = new Hono().basePath('/api'); api.get('/health', c => c.json({ ok: true })); | basePath:共通プレフィックス(/api/health) |
| app.post('/echo', async c => { const body: unknown = await c.req.json(); return c.json({ received: body }); }); | c.req.json:JSON本文(unknownから実行時検証。大きさ制限・不正JSONの処理も用意) |
| /* validatorまたはzValidatorの後のハンドラー内: */ /* const input=c.req.valid('json'); */ | c.req.valid:検証済み値を取得(TypeScriptの推論には同じルートのmiddleware連結を保つ) |
| app.get('/header', c => { c.header('X-App', 'sample'); return c.text('ok') }); | c.header:応答ヘッダー |
| import { logger } from 'hono/logger'; import { timing } from 'hono/timing'; app.use('*', logger(), timing()); | logger / timing:ログ・計測(ログに秘密・個人情報を含めない) |
| import { cors } from 'hono/cors'; app.use('/api/*', cors({ origin: 'https://app.example.com' })); | cors:CORS(認証・認可の代わりではない) |
| import { secureHeaders } from 'hono/secure-headers'; app.use('*', secureHeaders()); | secureHeaders:ヘッダー保護(ポリシーをアプリに合わせる) |
| import { bodyLimit } from 'hono/body-limit'; app.use('/upload', bodyLimit({ maxSize: 1024 * 1024 })); | bodyLimit:本文サイズ制限(実行環境の上限も別途確認) |
| const response = await app.request('/'); if (response.status !== 200) throw new Error('unexpected status'); | app.request:サーバー起動なしのリクエスト(アプリが定義済み。テストランナーのexpect等とも組合せ可能) |
| import { serve } from '@hono/node-server'; serve({ fetch: app.fetch, port: 3000 }); | Node.js adapter:Nodeで起動(補足: @hono/node-serverを別途導入。実行環境に合う版を使用) |
Honoメソッド(上級)
| 打つ言葉 | 意味・補足 |
|---|---|
| app.post('/form', async c => { const body = await c.req.parseBody(); return c.json({ name: typeof body.name === 'string' ? body.name : '' }); }); | c.req.parseBody / formData:フォーム本文(Content-Type・File等の型を確認) |
| import { validator } from 'hono/validator'; app.post('/users', validator('json', (value, c) => { if (!value || typeof value.name !== 'string') return c.json({ error: 'invalid name' }, 400); return { name: value.name }; }), c => c.json(c.req.valid('json'), 201)); | validator:組込みの薄い検証(JSONには適切なContent-Typeが必要) |
| import { z } from 'zod'; import { zValidator } from '@hono/zod-validator'; app.post( '/users', zValidator('json', z.object({ name: z.string().min(1) })), c => c.json(c.req.valid('json'), 201) ); | zValidator:Zodで検証(zod・@hono/zod-validatorを別途導入。版の互換性を確認) |
| app.use('*', async (c, next) => { console.log(c.req.method, c.req.path); await next(); c.header('X-App', 'sample'); }); | app.use / await next:前後処理(ルートより前に登録。nextをawait) |
| import { createMiddleware } from 'hono/factory'; const log = createMiddleware(async (c, next) => { console.log(c.req.path); await next(); }); app.use('*', log); | createMiddleware:型付きの独立middleware |
| type Env = { Variables: { requestId: string } }; const traced = new Hono<Env>(); traced.use('*', async (c, next) => { c.set('requestId', crypto.randomUUID()); await next() }); traced.get('/', c => c.json({ id: c.get('requestId') })); | c.set / c.get:リクエスト単位の値(対応middlewareを通ることは型宣言だけでは保証されない) |
| type Env = { Bindings: { APP_NAME: string } }; const worker = new Hono<Env>(); worker.get('/', c => c.text(c.env.APP_NAME)); | c.env:環境固有のBindings(Cloudflare Workers等でBindingsを設定。Nodeのprocess.envとは同一でない) |
| import { HTTPException } from 'hono/http-exception'; app.get('/private', () => { throw new HTTPException(401, { message: 'Unauthorized' }) }); | HTTPException:HTTPエラーを投げる(例は常に拒否。実際の認証を別途実装) |
| app.onError((err, c) => { if (err instanceof HTTPException) return err.getResponse(); console.error(err); return c.json({ error: 'internal error' }, 500); }); | app.onError:例外を応答へ変換(HTTPExceptionを別途import。機密情報を返さない) |
| import { getCookie, setCookie, deleteCookie } from 'hono/cookie'; app.post('/theme', c => { setCookie(c, 'theme', 'dark', { httpOnly: true, sameSite: 'Lax', path: '/' }); return c.json({ ok: true }) }); | getCookie / setCookie / deleteCookie:Cookie操作(本番HTTPSでsecure等を設定。認証CookieはCSRF・期限も設計) |
| const routes = app.get('/health', c => c.json({ ok: true })); export type AppType = typeof routes; /* クライアント別ファイル: */ /* import {hc} from 'hono/client'; */ /* const client=hc<AppType>('http://localhost:3000'); */ | hc / AppType:サーバーとクライアントの型共有(ルートを連結して推論を保持。型共有は認証や実行時検証を代替しない) |
| import { serveStatic } from '@hono/node-server/serve-static'; app.use('/static/*', serveStatic({ root: './' })); | serveStatic:Nodeで静的配信(対応ファイルが必要。cwd基準。秘密ファイルを公開しない) |