npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@usetoki/toki

v0.9.1

Published

A blazing-fast HTTP framework for Node.js with a native engine written in Zig.

Readme

The engine runs on Node's own libuv loop and calls handlers synchronously on the JS thread: no worker threads, no ThreadsafeFunction, no cross-thread hop. On the plaintext benchmark it sustains ~99k req/s at ~49 MB RSS on a single thread.

import { createApp, reply } from "@usetoki/toki";

const app = createApp();

app.get("/", () => reply.text("Hello, World!"));
app.get("/users/:id", (req) => reply.json({ id: req.params.id }));

app.listen(3000);
console.log("listening on http://127.0.0.1:3000");

✨ Why Toki

  • Native engine — HTTP/1.1 parsing, routing, and I/O run in Zig, on Node's own loop.
  • 🪶 Tiny footprint — a single-thread server in ~49 MB, ~2× leaner than node:http.
  • 🧩 Fully typed — strict TypeScript, no any, real editor autocompletion.
  • 🔌 Batteries included — routing, hooks, middleware, route groups, plugins, cookies, CORS, security headers, logging, req.id / req.ip.
  • 📦 Body parsing — JSON, urlencoded, and multipart/form-data uploads, plus pluggable content-type parsers.
  • 🗂️ Static files — MIME, ETag / 304, HEAD, traversal-safe, with pre-computed gzip/brotli.
  • 🗜️ Compression — gzip + brotli, negotiated per Accept-Encoding, off the event loop.
  • 🌊 Streamingreply.stream over chunked transfer encoding, with native backpressure.
  • 🔭 WebSockets — full RFC 6455 in native code: framing, masking, fragmentation, ping/pong, close codes, subprotocols, and a per-IP message-size guard.
  • 🔀 HTTP/2 — optional native h2 (ALPN over TLS, or h2c) with stream multiplexing, flow control, and HPACK; same handlers as HTTP/1.1.
  • 🔒 Direct HTTPS — terminate TLS 1.2/1.3 in the native engine (AEAD ciphers, ALPN, SNI; RSA + EC keys), no reverse proxy required.
  • 🛡️ Hardened — schema validation, JWT, a native per-IP rate limiter, slowloris guard, configurable limits.
  • 🧪 Testableapp.inject() runs a real request in-process, no port needed.

🚀 Install

npm install @usetoki/toki

Or build from source — see Build from source. You'll need Zig 0.16.0 and Node.js 20+.

Full guides and the API reference live at usetoki.github.io/toki.

📖 Features at a glance

import { createApp, reply, cors, securityHeaders, compression, jwtAuth } from "@usetoki/toki";

const app = createApp({ logger: "info" });

// Middleware + the full hook pipeline
app.use(cors({ origin: ["https://app.example"] }));
app.use(securityHeaders({ hsts: true }));
app.addHook("onResponse", compression());

// Route groups with a shared prefix + scoped hooks
app.group("/api/v1", (api) => {
  api.use(jwtAuth({ secret: process.env.JWT_SECRET! }));
  api.get("/me", (req) => reply.json(req.user));
});

// Encapsulated plugins
app.register(
  async (instance) => {
    instance.get("/health", () => ({ status: "ok" }));
  },
  { prefix: "/internal" },
);

// Schema validation (custom messages) + response serialization
app.post(
  "/users",
  {
    schema: {
      body: {
        type: "object",
        required: ["name"],
        properties: { name: { type: "string", minLength: 2 } },
        errorMessage: { required: { name: "name is required" } },
      },
    },
  },
  (req) => reply.json({ created: req.json<{ name: string }>().name }, 201),
);

// Uploads (req.form), static files, streaming, rate limiting
app.post("/upload", (req) => reply.json({ files: req.form?.files.length ?? 0 }));
app.static("/assets", "./public");
app.get("/events", () => reply.stream(sse(), { contentType: "text/event-stream" }));

app.listen(3000, { rateLimit: { max: 100, windowMs: 60_000 } });

Request: req.method, req.path, req.params, req.query, req.headers, req.cookies, req.body, req.form, req.ip, req.hostname, req.protocol, req.id, req.log, req.text(), req.json<T>(), await req.parseBody().

