Cloudflare Workers ワークショップ

アジェンダ

  1. 自己紹介
  2. Workers イントロダクション
  3. Hono イントロダクション
  4. 基本編
  5. プロキシ編
  6. Web API編
  7. フルスタック編
  8. AI編
  9. Honoをより深く知る
  10. その他

1. 自己紹介

自己紹介

Developer Relations

DevRel

やってること

  1. Honoの開発
  2. イベント開催
  3. イベント登壇
  4. その他

Workers Tech Talks

Cloudflare Workersとその周辺のプロダクトを使う開発者、ライブラリ作者による開発者のためのWorkersに特化したテックトークです。

Workers Tech Talks in Tokyo #5

Workers Tech Talks in Tokyo 5

2. Workers イントロダクション

2.1 開発者プラットフォーム

Dev Platform

何ができるのか?

Webアプリケーションの構築

Webapp

CDNで実行されるアプリケーション

CDN

特徴

特徴

Cloudflareの全プロダクト

プロダクト

開発者プラットフォームのプロダクト

プロダクト

https://developers.cloudflare.com/products/?product-group=Developer+platform

2.2 Cloudflare Workers

Build serverless applications and deploy instantly across the globe for exceptional performance, reliability, and scale.

サーバーレスアプリケーションを構築し、卓越したパフォーマンス、信頼性、スケールのために世界中に即座にデプロイします。

https://developers.cloudflare.com/workers/

Cloudflare Workersの思想

SS

v8

Wrangler

SS

2.3 Bindings

CloudflareのプロダクトとWorkersを結ぶ

以下プロダクトの説明

KV

Durable Objects

R2

D1

Service Bindings

Queue

Email

2.4 Workersを知るために

リソース

3. Hono イントロダクション

3.1 Honoとは?

副題

Fast, lightweight, built on Web Standards. Support for any JavaScript runtime.

3.2 ユースケース

3.3 どこで使われているか?

Who is using Hono in production?

3.4 5つの特徴

3.5 速い

RegExpRouter

SS

SS

TriRouter

SS

SmartRouter

readonly defaultRouter: Router = new SmartRouter({
  routers: [new RegExpRouter(), new TrieRouter()],
})

LinearRouter

• GET /user/lookup/username/hey
----------------------------------------------------- -----------------------------
LinearRouter     1.82 µs/iter      (1.7 µs … 2.04 µs)   1.84 µs   2.04 µs   2.04 µs
MedleyRouter     4.44 µs/iter     (4.34 µs … 4.54 µs)   4.48 µs   4.54 µs   4.54 µs
FindMyWay       60.36 µs/iter      (45.5 µs … 1.9 ms)  59.88 µs  78.13 µs  82.92 µs
KoaTreeRouter    3.81 µs/iter     (3.73 µs … 3.87 µs)   3.84 µs   3.87 µs   3.87 µs
TrekRouter       5.84 µs/iter     (5.75 µs … 6.04 µs)   5.86 µs   6.04 µs   6.04 µs

summary for GET /user/lookup/username/hey
  LinearRouter
   2.1x faster than KoaTreeRouter
   2.45x faster than MedleyRouter
   3.21x faster than TrekRouter
   33.24x faster than FindMyWay

PatternRouter

SS

ベンチマーク

対象のルーター

結果

• all together
---------------------------------------------------------------------------- -----------------------------
Hono RegExpRouter                      460.6 ns/iter (429.69 ns … 525.88 ns)  479.5 ns 520.63 ns 525.88 ns
Hono TrieRouter                         1.52 µs/iter     (1.39 µs … 1.73 µs)   1.56 µs   1.73 µs   1.73 µs
@medley/router                        618.21 ns/iter (591.08 ns … 764.25 ns) 636.08 ns 764.25 ns 764.25 ns
find-my-way                           959.15 ns/iter   (892.79 ns … 1.02 µs) 979.02 ns   1.02 µs   1.02 µs
koa-tree-router                       926.79 ns/iter   (866.17 ns … 1.04 µs) 943.57 ns   1.04 µs   1.04 µs
trek-router                             1.76 µs/iter     (1.69 µs … 1.84 µs)   1.79 µs   1.84 µs   1.84 µs
express (WARNING: includes handling)    4.02 µs/iter      (3.9 µs … 4.33 µs)   4.06 µs   4.33 µs   4.33 µs
koa-router                              1.39 µs/iter     (1.34 µs … 1.57 µs)   1.41 µs   1.57 µs   1.57 µs
radix3                                763.26 ns/iter (734.77 ns … 902.52 ns)  781.2 ns 902.52 ns 902.52 ns
Memoirist                             540.11 ns/iter (507.38 ns … 699.65 ns) 554.52 ns 697.25 ns 699.65 ns

