Node Server
@miiajs/node-server provides a high-performance Node.js HTTP server adapter for MiiaJS.
Installation
bun add @miiajs/node-server
npm install @miiajs/node-server
pnpm add @miiajs/node-server
yarn add @miiajs/node-server
Usage
import { Miia } from '@miiajs/core'
import { serve } from '@miiajs/node-server'
const app = new Miia().register(AppModule)
await app.listen(3000, serve)
With custom hostname:
await app.listen(3000, 'localhost', serve)
How it works
The serve function bridges Node.js's http.IncomingMessage/http.ServerResponse and the Web Standard Request/Response API that MiiaJS uses internally.
Performance modes
The adapter supports two modes:
Optimized mode (default)
Minimizes allocations on the hot path:
- Lazy Request Proxy - lightweight object instead of
new Request(). Method, URL, headers, and body are resolved only on first access. - Lightweight Headers - linear scan over raw header pairs instead of constructing a
Headersobject. Covers.get(),.has(),.forEach(), and iterators. - Body Buffering - small POST bodies (Content-Length ≤
bufferThreshold) are buffered and parsed directly, bypassingReadableStreamandRequestcreation. Large or chunked bodies fall back to streaming viaReadable.toWeb(). - LightResponse Cache - simple responses (string, null, Uint8Array) store a
[status, body, headers]tuple without creating a realResponseobject. - Sync Fast Path - synchronous handlers bypass
Promiseallocation entirely.
Native mode
Full Web API compliance with standard Request and Response objects:
import { serve } from '@miiajs/node-server'
const server = await serve({
fetch: app.fetch,
port: 3000,
mode: 'native',
})
Use native mode when you need strict instanceof Response checks or run multiple frameworks in the same process.
Standalone usage
The adapter can be used without the MiiaJS framework:
import { serve } from '@miiajs/node-server'
const server = await serve({
fetch: (req) => new Response('Hello, World!'),
port: 3000,
hostname: '0.0.0.0',
})
// Later...
await server.close()
Options
interface ServeOptions {
fetch: (req: Request) => Response | Promise<Response> // Required
port?: number // Default: 3000
hostname?: string // Default: '0.0.0.0'
mode?: 'optimized' | 'native' // Default: 'optimized'
bufferThreshold?: number // Default: 102400 (100KB)
}
The bufferThreshold controls the body buffering optimization in optimized mode. POST/PUT/PATCH bodies with a known Content-Length up to this size are buffered in memory for fast json()/text() access. Bodies without Content-Length or larger than the threshold use streaming via Readable.toWeb().
ServerHandle
The serve function returns a ServerHandle with a close() method:
const server = await serve({ fetch: handler, port: 3000 })
// Graceful shutdown
await server.close()
Testing
@miiajs/node-server is exercised directly - serve() accepts any (req: Request) => Response | Promise<Response>, so you don't need a Miia instance to test it.
import { afterEach, expect, it } from 'bun:test'
import { serve, type ServerHandle } from '@miiajs/node-server'
let server: ServerHandle
afterEach(async () => {
if (server) await server.close()
})
it('handles GET requests', async () => {
server = await serve({
port: 18234,
fetch: () => Response.json({ ok: true }),
})
const res = await fetch('http://localhost:18234/')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ ok: true })
})
For applications that wire the adapter through app.listen(port, host, serve), prefer the TestApp pattern from Testing - it runs handlers without binding a port and works under any runtime.