Reply: reply.text / html / json / empty / redirect / bytes / stream.

Built in: cors(), securityHeaders(), compression(), signJwt / verifyJwt / jwtAuth, setErrorHandler, setNotFoundHandler, addContentTypeParser.

⚙️ listen options

app.listen(3000, { host: "0.0.0.0", maxBodyBytes: 5_000_000 });

| Option | Default | Description | | --------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------ | | host | 0.0.0.0 | Bind interface. Pass 0 as the port to pick a free one. | | maxBodyBytes | 1 MiB | Largest accepted request body (413 above it). | | maxHeaders | 128 | Max header lines per request. | | backlog | 512 | Listen backlog. | | headerTimeoutMs | 0 | Close a connection stalled mid-request, in ms; 0 disables (slowloris guard). | | reusePort | false | SO_REUSEPORT for kernel-balanced multi-worker scaling (Linux/BSD). | | rateLimit | — | { max, windowMs } — native per-IP limiter; over-limit requests get a 429 before reaching JS. | | unixPath | — | Bind a unix-domain socket at this path instead of TCP (the port is ignored). Ideal for a reverse proxy → app on the same host. | | tls | — | { cert, key } PEM — terminate HTTPS directly (TLS 1.2/1.3); see HTTPS. | | http2 | false | Serve HTTP/2 — ALPN h2 over TLS, h2c in cleartext; see HTTP/2. | | http2Cleartext | "multiplex" | Cleartext h2 mode: "multiplex" (shares the port with HTTP/1.1) or "exclusive" (h2c only). Ignored over TLS. | | http2InitialWindow | 256 KiB | h2 per-stream receive window (and the connection window we raise to). | | http2MaxConcurrentStreams | 128 | h2 cap on simultaneous streams per connection. | | maxWsMessageBytes | 16 MiB | Largest accepted WebSocket message; a larger one is closed with 1009. | | wsCompression | false | Offer permessage-deflate (RFC 7692) when a client requests it. |

createApp({ logger, requestTimeoutMs }) configures the app; app.listen returns a handle whose close() shuts the server down gracefully.

🔒 HTTPS

Pass a PEM certificate chain and private key to terminate TLS directly in the engine — no reverse proxy. TLS 1.2 and 1.3, AEAD ciphers only (AES-GCM, ChaCha20-Poly1305), with ALPN and SNI. RSA and EC keys both work.

import { readFileSync } from "node:fs";

app.listen(443, {
  tls: {
    cert: readFileSync("fullchain.pem"), // leaf first, then any intermediates
    key: readFileSync("privkey.pem"),
  },
});