summary for all together
  Hono RegExpRouter
   1.17x faster than Memoirist
   1.34x faster than @medley/router
   1.66x faster than radix3
   2.01x faster than koa-tree-router
   2.08x faster than find-my-way
   3.02x faster than koa-router
   3.3x faster than Hono TrieRouter
   3.82x faster than trek-router
   8.74x faster than express (WARNING: includes handling)

@usualomaさん

RegExpRouter, SmartRouter, LinearRouter, PatternRouterは @usualomaさん が作りました。 @usualomaさんが発表した資料が参考になります。

SS

3.6 軽量

プリセット

あらかじめ、おすすめのルーターのセッティングをプリセットとして提供している。

  • hono: ほとんどのユースケースでオススメです。ルーティング登録がhono/quickより遅いとはいえ、一度登録されれば高いパフォーマンスを発揮します。DenoBun、それにNode.jsなどを使った常駐型のサーバーには最適です。また、Cloudflare WorkersDeno Deployでもこのプリセットを使えばいいでしょう。というのもこれらのようなv8 isolateを使った環境では、isolateは起動後しばらく行き続けるからです(時間が決まっていたり、メモリなどの状況に応じて変化したりします)。
  • hono/quick: このプリセットはリクエストのたびにアプリケーションが初期化されるような環境に適しています。
  • hono/tiny: このプリセットは一番ファイルサイズの小さいプリセットです。リソースが限られている環境にはいいでしょう。

3.7 どこでも動く

Web Standard APIs

最低限のコード

export default {
  async fetch() {
    return new Response('Hello World')
  },
}

よく使うAPI

スターターのテンプレートは13種類

  1. aws-lambda
  2. bun
  3. cloudflare-pages
  4. cloudflare-workers
  5. cloudflare-workers+vite
  6. deno
  7. fastly
  8. lambda-edge
  9. netlify
  10. nextjs
  11. nodejs
  12. vercel
  13. x-basic

CIでは9種類のランタイムのテストが走る

CI

エントリポイントが違うだけ

Cloudflare Workers & Bun & Vercel

import { Hono } from 'hono'
const app = new Hono()

app.get('/', (c) => c.text('Hello Hono!'))

export default app

Fastly Compute

import { Hono } from 'hono'
import { fire } from '@fastly/hono-fastly-compute'

const app = new Hono()

app.get('/', (c) => c.text('Hello Fastly!'))

fire(app)

Deno

import { Hono } from '@hono/hono'

const app = new Hono()

app.get('/', (c) => c.text('Hello Hono!'))

Deno.serve(app.fetch)

Netlify

import { Hono } from 'jsr:@hono/hono'
import { handle } from 'jsr:@hono/hono/netlify'

const app = new Hono()

app.get('/', (c) => {
  return c.text('Hello Hono!')
})

export default handle(app)

AWS Lambda

import { Hono } from 'hono'
import { handle } from 'hono/aws-lambda'

const app = new Hono()

app.get('/', (c) => c.text('Hello Hono!'))

export const handler = handle(app)

Lambda@Edge

import { Hono } from 'hono'
import { handle } from 'hono/lambda-edge'

const app = new Hono()

app.get('/', (c) => c.text('Hello Hono!'))

export const handler = handle(app)

Node.js

import { serve } from '@hono/node-server'
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => c.text('Hello Hono!'))

serve(app)

3.8 揃っている

コアは小さいが、ミドルウェアとヘルパーがある。

ミドルウェア

onion

3つのミドルウェア

ミドルウェアとヘルパーの例

カスタムミドルウェア

app.use('*', async (c, next) => {
  const start = Date.now()
  await next()
  const end = Date.now()
  c.res.headers.set('X-Response-Time', `${end - start}`)
})

JSX

import type { PropsWithChildren } from 'hono/jsx'

const app = new Hono()

const Layout = (props: PropsWithChildren) => {
  return (
    <html>
      <body>{props.children}</body>
    </html>
  )
}

