A high-performance HTTP and WebSocket server for Node.js built on the native swm-uws transport. One server instance can handle both protocols.
Choose swm-core when you need a complete server layer: routing and before hooks, request contexts, body and time limits, streaming, backpressure, and graceful shutdown.
Keep synchronous handlers synchronous: use the Promise path only when the handler actually awaits work.
http.onRequest and http.routes are mutually exclusive. An empty http: {} configuration returns a predictable 404.
HTTP and WebSocket can be enabled independently; at least one protocol must be configured.
ws.onUpgrade is required: return a user-data object to accept or null to reject. That exact object becomes ctx.data without copying; a separate selectProtocol chooses the subprotocol.
All sizes are byte counts. By default, an HTTP body is limited to 1 MiB per request and a 256 MiB aggregate budget.
In v5.1.2, ctx.bodyStream() provides owned Buffer chunks with backpressure for large uploads. Create it before the first await, do not combine it with prefetch or other readers, and use maxStreamBodySize to cap one stream; streamed bytes do not count toward maxBodyBudget.
02
What the server provides
Native transport
HTTP and WebSocket traffic passes through @swarmmachina/swm-uws without an intermediate JavaScript transport layer.
Routing and hooks
Use a universal onRequest handler or declarative method/path routes with :param, wildcard /* segments, and before chains.
Resource control
HttpContext pools, a finite body budget, buffered and streaming per-route body limits, a 30-second async timeout, and native transport policy for headers, body rate, connections, and trusted proxies.
Streaming and backpressure
ctx.bodyStream() for backpressured uploads, stream, startStreaming, tryEnd, onWritable, and a numeric WebSocket send status.
Connection control
Safe async upgrades: ctx.data preserves the exact identity of the returned object; selectProtocol, pub/sub, connectionKey addressing, graceful close, and forced termination are available.
Responses and lifecycle
Prepared headers, CORS, and static-file helpers; shutdown(timeout) waits for active requests and connections, while close() stops them immediately.
03
Runtime and platforms
Environment
Status
Note
Node.js 22 / 24
Supported
Other major versions are rejected by the engines constraint.
Linux x64 + glibc
Supported
Use bookworm/slim-based container images.
Windows x64
Supported
A prebuilt native binary is included.
macOS arm64 / x64
Supported
Apple Silicon and Intel are supported.
Linux ARM64 / Windows ARM64
No build
These targets are not in the current prebuild matrix.
Alpine / musl
Unsupported
Use a glibc-based image.
TLS / permessage-deflate
Disabled
Terminate TLS in front of the application.
04
API map
Server
Lifecycle, pub/sub, and addressable WebSocket connections.
Explicit get* readers share one request cache; buffer() for body() is the only concise alias left in v5.
headersreplied / abortedgetIP()getMethod()getUrl()getQuery()getQuery(name)getParameter(name)getReqHeader(name)getHeaders()getContentLength()body()buffer() — alias of body()json()text()bodyStream()setStatus()setHeader()appendHeader()setHeaders()flushHeaders()send()sendJson()sendText()sendBuffer()sendError()reply()replyAndClose()terminate()stream()startStreaming()write()end()tryEnd()onWritable()getWriteOffset()
Complete signatures, types, and examples are available in the source repository README and TypeScript declarations.
05
Body lifetime, limits, and backpressure
swm-core deliberately keeps cost and data lifetime visible. These rules matter for correct production code.
Read lazy bodies before the first await
In the default mode, call ctx.body(), buffer(), text(), or json() before the first asynchronous operation. Otherwise uWS body events may arrive before a reader is registered.
Use prefetch for asynchronous auth
Enable http.prefetch or route.prefetch when authorization or database work must finish before reading the body. Bytes are collected early while JSON parsing remains lazy.
Create a streaming upload before await
For a large request body, call ctx.bodyStream() synchronously before the first await in a hook or handler and pass the stream to pipeline(). Do not combine it with prefetch, body(), buffer(), text(), or json(). maxStreamBodySize and a narrower route or per-call limit cap one upload; call stream.destroy() if the handler stops reading early.
Bound memory and time
Values are byte counts: maxBodySize defaults to 1 MiB (64 MiB maximum), while maxBodyBudget defaults to 256 MiB. maxStreamBodySize defaults to maxBodySize and is not charged to the aggregate body budget. Zero means zero capacity; null explicitly disables aggregate accounting. An oversized request gets 413 and budget exhaustion gets 503.
Account for the v4.1 safety defaults
requestTimeoutMs ends a stalled async chain after 30 seconds by default with 408. WebSocket defaults allow a 1 MiB inbound message and 64 KiB of backpressure per connection; a slow socket closes after a drop by default.
Isolate error delivery
In v5.1 http.onError receives an immutable, body-free event rather than HttpContext. Select only needed errorDelivery.headers, query, and includeIp metadata; queueLimit, concurrency, and timeoutMs bound a remote sink. Inspect httpErrorDeliveryStats, including dropped and timedOut.
Retain only the headers you need
HTTP, route, and WebSocket prefetchHeaders retain selected fields in ctx.headers or meta.headers across an await. Use 'all' only when the complete set is required; selective prefetch is cheaper and more explicit.
Use explicit request readers
In v5, use getIP(), getMethod(), getUrl(), getQuery(), getParameter(), getReqHeader(), getHeaders(), and getContentLength(); the concise aliases were removed. getHeaders() returns an isolated copy of the complete set and requires retained data after an await.
Bound the native transport
transport configures maximum header size/count, header, keep-alive, body-idle and response-write timeouts, a minimum body rate, and trustedProxy. Values are validated before the server starts.
Check the send status
WSContext.send() returns 1 on success, 0 on backpressure, and 2 when a message is dropped because of a limit.
Do not retain HttpContext
HttpContext is reused between requests. WSContext lives for the connection, but must not be used after onClose.
Declare the proxy trust boundary explicitly
transport.trustedProxy is disabled by default. Enable { header: 'x-real-ip' } or { header: 'x-forwarded-for', hops: N } only on a listener behind a proxy that overwrites the selected header; X-Forwarded-For is counted from the right. ctx.getIP() returns the selected address and falls back to the upstream peer when the header is absent. Do not treat a network address as authenticated identity.
06
Common questions
Concise answers about package selection, runtime contracts, and production constraints.
01
What is swm-core?
swm-core is an experimental HTTP and WebSocket server for Node.js 22 and 24 built on the native swm-uws binding. It combines routing, request contexts, body limits, timeouts, backpressure, and graceful shutdown in one server API.
02
When should I use swm-core instead of swm-uws?
Start with swm-core for an application API, WebSocket gateway, or regular production service. Use swm-uws when you need the compatible low-level uWebSockets.js App() surface and the team is prepared to own routing, request and response lifetimes, limits, and shutdown.
03
How do I retain HTTP headers across an await in swm-core 5.1?
Set http.prefetchHeaders or route.prefetchHeaders to the fields you need. Use ws.prefetchHeaders for WebSocket upgrades. Selected fields remain available through ctx.headers or meta.headers after an await; reserve 'all' for code that genuinely needs the complete header set.
04
How do I send HTTP errors safely to an external service?
In v5.1 http.onError receives an immutable HttpErrorEvent rather than a reused HttpContext. Configure finite errorDelivery concurrency, queueLimit, and timeoutMs; enable headers, query, and includeIp only by explicit allowlist. server.httpErrorDeliveryStats exposes queue state and drops.
05
How do I read a request body after asynchronous authorization?
Enable http.prefetch or route.prefetch so swm-core starts bounded byte collection before before hooks and handlers run; JSON parsing remains lazy until ctx.json(). In lazy mode, call ctx.body(), buffer(), text(), or json() before the first await and retain the Promise.
06
How do I optimize large uploads in swm-core 5.1.2?
Do not materialize the complete body: with prefetch disabled, create ctx.bodyStream() synchronously before the first await and pass it to pipeline(). maxStreamBodySize caps one stream, while a route or per-call limit can only narrow it. The stream cannot be combined with body(), buffer(), text(), or json(); call stream.destroy() when a handler stops reading early. Backpressure bounds the stream queue, but does not replace a concurrency limit for uploads or a downstream-storage budget.
07
How do I bound slow or malformed HTTP clients?
Pass a transport policy with maxHeaderSize, maxHeaderCount, headersTimeoutMs, keepAliveTimeoutMs, bodyIdleTimeoutMs, minBodyRateBytesPerSec, and responseWriteTimeoutMs. The native transport applies these limits before or while an application handler runs.
08
How do I read a client IP behind a trusted reverse proxy?
In swm-core 5.1.2, set transport.trustedProxy to { header: 'x-real-ip' } or { header: 'x-forwarded-for', hops: N }. It is disabled by default; enable it only on a listener reached through a proxy that overwrites the selected header. X-Forwarded-For is counted from the right, and ctx.getIP() falls back to the upstream peer when the header is absent.
09
Does swm-core support TLS, Alpine, or Linux ARM64?
No. The current matrix supports Node.js 22/24, Linux x64 with glibc, Windows x64, and macOS arm64/x64. TLS and permessage-deflate are disabled, while Alpine/musl and Linux or Windows ARM64 have no current prebuilds; terminate TLS before the application.
07
When to use the low-level binding
swm-core fits most services. Move to swm-uws when you need direct compatibility with the regular uWebSockets.js App() API and are prepared to manage request and response object lifetimes yourself.
JavaScripttransport-choice.js
// High-level serverimport Server from'@swarmmachina/swm-core'// Low-level bindingimport uWS from'@swarmmachina/swm-uws'