cert and key take a PEM string or a Buffer/Uint8Array. WebSockets (wss://), streaming, and static files all ride over TLS unchanged. ALPN advertises http/1.1, plus h2 when HTTP/2 is enabled (see below).

🔀 HTTP/2

Set http2: true and toki serves HTTP/2. Over TLS it's negotiated with ALPN (h2), falling back to HTTP/1.1 for clients that don't offer it; in cleartext it accepts the h2c prior-knowledge preface. Frame parsing, stream multiplexing, flow control, and HPACK header compression all run in native code — your routes, hooks, and reply builders are unchanged.

import { readFileSync } from "node:fs";

app.listen(443, {
  http2: true,
  tls: { cert: readFileSync("fullchain.pem"), key: readFileSync("privkey.pem") },
});

Many requests share one connection as independent streams, so a slow handler never blocks the others. Async handlers, reply.stream/SSE, and static files all work over h2. In cleartext, h2c shares the port with HTTP/1.1 by default; set http2Cleartext: "exclusive" for an h2c-only port (gRPC, pure-h2 internal services). Every peer-controlled resource is bounded — concurrent streams, header-list size (with a CONTINUATION-flood guard), the HPACK table, per-stream send buffers, and the aggregate in-flight request body per connection. A reset-without-progress flood (CVE-2023-44487) trips a GOAWAY, and headerTimeoutMs sweeps connections stalled mid-request. Passes the full h2spec conformance suite and a frame/HPACK fuzzer under a memory-checked build. There's no server push or TLS session resumption. Use TLS in production: browsers only speak h2 over TLS, and ALPN picks it automatically.

🔭 WebSockets

app.ws(path, handler) registers a WebSocket endpoint. The handler runs once per connection with the live socket and the upgrade request. Framing, masking, fragmentation, ping/pong, and the close handshake all run in native code, so handlers only ever see complete messages.

app.ws("/chat", { protocols: ["chat"] }, (socket, req) => {
  console.log(`connected from ${req.ip} as ${socket.protocol}`);

  socket.on("message", (data, isBinary) => {
    socket.send(isBinary ? data : `echo: ${data.toString()}`);
  });
  socket.on("close", (code, reason) => console.log("closed", code, reason));
});
  • Send: socket.send(string | Uint8Array) (returns the write backlog in bytes), socket.ping(), socket.pong(), socket.close(code?).
  • Events: message (data, isBinary), close (code, reason), ping, pong, and drain (fires when a backpressured socket's write queue empties).
  • State: socket.data is a free-form per-connection bag; socket.protocol is the negotiated subprotocol.
  • Compression: set wsCompression: true in listen to offer permessage-deflate (RFC 7692). It's negotiated per connection and applied transparently; handlers send and receive plain data.
  • The Buffer passed to message / ping / pong is a view over native memory valid only during the call — copy it (Buffer.from(data) / data.toString()) to keep it.

A plain GET to a WebSocket path (no Upgrade header) gets 426 Upgrade Required.

🔌 TCP & UDP

Beyond HTTP, the engine exposes raw TCP and UDP servers on the same libuv loop. No extra thread, the same try-write-then-queue backpressure, and a connection that costs about what the kernel charges (no per-socket HTTP buffer).

import { createTcpServer, createUdpServer } from "@usetoki/toki";

// TCP echo
const tcp = createTcpServer((socket) => {
  socket.on("data", (chunk) => socket.write(chunk));
});
tcp.listen(9000);

// UDP echo
const udp = createUdpServer((msg, rinfo, sock) => sock.send(msg, rinfo.port, rinfo.address));
udp.bind(9001);

socket.write() returns false under backpressure (resume on drain); socket.end() half-closes after flushing. The engine is process-global: one createTcpServer (and one UDP server) owns it, but that server can listen() on several ports with a single handler — route by socket.localPort — so e.g. an XMPP server runs c2s and s2s side by side. Scale across cores with reusePort and a process per core.

The TCP socket is a full TLS peer, not just a server. Terminate TLS at accept with tls: { cert, key } (mutual TLS via requestCert + ca, virtual hosts via sni, ALPN via alpn); dial out with connectTcp(host, port, { tls }); or start in cleartext and STARTTLS-upgrade in place with socket.upgradeTLS() (a synchronous secure event fires when the handshake establishes). Either side can read socket.peerCertificate(), socket.alpnProtocol, socket.servername, and derive RFC 9266 channel-binding material with socket.exportKeyingMaterial(len, label). See examples/tcp-tls-peer.ts for the whole story, and the TCP and UDP docs for the full API.

There's no TLS session resumption yet — the server completes a full handshake per connection and issues no session tickets.

🔌 Plugins

Official, first-party plugins, each its own @usetoki/* package, installed on demand. A plugin is a function you call on the app (or any scope) to extend that scope.

Auth & security

| Package | What it does | | ---------------------------------------------------- | ---------------------------------------------------------------------------- | | @usetoki/toki-auth | Multi-strategy auth — basic, bearer, API key, composed with anyOf/allOf. | | @usetoki/toki-jwt | Asymmetric JWT — RS/PS/ES/EdDSA sign + verify with remote JWKS. | | @usetoki/toki-csrf | CSRF protection — signed double-submit tokens with optional origin checks. | | @usetoki/toki-helmet | Secure response headers — CSP, HSTS, frameguard, and more. | | @usetoki/toki-ip-filter | Allow/deny by IP and CIDR (IPv4 + IPv6), dependency-free. | | @usetoki/toki-ratelimiter | Per-route, per-key rate limiting with pluggable stores. |

Sessions & cookies

| Package | What it does | | ---------------------------------------------------------- | ----------------------------------------------------------------------- | | @usetoki/toki-cookie | Signed and encrypted cookies — HMAC + AES-256-GCM with key rotation. | | @usetoki/toki-session | Stateful sessions — signed id cookie, store (memory, Redis, memcached). | | @usetoki/toki-secure-session | Stateless encrypted-cookie sessions — no server-side store. |

HTTP features

| Package | What it does | | ---------------------------------------------------------------- | --------------------------------------------------------------------- | | @usetoki/toki-cache | Route response caching — TTL + Vary over memory, Redis, or memcached. | | @usetoki/toki-etag | Automatic ETag validators and 304 Not Modified. | | @usetoki/toki-range | HTTP Range requests and 206 Partial Content — buffers or files. | | @usetoki/toki-sse | Server-Sent Events — heartbeats, event ids, Last-Event-ID resume. | | @usetoki/toki-idempotency | Idempotency-Key dedup and replay over memory, Redis, or memcached. | | @usetoki/toki-multipart-storage | Stream multipart uploads to disk, S3, or a custom store. |

Infrastructure & DX

| Package | What it does | | ------------------------------------------------------------ | ------------------------------------------------------------------------- | | @usetoki/toki-proxy | Reverse-proxy gateway — streaming pass-through to an upstream. | | @usetoki/toki-circuit-breaker | Per-route circuit breaker — fast-fail 503 when a dependency is failing. | | @usetoki/toki-view | Server-side templates — bring eta, ejs, or handlebars. | | @usetoki/toki-autoload | Filesystem routing — auto-register a directory tree of route modules. | | @usetoki/toki-env | Validate and coerce env vars at boot into a typed, frozen config. | | @usetoki/toki-sensible | Sensible defaults — HTTP errors, RFC 9457 problem+json, assertions. |

🧭 Native vs JavaScript — the boundary

The shared, heavy logic is native: HTTP parsing, routing, the MIME table, ETag and header assembly, status phrases, static serving, and compression negotiation. The TypeScript layer is the developer API plus the unavoidable Node bits (the handler pipeline, fs/zlib for static assets).

That line is drawn on purpose, and it's measured. Building a parsed request object in Zig and handing it to V8 costs ~20 N-API calls per request, which is slower than letting V8's own C++ URLSearchParams / Headers / JSON do it. So query/header/cookie/JSON parsing and schema validation stay in TypeScript. Crossing the N-API boundary to go native there would only make it slower.

🔧 Build from source

npm install
npm run build      # native addon (ReleaseFast) + TypeScript → dist/
npm test           # zig build test + the Node suite
npm run lint       # zig fmt + tsc + prettier

npm run build:debug is a faster, unoptimized native build for iteration. The build cross-compiles: zig build -Dtarget=aarch64-linux-gnu (and friends) produces the addon for any platform from one host.

📂 Layout

| Folder | What | | ---------------- | ----------------------------------------------------------------------------------------------------- | | src/core/ | Engine state, the libuv hot path, and server lifecycle (engine, loop, server). | | src/http/ | HTTP layer — parser, router, request, response, static, stream, mime. | | src/websocket/ | WebSocket wire format (frame) and the session/dispatch layer (session). | | src/security/ | The native rate limiter. | | src/ffi/ | Hand-declared N-API and libuv bindings. | | ts/ | TypeScript framework layer (core/, http/, websocket/, security/, native/) → dist/. | | __test__/ | Node test suite (node:test, run as .ts); Zig unit tests live in *.test.zig beside their module. | | examples/ | A runnable, self-checking example per feature. |

🧪 Examples

node examples/routing.ts

Browse examples/ for routing, async handlers, hooks, groups, plugins, validation, cookies, static files, forms, CORS, compression, rate limiting, JWT, TLS, streaming, and graceful shutdown.

Scope

Toki speaks HTTP/1.1, with optional direct TLS (HTTPS) termination in the engine. HTTP/2 is intentionally out of scope — put it behind a reverse proxy (nginx, Caddy) if you need it.

License

MIT — see LICENSE.