const Top = (props: PropsWithChildren<{ messages: string[] }>) => {
  return (
    <Layout>
      <h1>Hello Hono!</h1>
      <ul>
        {props.messages.map((message) => {
          return <li>{message}!!</li>
        })}
      </ul>
    </Layout>
  )
}

app.get('/', (c) => {
  const messages = ['Good Morning', 'Good Evening', 'Good Night']
  return c.html(<Top messages={messages} />)
})

3.8 楽しいDX

TypeScript

SS

RPC

APIを書く

import { Hono } from 'hono'

const app = new Hono()

app.get('/hello', (c) => {
  return c.json({
    message: `Hello!`,
  })
})

Zodでバリデーションをする

SC

import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

app.get(
  '/hello',
  zValidator(
    'query',
    z.object({
      name: z.string(),
    })
  ),
  (c) => {
    const { name } = c.req.valid('query')
    return c.json({
      message: `Hello! ${name}`,
    })
  }
)

型を共有する

const route = app.get(
  '/hello',
  zValidator(
    'query',
    z.object({
      name: z.string(),
    })
  ),
  (c) => {
    const { name } = c.req.valid('query')
    return c.json({
      message: `Hello! ${name}`,
    })
  }
)

export type AppType = typeof route

クライアントの実装

SC

import { AppType } from './server'
import { hc } from 'hono/client'

const client = hc<AppType>('/api')
const res = await client.hello.$get({
  query: {
    name: 'Hono',
  },
})

SC

const data = await res.json()
console.log(`${data.message}`)

SS

テスト

describe('Example', () => {
  test('GET /posts', async () => {
    const res = await app.request('/posts')
    expect(res.status).toBe(200)
    expect(await res.text()).toBe('Many posts')
  })
})

output

import { testClient } from 'hono/testing'

it('test', async () => {
  const app = new Hono().get('/search', (c) =>
    c.json({ hello: 'world' })
  )
  const res = await testClient(app).search.$get()

  expect(await res.json()).toEqual({ hello: 'world' })
})

3.10 Cloudflare + Hono を使ったアプリ例

r2-image-worker

SC

4. 基本編

4.1 初めてのCloudflare Workers

C3を使う

npm create cloudflare@latest

Or

yarn create cloudflare

Or

pnpm create cloudflare@latest

Or

bun create cloudflare

"Hello World" Worker

package.json

{
  "name": "01-hello-world",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "deploy": "wrangler deploy",
    "dev": "wrangler dev",
    "cf-typegen": "wrangler types"
  }
}

src/index.ts

export default {
  async fetch(request, env, ctx): Promise<Response> {
    return new Response('Hello World!')
  },
} satisfies ExportedHandler<Env>

fetchの引数

Workersアプリの流れ

  1. Requestを受け取る
  2. ロジック
  3. Responseを作る
  4. Responseを返す

waitUntil()

export default {
  async fetch(request, env, ctx): Promise<Response> {
    const log = async () => console.log('Foo')
    ctx.waitUntil(log())
    return new Response('Hello World!')
  },
} satisfies ExportedHandler<Env>

4.2 初めてのHono

create hono

npm create hono@latest

Or

yarn create hono

Or

pnpm create hono@latest

Or

bun create hono

レスポンスを返す

app.get('/', (c) => {
  return c.text('Hello!')
})

app.get('/json', (c) => {
  return c.json({
    message: 'Hello!',
  })
})

app.get('/html', (c) => {
  return c.json({
    message: '<h1>Hello!</h1>',
  })
})

app.get('/stream', (c) => {
  return c.streamText(async (stream) => {
    stream.sleep(1000)
    stream.writeln('Hello!')
  })
})

Contextを通してレスポンスを作る

app.get('/welcome', (c) => {
  // Set headers
  c.header('X-Message', 'Hello!')
  c.header('Content-Type', 'text/plain')

  // Set HTTP status code
  c.status(201)

  // Return the response body
  return c.body('Thank you for coming')
})

以下と同じ

new Response('Thank you for coming', {
  status: 201,
  headers: {
    'X-Message': 'Hello',
    'Content-Type': 'text/plain',
  },
})

その他

ルーティング

