本文へ移動

Node.js ​

Node.js はオープンソースでクロスプラットフォームの JavaScript ランタイム環境です。

Hono はもともと Node.js 向けに設計されていませんが、Node.js アダプターを使うことで Node.js 上でも動作します。

情報

Node.js 18.x 以降の系列で動作します。必要なバージョンは次のとおりです。

  • 18.x => 18.14.1+
  • 19.x => 19.7.0+
  • 20.x => 20.0.0+

基本的には、各メジャーリリースの最新バージョンを使えば問題ありません。

1. セットアップ ​

Node.js 向けのスターターテンプレートがあります。 「create-hono」コマンドでプロジェクトを開始します。 この例では nodejs テンプレートを選択します。

sh
npm create hono@latest my-app
sh
yarn create hono my-app
sh
pnpm create hono my-app
sh
bun create hono@latest my-app
sh
deno init --npm hono my-app

my-app に移動して依存関係をインストールします。

sh
cd my-app
npm i
sh
cd my-app
yarn
sh
cd my-app
pnpm i
sh
cd my-app
bun i

2. Hello World ​

src/index.ts を編集します。

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

const app = new Hono()
app.get('/', (c) => c.text('Hello Node.js!'))

serve(app)

サーバーをグレースフルに終了するには、次のように記述します。

ts
const server = serve(app)

// graceful shutdown
process.on('SIGINT', () => {
  server.close()
  process.exit(0)
})
process.on('SIGTERM', () => {
  server.close((err) => {
    if (err) {
      console.error(err)
      process.exit(1)
    }
    process.exit(0)
  })
})

情報

Node.js では、serve() が node:http モジュールをラップし、基盤となるサーバーインスタンスを返すため、終了処理は自分で行います。Bun と Deno は Fetch ハンドラーをネイティブに扱い、サーバーを自ら管理するため、それぞれのガイドにはこの手順がありません。

3. 実行 ​

開発サーバーをローカルで起動し、Web ブラウザーで http://localhost:3000 にアクセスします。

sh
npm run dev
sh
yarn dev
sh
pnpm dev

ポート番号の変更 ​

port オプションでポート番号を指定できます。

ts
serve({
  fetch: app.fetch,
  port: 8787,
})

WebSocket ​

@hono/node-server には WebSocket のサポートが組み込まれています。ws をインストールし、TypeScript を使う場合は @types/ws もインストールします。続いて { noServer: true } で WebSocketServer を作成し、websocket オプションで serve() に渡します。

@hono/node-ws は非推奨です。

ts
import { serve, upgradeWebSocket } from '@hono/node-server'
import { Hono } from 'hono'
import { WebSocketServer } from 'ws'

const app = new Hono()

app.get(
  '/ws',
  upgradeWebSocket(() => ({
    onMessage(event, ws) {
      ws.send(event.data)
    },
  }))
)

const wss = new WebSocketServer({ noServer: true })

serve({
  fetch: app.fetch,
  websocket: { server: wss },
})

ネイティブの Node.js API へのアクセス ​

c.env.incoming と c.env.outgoing から Node.js の API にアクセスできます。

ts
import { Hono } from 'hono'
import { serve, type HttpBindings } from '@hono/node-server'
// or `Http2Bindings` if you use HTTP2

type Bindings = HttpBindings & {
  /* ... */
}

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

app.get('/', (c) => {
  return c.json({
    remoteAddress: c.env.incoming.socket.remoteAddress,
  })
})

serve(app)

静的ファイルの配信 ​

serveStatic を使ってローカルファイルシステムの静的ファイルを配信できます。たとえば、ディレクトリ構成が次のようになっているとします。

sh
./
├── favicon.ico
├── index.ts
└── static
    ├── hello.txt
    └── image.png

/static/* へのリクエストに対して ./static 内のファイルを返すには、次のように記述します。

ts
import { serveStatic } from '@hono/node-server/serve-static'

app.use('/static/*', serveStatic({ root: './' }))

注意

root オプションは、現在の作業ディレクトリ(process.cwd())を基準にパスを解決します。つまり、動作はソースファイルの場所ではなく、Node.js プロセスをどのディレクトリから起動するかに依存します。別のディレクトリからサーバーを起動すると、ファイルの解決に失敗する場合があります。

常にソースファイルと同じディレクトリを指す、確実なパス解決には import.meta.url を使います。

ts
import { fileURLToPath } from 'node:url'
import { serveStatic } from '@hono/node-server/serve-static'

app.use(
  '/static/*',
  serveStatic({ root: fileURLToPath(new URL('./', import.meta.url)) })
)

ルートディレクトリの favicon.ico を配信するには、path オプションを使います。

ts
app.use('/favicon.ico', serveStatic({ path: './favicon.ico' }))

/hello.txt または /image.png へのリクエストに対して、./static/hello.txt または ./static/image.png というファイルを返すには、次の方法を使えます。

ts
app.use('*', serveStatic({ root: './static' }))

rewriteRequestPath ​

http://localhost:3000/static/* を ./statics に対応させるには、rewriteRequestPath オプションを使います。

ts
app.get(
  '/static/*',
  serveStatic({
    root: './',
    rewriteRequestPath: (path) =>
      path.replace(/^\/static/, '/statics'),
  })
)

http2 ​

Node.js http2 サーバー上で Hono を実行できます。

暗号化されていない http2 ​

ts
import { createServer } from 'node:http2'

const server = serve({
  fetch: app.fetch,
  createServer,
})

暗号化された http2 ​

ts
import { createSecureServer } from 'node:http2'
import { readFileSync } from 'node:fs'

const server = serve({
  fetch: app.fetch,
  createServer: createSecureServer,
  serverOptions: {
    key: readFileSync('localhost-privkey.pem'),
    cert: readFileSync('localhost-cert.pem'),
  },
})

ビルドとデプロイ ​

sh
npm run build
sh
yarn run build
sh
pnpm run build
sh
bun run build

情報

フロントエンドフレームワークを使うアプリでは、Hono の Vite プラグインが必要になる場合があります。

Dockerfile ​

以下は Node.js の Dockerfile の例です。

Dockerfile
FROM node:22-alpine AS base

FROM base AS builder

RUN apk add --no-cache gcompat
WORKDIR /app

COPY package*json tsconfig.json src ./

RUN npm ci && \
    npm run build && \
    npm prune --production

FROM base AS runner
WORKDIR /app

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 hono

COPY --from=builder --chown=hono:nodejs /app/node_modules /app/node_modules
COPY --from=builder --chown=hono:nodejs /app/dist /app/dist
COPY --from=builder --chown=hono:nodejs /app/package.json /app/package.json

USER hono
EXPOSE 3000

CMD ["node", "/app/dist/index.js"]

MIT ライセンスで公開されています。