Skip to content

Node.js

Node.js is an open-source, cross-platform JavaScript runtime environment.

Hono was not designed for Node.js at first. But with a Node.js Adapter it can run on Node.js as well.

INFO

It works on Node.js versions greater than 18.x. The specific required Node.js versions are as follows:

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

Essentially, you can simply use the latest version of each major release.

1. Setup

A starter for Node.js is available. Start your project with "create-hono" command.

txt
npm create hono@latest my-app
npm create hono@latest my-app
txt
yarn create hono my-app
yarn create hono my-app
txt
pnpm create hono my-app
pnpm create hono my-app
txt
bunx create-hono my-app
bunx create-hono my-app
txt
deno run -A npm:create-hono my-app
deno run -A npm:create-hono my-app

Move to my-app and install the dependencies.

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

2. Hello World

Edit 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)
import { serve } from '@hono/node-server'
import { Hono } from 'hono'

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

serve(app)

3. Run

Run the development server locally. Then, access http://localhost:3000 in your Web browser.

txt
npm run dev
npm run dev
txt
yarn dev
yarn dev
txt
pnpm dev
pnpm dev

Change port number

You can specify the port number with the port option.

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

Access the raw Node.js APIs

You can access the Node.js APIs from c.env.incoming and c.env.outgoing.

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)
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)

Serve static files

You can use serveStatic to serve static files from the local file system.

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

app.use('/static/*', serveStatic({ root: './' }))
import { serveStatic } from '@hono/node-server/serve-static'

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

rewriteRequestPath

If you want to map http://localhost:3000/static/* to ./statics, you can use the rewriteRequestPath option:

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

mimes

You can add MIME types with mimes:

ts
app.get(
  '/static/*',
  serveStatic({
    mimes: {
      m3u8: 'application/vnd.apple.mpegurl',
      ts: 'video/mp2t',
    }
  })
)
app.get(
  '/static/*',
  serveStatic({
    mimes: {
      m3u8: 'application/vnd.apple.mpegurl',
      ts: 'video/mp2t',
    }
  })
)

http2

You can run hono on a Node.js http2 Server.

unencrypted http2

ts
import { createServer } from 'node:http2'

const server = serve({
  fetch: app.fetch
  createServer,
})
import { createServer } from 'node:http2'

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

encrypted 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'),
  },
})
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'),
  },
})

Dockerfile

Here is an example of a Dockerfile.

Dockerfile
FROM node:20-alpine AS base

FROM base AS builder

RUN apk add --no-cache libc6-compat
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

USER hono
EXPOSE 3000

CMD ["node", "/app/dist/index.js"]
FROM node:20-alpine AS base

FROM base AS builder

RUN apk add --no-cache libc6-compat
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

USER hono
EXPOSE 3000

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

The following steps shall be taken in advance.

  1. Add "outDir": ". /dist" to the compilerOptions section tsconfig.json.
  2. Add "exclude": ["node_modules"] to tsconfig.json.
  3. Add "build": "tsc" to script section of package.json.
  4. Run npm install typescript --save-dev.

Released under the MIT License.