// HTTP Methods
app.get('/', (c) => c.text('GET /'))
app.post('/', (c) => c.text('POST /'))
app.put('/', (c) => c.text('PUT /'))
app.delete('/', (c) => c.text('DELETE /'))

// Wildcard
app.get('/wild/*/card', (c) => {
  return c.text('GET /wild/*/card')
})

// Any HTTP methods
app.all('/hello', (c) => c.text('Any Method /hello'))

// Custom HTTP method
app.on('PURGE', '/cache', (c) => c.text('PURGE Method /cache'))

// Multiple Method
app.on(['PUT', 'DELETE'], '/post', (c) => c.text('PUT or DELETE /post'))

// Path parameters
app.get('/user/:name', (c) => {
  const name = c.req.param('name')
  ...
})

// Optional parameters
// Will match `/api/animal` and `/api/animal/:type`
app.get('/api/animal/:type?', (c) => c.text('Animal!'))

// Regexp
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
  const { date, title } = c.req.param()
  ...
})

// Including a slash
app.get('/posts/:filename{.+.png$}', (c) => {
  //...
})

// Chained routes
app
  .get('/endpoint', (c) => {
    return c.text('GET /endpoint')
  })
  .post((c) => {
    return c.text('POST /endpoint')
  })
  .delete((c) => {
    return c.text('DELETE /endpoint')
  })

ルートを分ける

const book = new Hono()

book.get('/', (c) => c.text('List Books')) // GET /book
book.get('/:id', (c) => {
  // GET /book/:id
  const id = c.req.param('id')
  return c.text('Get Book: ' + id)
})
book.post('/', (c) => c.text('Create Book')) // POST /book

const app = new Hono()
app.route('/book', book)

ホスト名でのルーティング

const app = new Hono({
  getPath: (req) => req.url.replace(/^https?:\/(.+?)$/, '$1'),
})

app.get('/www1.example.com/hello', (c) => c.text('hello www1'))
app.get('/www2.example.com/hello', (c) => c.text('hello www2'))

Context

Bindingsの取得

// Environment object for Cloudflare Workers
app.get('*', async c => {
  const counter = c.env.COUNTER
  ...
})

c.set() / c.get()

app.use('*', async (c, next) => {
  c.set('message', 'Hono is cool!!')
  await next()
})

app.get('/', (c) => {
  const message = c.get('message')
  return c.text(`The message is "${message}"`)
})

Variableappへ渡すことで型がつく

type Variables = {
  message: string
}

const app = new Hono<{ Variables: Variables }>()

c.var

c.set()でセットした変数へアクセスできる

const result = c.var.client.oneMethod()

その他

HonoRequest

5. プロキシ編

Cloudflare Workersプロキシパターンをやる

レスポンスヘッダの追加

import { Hono } from 'hono'

const app = new Hono()

app.all('*', async (c) => {
  const res = await fetch(c.req.raw)
  const newResponse = new Response(res.body, res)
  newResponse.headers.set('X-Custom', 'Foo')
  return newResponse
})

export default app

CORS

app.use('/api/*', cors())
app.use(
  '/api2/*',
  cors({
    origin: 'http://example.com',
    allowHeaders: ['X-Custom-Header', 'Upgrade-Insecure-Requests'],
    allowMethods: ['POST', 'GET', 'OPTIONS'],
    exposeHeaders: ['Content-Length', 'X-Kuma-Revision'],
    maxAge: 600,
    credentials: true,
  })
)

Basic認証

app.use(
  '/auth/*',
  basicAuth({
    username: 'yourname',
    password: 'yoursecret',
  })
)

CloudflareのBindingsの変数を使う場合

app.use('/auth/*', async (c, next) => {
  const auth = basicAuth({
    username: c.env.USERNAME,
    password: c.env.PASSWORD,
  })
  return auth(c, next)
})

認証は以下のミドルウェアがある

リダイレクト

app.get('/old/:id', async (c) => {
  const id = c.req.param('id')
  return c.redirect(`/new/${id}`)
})

オリジンの振り分け

const imageHost = 'http://imagehost'
const fontHost = 'http://fonthost'

app.get('/assets/:type{(?:images|fonts)}/:filename', async (c) => {
  const { type, filename } = c.req.param()
  const hostName = type === 'images' ? imageHost : fontHost
  const url = new URL(`/${filename}`, hostName)
  return fetch(url)
})

キャッシュ

