0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HonoのマージされたPRを読んでいく#61 - #65

0
Last updated at Posted at 2025-12-12

この記事何?

エンジニア歴1年半。業務で利用しているHonoが大好きだが、TSもプログラミング知識も弱々すぎて上手く使いこなせない。速いはずが多分生かせてない。
ちゃんと理解するためにソースコードリーディングしたい。

でも目的なくソースコード眺めても意味ないし……ねむい……
そうだ!PRを全部読めば、変遷やなぜ変更されているかストーリー的に分かるのでは。
と思ったのでチャレンジしてみる。PR2000以上あるので、全部できるかは知らん。
https://github.com/honojs/hono

61 chore: Add serve static example

静的ファイル配信のサンプルを追加。

  // examples/serve-static/src/index.ts
 import { Hono } from 'hono'

 const app = new Hono()

 // 静的ファイルを配信(KV Sitesを使用)
 app.get('/static/*', async (c) => {
   const path = c.req.path.replace(/^\/static/, '')
   const object = await c.env.ASSETS.get(path)

   if (!object) {
     return c.text('Not Found', 404)
   }

   return new Response(object.body, {
     headers: {
       'Content-Type': object.httpMetadata.contentType,
     },
   })
 })

 export default app
static/hello.txt:
=> Hello from static file!

#62 feat: Add new shortcuts for request/response

ショートカットメソッドを追加して、よりシンプルなコードが書けるように修正

追加されたショートカット

  1. Context ヘッダー設定のショートカット
  // 変更前
  app.get('/', (c) => {
    c.res.headers.set('X-Custom', 'value')
    return c.text('Hello')
  })

  // 変更後
  app.get('/', (c) => {
    c.header('X-Custom', 'value')  // ショートカット
    return c.text('Hello')
  })

2.ステータスコードとステータステキストの設定

  app.get('/', (c) => {
    c.status(201)  // ステータスコード設定
    c.statusText('Created!!!!')  // ステータステキスト設定
    return c.body('Resource created')
  })

3.c.body() メソッド

  app.get('/', (c) => {
    return c.body('Hello')  // シンプルなレスポンス
  })

実装

  // src/context.ts
  export class Context {
    private _headers: Headers = {}
    private _status: number
    private _statusText: string

    header(name: string, value: string): void {
      this._headers[name] = value
    }

    status(number: number): void {
      this._status = number
    }

    statusText(text: string): void {
      this._statusText = text
    }

    body(body: BodyInit, init?: ResponseInit): Response {
      return this.newResponse(body, init)
    }

    newResponse(body: BodyInit, init?: ResponseInit): Response {
      return new Response(body, {
        status: this._status || init?.status || 200,
        statusText: this._statusText || init?.statusText,
        headers: { ...init?.headers, ...this._headers },
      })
    }
  }

#63 refactor(equal): user Expect.toStrictEqual to simplify value comparison

テストコードのリファクタリング

  // 変更前
  expect(res.param).toEqual({ id: '123' })

  // 変更後
  expect(res.param).toStrictEqual({ id: '123' })

toStrictEqual の利点:

  • より厳密な比較(undefined プロパティも検証)
  • より安全なテスト

#64 feat: Cookie middleware

Cookie ミドルウェアを追加。

使用方法

  import { Hono, Middleware } from 'hono'

  const app = new Hono()

  app.use('*', Middleware.cookie())

  app.get('/', (c) => {
    // Cookieを読み取る
    const sessionId = c.req.cookie('session_id')

    // Cookieを設定
    c.cookie('user', 'john', {
      maxAge: 60 * 60 * 24,  // 1日
      httpOnly: true,
      secure: true,
      sameSite: 'Lax',
    })

    return c.text('Hello')
  })

CookieOptions

  type CookieOptions = {
    domain?: string      // Cookie有効ドメイン
    expires?: Date       // 有効期限
    httpOnly?: boolean   // JavaScriptからアクセス不可
    maxAge?: number      // 有効期限(秒)
    path?: string        // Cookie有効パス
    secure?: boolean     // HTTPS のみ
    sameSite?: 'Strict' | 'Lax' | 'None'  // CSRF対策
  }

実装

  // Cookieをパース
  const parse = (cookie: string): Cookie => {
    const pairs = cookie.split(/;\s*/g)
    const parsedCookie: Cookie = {}
    for (let i = 0, len = pairs.length; i < len; i++) {
      const pair = pairs[i].split(/\s*=\s*([^\s]+)/)
      parsedCookie[pair[0]] = decodeURIComponent(pair[1])
    }
    return parsedCookie
  }

  // Cookieをシリアライズ
  // 例:Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secureのような文字列を作る
  const serialize = (name: string, value: string, opt?: CookieOptions): string => {
    // 特殊文字をエンコード 例:'john@example.com' → 'john%40example.com'
    let cookie = `${name}=${encodeURIComponent(value)}`

    if (opt?.maxAge) {
      cookie += `; Max-Age=${opt.maxAge}`
    }
    if (opt?.httpOnly) {
      cookie += '; HttpOnly'
    }
    if (opt?.secure) {
      cookie += '; Secure'
    }
    if (opt?.sameSite) {
      cookie += `; SameSite=${opt.sameSite}`
    }

    return cookie
  }

#65 refactor: refactor something

プロジェクト構造などのリファクタリング。

cookieの処理の中身などが知れてよかった。今回はここまで。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?