Node.js Modules
Import Node.js builtins the way you always have. There is no GJSify-specific spelling and nothing extra to install:
import { readFile, writeFile } from 'node:fs/promises';import { createServer } from 'node:http';import { createHash, randomUUID } from 'node:crypto';import { EventEmitter } from 'node:events';import path from 'node:path';In an --app gjs build each import is rewritten to its @gjsify/* implementation: node:fs to Gio.File, node:http to Soup.Server, node:crypto to GLib.Checksum and GLib.Hmac. The same file also builds for --app node, where the imports stay as written and Node, Bun and Deno answer them from their own builtins.
The bare form works too. import fs from 'fs' resolves the same way, which is why dependencies written before the node: prefix existed keep working unchanged.
Globals need no import
Section titled “Globals need no import”process, Buffer, URL, structuredClone, queueMicrotask and setImmediate are there without one. An --app gjs build scans the bundled output, finds the ones you actually reach, and injects only those. An --app node build injects none of them, because Node, Bun and Deno already define them.
const port = Number(process.env.PORT ?? '8080');const body = Buffer.from('hello', 'utf-8');Which modules are ready
Section titled “Which modules are ready”Three levels of completeness. Full means everything a normal app touches is there. Partial means the core works and specific corners are missing, each one named in the table. Stub means the module resolves so a dependency check passes, but does nothing useful.
Node.js API Coverage
38 of 41 Node.js modules
User-facing Node modules backed by GLib, Gio, Soup, and native Vala bridges. 3 deprecated or browser-irrelevant modules ship as stubs so dependency checks resolve.
| Module | Backed by | Status | Worth knowing |
|---|---|---|---|
assert | pure TS | Full | AssertionError, deepEqual, throws, strict |
async_hooks | pure TS | Full | AsyncLocalStorage, AsyncResource, createHook |
buffer | pure TS | Full | alloc, from, concat, every encoding, read/write helpers, plus the Blob and File classes Node re-exports here |
child_process | Gio | Full | exec, execFile, spawn, and their sync variants, via Gio.Subprocess. Shell, detached, argv0 and signal numbers resolve per platform. Per-platform contract: package README |
cluster | pure TS | Stub | isPrimary / isWorker only; fork() throws |
console | pure TS | Full | Console class, format specifiers, table, group, count, time |
constants | pure TS | Full | The deprecated flat alias for os.constants plus fs.constants |
crypto | GLib | Full | Hash, Hmac, PBKDF2, HKDF, scrypt, AES, DH, ECDH, Sign / Verify, KeyObject, X509 |
dgram | Gio | Full | UDP over Gio.Socket, incl. multicast, broadcast, TTL, IPv6 |
diagnostics_channel | pure TS | Full | Channel, TracingChannel, subscribe / unsubscribe |
dns | Gio | Full | lookup, resolve4/6, reverse via Gio.Resolver, plus dns/promises |
domain | pure TS | Stub | Deprecated in Node; pass-through |
events | pure TS | Full | EventEmitter with enumerable prototype methods, so socket.io v4 works |
fs | Gio | Full | sync, callback, promises, streams, FSWatcher, FileHandle; URL path args everywhere |
globals | GLib | Full | Not an import. This is @gjsify/node-globals, the package behind process, Buffer, URL, URLSearchParams, structuredClone, queueMicrotask, btoa / atob and the timer family |
http | Soup 3.0 | Full | Server, IncomingMessage, ServerResponse, Agent, chunked streaming, upgrade |
http2 | Soup 3.0 | Full | h2 over ALPN, plus respondWithFile and respondWithFD via @gjsify/http2-native. pushStream builds the PUSH_PROMISE and hands you a working stream object, but Soup exposes no way to put it on the wire |
https | Soup 3.0 | Full | TLS Agent, request / get, createServer |
inspector | pure TS | Stub | Session stub; the V8 inspector protocol has no GJS equivalent |
module | Gio, GLib | Full | builtinModules, isBuiltin, createRequire (PnP aware) |
net | Gio | Full | Socket over Gio.SocketClient, Server over Gio.SocketService, isIP |
os | GLib | Full | homedir, hostname, cpus, platform, userInfo, networkInterfaces, loadavg |
path | pure TS | Full | POSIX and Win32 flavours, selected per host |
perf_hooks | pure TS | Full | performance, marks and measures, PerformanceObserver, timerify |
process | GLib | Full | EventEmitter based; env, cwd, argv, platform, hrtime, nextTick, memoryUsage |
querystring | pure TS | Full | parse / stringify with full encoding |
readline | pure TS | Full | Interface, createInterface, question, prompt, async iterator, cursor control |
sqlite | Gda 6.0 | Partial | DatabaseSync and StatementSync on the libgda SQLite provider |
stream | pure TS | Full | Readable, Writable, Duplex, Transform, PassThrough, objectMode, backpressure |
string_decoder | pure TS | Full | UTF-8, Base64, hex, streaming |
sys | pure TS | Full | Deprecated alias, re-exports util |
timers | pure TS | Full | setTimeout / setInterval / setImmediate plus timers/promises, on GLib sources |
tls | Gio | Full | TLSSocket over Gio.TlsClientConnection; mTLS, ALPN, peer certificates |
tty | GLib, @gjsify/terminal-native | Full | ReadStream / WriteStream, isatty, ANSI escapes, window size, raw mode |
url | GLib | Full | URL incl. createObjectURL / revokeObjectURL, URLSearchParams over GLib.Uri |
util | pure TS | Full | inspect, format, promisify, callbackify, styleText, types |
v8 | GLib | Partial | serialize / deserialize (wire format v15); getHeapStatistics is approximated from OS process memory (/proc/self/status on Linux, ps on macOS) and reports all zeros where neither is readable. No heap snapshots, no CPU profiles |
vm | pure TS | Partial | runInThisContext, runInNewContext, Script, compileFunction, without realm isolation |
worker_threads | Gio, GLib | Partial | MessageChannel, MessagePort, BroadcastChannel; Worker runs as a subprocess. SharedArrayBuffer is cross-process and file backed via @gjsify/sab-native |
ws (npm) | Soup 3.0 | Partial | Drop-in for the ws package: client plus WebSocketServer over @gjsify/websocket |
zlib | Gio | Full | gzip, deflate and deflate-raw over Gio.ZlibCompressor. A host that brings its own Compression Streams API gets that path instead |
ws is in this list because import { WebSocketServer } from 'ws' is aliased to @gjsify/ws, so packages that depend on ws work without a change. isomorphic-ws and node-fetch are aliased the same way.
Where each module also runs
Section titled “Where each module also runs”Every package declares which runtimes it supports, in a gjsify.runtimes field. Pure TypeScript modules such as path, buffer, events, querystring and string_decoder also run in the browser and under NativeScript. The Gio-backed ones such as fs, net, http and crypto declare an implementation for GJS only: they call into GIO and Soup directly, and on Node, Bun and Deno the same import resolves to that runtime’s own builtin instead. Runtimes has the whole picture, and Platform Support covers which operating systems each native bridge reaches.