app.get(
  '/assets/*',
  cache({
    cacheName: 'my-app',
  })
)

デバイス別の挙動変更

app.get('/pages/*', async (c) => {
  let isMobile = false
  const userAgent = c.req.header('User-Agent') || ''

  if (userAgent.match(/(iPhone|iPod|Android|Mobile)/)) {
    isMobile = true
  }

  const cache = caches.default

  const device = isMobile ? 'Mobile' : 'Desktop'
  const cacheKey = c.req.url + '-' + device

  let response = await cache.match(cacheKey)

  if (!response) {
    response = await fetch(c.req.raw)
    response = new Response(response.body, response)
    response.headers.append('Cache-Control', 's-maxage=3600')
    c.executionCtx.waitUntil(cache.put(cacheKey, response.clone()))
  }

  return response
})

HTMLタグの置換

HTMLRewriterを使う

app.get('/pages/*', async (c) => {
  const OLD_URL = 'oldhost'
  const NEW_URL = 'newhost'

  class AttributeRewriter {
    constructor(attributeName) {
      this.attributeName = attributeName
    }
    element(element) {
      const attribute = element.getAttribute(this.attributeName)
      if (attribute) {
        element.setAttribute(
          this.attributeName,
          attribute.replace(OLD_URL, NEW_URL)
        )
      }
    }
  }

  const rewriter = new HTMLRewriter().on(
    'a',
    new AttributeRewriter('href')
  )

  const res = await fetch(c.req.raw)
  const contentType = res.headers.get('Content-Type')

  if (contentType.startsWith('text/html')) {
    return rewriter.transform(res)
  } else {
    return res
  }
})

その他

6. Web API編

以下基本をやる

6.1 Blog APIをつくってみよう

D1の設定

npx wrangler d1 create blog
npx wrangler d1 execute blog --local --file ./blog.sql
npx wrangler d1 execute blog --local --command "INSERT INTO posts(id,title,content) VALUES('1','Hello','Nice day!')"
npx wrangler d1 execute blog --local --command "SELECT * FROM posts"

wrangler.jsonc

{
  "name": "05-web-api",
  "main": "src/index.ts",
  "compatibility_date": "2025-04-16",
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "blog",
      "database_id": "xxxxxxxxxxxxxxxxxxxxxxx"
    }
  ]
}
CREATE TABLE posts (
  id TEXT PRIMARY KEY,
  created_at TEXT DEFAULT (datetime('now')),
  title TEXT,
  content TEXT
);
npm i zod @hono/zod-validator

共通で使う型

import { z } from 'zod'

export const schema = z.object({
  title: z.string().min(1),
  content: z.string().min(1),
})

type Post = z.infer<typeof schema> & {
  created_at: string
  id: string
}

GET /postsの実装

const { results } = await c.env.DB.prepare(
  'SELECT * FROM posts ORDER BY created_at DESC;'
).all<Post>()

POST /postsの実装

Zod Validatorを使う

import { zValidator } from '@hono/zod-validator'
// ...
app.post('/posts', zValidator('form', schema), async (c) => {
  const { title, content } = c.req.valid('form')
  // ...
})

UUIDの生成

const id = crypto.randomUUID()

Insertの実行

const { success } = await c.env.DB.prepare(
  'INSERT INTO posts(id, title, content) values (?, ?, ?)'
)
  .bind(id, title, content)
  .run()

デプロイ

npm run deploy

実際はWranglerを実行している

wrangler deploy --minify src/index.ts

7. フルスタック編

7.1 BlogのUIもつくってみよう

完成形

SS

Rendererを定義する

import { jsxRenderer } from 'hono/jsx-renderer'

export const renderer = jsxRenderer(({ children }) => {
  return (
    <html lang='en'>
      <head>
        <meta
          name='viewport'
          content='width=device-width, initial-scale=1'
        />
        <link
          rel='stylesheet'
          href='https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css'
        />
      </head>
      <body>
        <main class='container'>
          <h1>
            <a href='/'>Blog!</a>
          </h1>
          {children}
          <div style='margin-top:2rem'>
            <small>© 2025 your name</small>
          </div>
        </main>
      </body>
    </html>
  )
})

Rendererを使う

import { Hono } from 'hono'
import { renderer } from './renderer'

const app = new Hono<{ Bindings: CloudflareBindings }>()

app.use(renderer)

