# Migrating from node-http-server v8 to v9 Version 9 keeps native HTTP and HTTPS listeners, the small server surface, default singleton, hooks, and `key=value` CLI. The major changes make network exposure explicit, isolate multiple servers, and bring static delivery behavior up to date. ## Runtime - Use Node.js 22.12 or newer. - Install normally with `npm install node-http-server`. - Published installs have zero runtime dependencies. The repository uses the exact owner-maintained `vanilla-test@2.1.1` release as its sole direct development dependency for native V8 coverage. - CommonJS and ESM are Node.js module entry points, not browser entry points. No transpiler or TypeScript toolchain is needed. - node-http-server is Node.js-only; native-browser execution, import maps, and browser-bundler conformance are not applicable. It serves files to browsers; the package itself does not execute in browsers. Browser-targeted bundles and `file://` are not supported runtime paths. ## CommonJS and ESM The v8 singleton still works: ```js const server=require('node-http-server'); server.deploy({root:'./public'}); ``` ESM can use the same default shape: ```js import server from 'node-http-server'; server.deploy({root:'./public'}); ``` For new code, prefer an isolated named `Server` instance: ```js import {Server} from 'node-http-server'; const server=new Server({ root:'./public', port:8080 }); server.deploy(); ``` `Server`, `Config`, and `RefString` are named exports in both module systems. ## Localhost is the new default v8 did not pass a host to `listen()`, so Node accepted connections on available interfaces. v9 listens on `127.0.0.1` unless configured otherwise. For a deliberately public or LAN-accessible server: ```js new Server({ host:'0.0.0.0', port:8080, root:'./public' }).deploy(); ``` `host` now controls the network interface. `domain` and `domains` only select accepted Host values and static roots. Audit firewall, authentication, TLS, and request-limit requirements before changing the host. ## Multiple servers and shutdown Give each unrelated listener set its own `Server` instance: ```js const {Server}=require('node-http-server'); const first=new Server({port:8080,root:'./one'}).deploy(); const second=new Server({port:8081,root:'./two'}).deploy(); await Promise.all([ first.close(), second.close() ]); ``` `deploy()` now returns its instance. `close(callback?)` closes its HTTP and HTTPS listeners, returns a Promise, and leaves the instance ready for a later `deploy()`. The optional deploy callback still receives the server instance. When HTTP and HTTPS are both configured, it runs as each listener becomes ready. ## Configuration changes | v8 behavior | v9 replacement | |---|---| | Listen interface left to Node | `host:'127.0.0.1'` by default; set `host` explicitly to expose it | | `Config` reads every process argument | Only the CLI parses arguments; programmatic `Config` is deterministic | | Shallow nested assignment | Known nested objects merge into isolated defaults | | Unknown extension returns `415` | Unknown extension uses `application/octet-stream` | | Dotfiles are served | Dot-prefixed path segments return `403` unless `server.allowDotfiles:true` | | Shared or mutable default objects | Every `Config` and `Server` owns a deep copy | | No request-body limit | `server.maxRequestBodyBytes` is opt-in and set in bytes | | No clean listener lifecycle | `await server.close()` | The current server defaults are: ```js { host:'127.0.0.1', port:8080, root:process.cwd(), server:{ index:'index.html', noCache:true, allowDotfiles:false, timeout:30000, requestTimeout:300000, headersTimeout:60000, keepAliveTimeout:5000, maxRequestBodyBytes:false, compression:false, compressionThreshold:1024, spaFallback:false } } ``` Timeout values are milliseconds. `false` or `0` disables an individual timeout. `maxRequestBodyBytes:false` or `0` leaves the body unlimited. Public deployments should set a finite body limit that matches the application. ### MIME configuration A `contentType` object now overlays the built-in map instead of forcing you to reproduce it: ```js new Server({ contentType:{ custom:'application/x-custom' } }); ``` Set `contentType:false` to remove automatic MIME mapping from the active configuration. Files then use `application/octet-stream` unless your hook sets a type. An extension explicitly set to `false` returns `415`. ## Static response behavior The fallback file server now handles: - `GET` and `HEAD`; - streaming files; - one satisfiable `GET` byte range with `206`, and `416` for valid but unsatisfiable ranges; - weak ETags and `Last-Modified` validation; - `304 Not Modified`; - optional Brotli/gzip compression; and - optional SPA fallback. Unsupported methods run the request hooks, then receive `405` if no hook takes ownership. Code that accidentally depended on the old server treating every method like `GET` must move that behavior into `onRequest`. Malformed, unsupported-unit, and multi-range headers are ignored and receive the full `200` response. `HEAD` ignores Range and mirrors full `GET` headers without a body. ## Replace old certificate fixtures The local certificate and private-key fixtures tracked by older releases were removed. Any copy obtained from v8, an old package, or repository history is public and untrusted. Never deploy with it. Generate a new development certificate when one is needed, use certificates and keys managed for the actual environment in production, and keep private keys out of source control. ## Hook compatibility The hook names and truthy takeover contract remain: ```text onRawRequest(request, response, serve) onRequest(request, response, serve) beforeServe(request, response, bodyRef, encodingRef, serve) afterServe(request, response) ``` Existing `afterServe(request)` functions continue to work; v9 supplies an additional response argument. `request.body` remains a UTF-8 string. Use `request.bodyBuffer` when the original bytes matter. Static files stream when the default `beforeServe` method is untouched. Assigning or overriding `beforeServe` selects the buffered compatibility path so existing body replacement code can still modify `bodyRef.value`. ## CLI migration The v8 form remains valid: ```sh node-http-server port=9000 root=./public verbose=true ``` The preferred v9 form is: ```sh node-http-server --port 9000 --root ./public --verbose ``` Run `node-http-server --help` for the complete option list. CLI parsing no longer leaks into code that imports `Config`. ## Upgrade checklist 1. Upgrade Node.js to 22.12 or newer. 2. Decide whether localhost-only is correct. Add `host` only when network access is intended. 3. Move repeated singleton deployments to separate `Server` instances. 4. Await `close()` during tests and graceful shutdown. 5. Set a finite request-body limit for an exposed service. 6. Review custom `beforeServe` hooks because they intentionally buffer static files. 7. Update tests for `HEAD`, `405`, binary fallback, ranges, and conditional responses. 8. Keep custom CLI parsing outside `Config`. 9. Delete copied v8 certificate fixtures and replace them with newly managed credentials. 10. Keep dotfiles blocked, or explicitly audit the complete root before setting `server.allowDotfiles:true` / `--allow-dotfiles`. See the [README](README.md) for the complete configuration and API contract and [SECURITY.md](SECURITY.md) for deployment guidance.