一覧をHTMLで表示する

app.get('/', async (c) => {
  const { results: posts } = await c.env.DB.prepare(
    'SELECT * FROM posts ORDER BY created_at DESC;'
  ).all<Post>()
  return c.render(
    <div>
      <title>Blog</title>
      <form action='/' method='post'>
        <div>
          <label>Title</label>
          <input type='text' name='title' />
        </div>
        <div>
          <label>Content</label>
          <textarea name='content'></textarea>
        </div>
        <button type='submit'>Submit</button>
      </form>
      {posts.map((post) => {
        return (
          <article>
            <h2>{post.title}</h2>
            <p>{post.content}</p>
          </article>
        )
      })}
    </div>
  )
})

記事を作る

app.post(
  '/',
  zValidator('form', schema, (result, c) => {
    if (!result.success) {
      console.error(result.error.message)
      return c.redirect('/')
    }
  }),
  async (c) => {
    const id = crypto.randomUUID()
    const { title, content } = c.req.valid('form')

    await c.env.DB.prepare(
      'INSERT INTO posts(id, title, content) values (?, ?, ?)'
    )
      .bind(id, title, content)
      .run()

    return c.redirect('/')
  }
)

8. AI編

8.1 リモートMCPサーバーをつくってみよう

@hono/mcpを使う

import { Hono } from 'hono'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StreamableHTTPTransport } from '@hono/mcp'

const app = new Hono<{ Bindings: CloudflareBindings }>()

app.all('/mcp', async (c) => {
  const transport = new StreamableHTTPTransport()

  const mcpServer = new McpServer({
    name: 'my-blog-mcp-server',
    version: '1.0.0',
  })

  await mcpServer.connect(transport)
  return transport.handleRequest(c)
})

export default app

ツールの定義

app.all('/mcp', async (c) => {
  const transport = new StreamableHTTPTransport()

  const mcpServer = new McpServer({
    name: 'my-blog-mcp-server',
    version: '1.0.0',
  })

  mcpServer.tool('get_posts', async () => {
    const { results: posts } = await c.env.DB.prepare(
      'SELECT * FROM posts ORDER BY created_at DESC;'
    ).all<Post>()
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(posts),
        },
      ],
    }
  })

  mcpServer.tool(
    'create_post',
    { title: z.string(), content: z.string() },
    async ({ title, content }) => {
      const id = crypto.randomUUID()
      const { success } = await c.env.DB.prepare(
        'INSERT INTO posts(id, title, content) values (?, ?, ?)'
      )
        .bind(id, title, content)
        .run()
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({ success }),
          },
        ],
      }
    }
  )

  await mcpServer.connect(transport)
  return transport.handleRequest(c)
})

8.2 Workers AIを使ってみよう

8.3 Agents

そもそもCloudflareでAIをやる意味

8.4 Sandbox SDK

import { getSandbox } from '@cloudflare/sandbox'

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const sandbox = getSandbox(env.Sandbox, 'user-123')

    // Execute a command and get the result
    const result = await sandbox.exec('python --version')

    return Response.json({
      output: result.stdout,
      exitCode: result.exitCode,
      success: result.success,
    })
  },
}

8.5 x402

デモ

9. Honoをより深く使う

9.1 他のランタイムで動かしてみよう

  1. aws-lambda
  2. bun
  3. cloudflare-pages
  4. cloudflare-workers
  5. deno
  6. fastly
  7. lagon
  8. lambda-edge
  9. netlify
  10. nextjs
  11. nodejs
  12. vercel

9.2 大きなアプリケーションの構成

9.3 アダプトとマウント

app.mount()

// Create itty-router application
const ittyRouter = IttyRouter()

// Handle `GET /itty-router/hello`
ittyRouter.get(
  '/hello',
  () => new Response('Hello from itty-router!')
)

// Hono application
const app = new Hono()

// Hono application
app.mount('/itty-router', ittyRouter.handle)

Honoはランタイムにアダプトし、フレームワークをマウントする

SS

フレームワークAは各ランタイムへのアダプターを作りがち

SS

フレームワークAを各種ランタイムに対応させたければ、Hono上で動けばよい

SS

マウントすることでHonoのミドルウェアが使える

app.use('/another-app/admin/*', basicAuth({ username, password }))

10. その他

おまけ. この資料もWorkersでできてる

リンク集