Bun APIs, mapped to Node
An API-level breakdown of every Bun primitive used by the reepolee framework and the reeweb static-site generator - with the equivalent Node API or npm package for each one. Every row links to the Bun API reference.
Two projects, one runtime
Full-stack application framework: HTTP server, SQL data layer, S3 storage, Redis cache and queue, websockets, a code generator, and CLI scripts. Runs 100% on Bun - no deps, no ceremony, full control.
-
Bun.serve()with native Radix-treeroutes:in production -
import { SQL } from "bun"- one client for SQLite, MySQL, Postgres -
S3Client,RedisClient,Cookieas namedbunimports -
git clone,bun start. Done.
Static-site generator with a dev server, preview/publisher daemons, and an image pipeline. Deploys to Cloudflare Workers (wrangler), while all build and CLI tooling runs on Bun.
-
SSG + dev server built on
Bun.serve(),Bun.file(),Bun.write() -
Bun.Imagefor responsive WebP/AVIF/JPEG generation -
Bun.markdown.html()andBun.YAML.parse()for content -
cf-worker.tsuses Cloudflare'sHTMLRewriter, not Bun
What this analysis covers
Both projects use Bun-native primitives end-to-end: file I/O, process spawning, hashing, markdown, YAML, archives, images, and the full data layer (SQL, S3, Redis). Below is the complete map - Bun API, where it is used, and what it would become on Node.
1. Core runtime globals
apps/main/server.ts, lib/bootstrap.ts, all scripts) - typed env access, auto-loads .env, settable at runtimeprocess.env; built-in .env loading via process.loadEnvFile() (v20.12+) / --env-file flag (v20.6+)--dev / --prod / --agent / --test in apps/main/server.ts and scripts/*.ts)process.argv.slice(2), or node:util parseArgs() (stable v20+)scripts/check_domain_compliance.ts)require.main === module (CJS) or compare process.argv[1]reeweb/scripts/test_replay.ts)which npm package, or child_process.execSync("which …")node:util styleText() (stable v22.11+), or chalk / picocolorshrtime.bigint(), exit, cwd(), pid, platform, kill, signal handlers - run under Bun's Node-compat layer2. HTTP server & WebSockets
Bun.serve is fetch-handler based, not request/response-object based.lib/server_startup.ts, apps/reeman/server.ts, dev/preview/publisher scripts, testsnode:http.createServer() or Express/Fastify/Hono - not drop-inapps/main/server.ts / apps/reeman/server.ts.port, .upgrade(), .reload() in lib/bootstrap.ts, apps/main/server.tsnode:http.Server (no upgrade helper - see ws)apps/reeman/**, lib/crud_routes.ts, lib/middleware/*node:http.IncomingMessage + manual URL/body parsinglib/bootstrap.ts, apps/main/server.tsws npm package (WebSocketServer)/__reload livereload)ws + http upgrade event (wss.handleUpgrade)reeweb/scripts/dev/*.tsWebSocket client built in (default v21+, stable v22.4+); server side still needs wsRequest / Response / fetch()
scripts/preview.ts, dev responses)fs.createReadStream() piped to the response3. File system
fs.promises.readFile(path, "utf8")JSON.parse(await fs.promises.readFile(path, "utf8"))fs.promises.access(path) or fs.existsSyncfs.promises.writeFile + mkdir(…, { recursive: true })json_to_sql.ts, publisher/files.ts, og-images)node:fs glob() / globSync() (stable v22.12+), or fast-glob / glob4. Child processes & shell
node:child_process.spawn() - different arg shape: Bun takes { cmd: ["bin", "arg"] }node:child_process.spawnSync().exited, .stdout, .kill() in publisher runner and orchestratenode:child_process.ChildProcess + promisified exitBun.$`git add -A) in git scriptsexeca / zx npm, or child_process.execFilescripts/mcp/operations.ts, generator/crud/refresh_fields.tsrequire("node:child_process").spawnSync5. Bundler & transpiler
reeweb/scripts/engine_drift_check.ts)typescript ts.transpileModule() or esbuild/swc; Node 22.6+/24+ also runs .ts natively (type stripping)6. Crypto & password hashing
node:crypto createHash() → .update().digest(), or one-shot crypto.hash() (v21.7+/22+)hash() / verify() bcrypt for auth routes and user generatorbcrypt npm (hash/compare), or node:crypto.scrypt7. Data formats (markdown / YAML)
marked / markdown-it / remark npm (no built-in)8. Database - bun:sql (import { SQL } from "bun")
config/db.ts, config/db_cli.ts, lib/i18n.ts, generators, MCP db toolnode:sqlite (v22.5+ behind a flag, unflagged v22.12+, stable v24+; ships in Node 26). MySQL → mysql2, Postgres → pg; better-sqlite3 as a SQLite alternativedb_cli`SELECT …` built-in query interpolationsql-template-strings, pg tagged templates, or Knex/Prisma9. Object storage - bun:s3
lib/s3/core.ts, lib/issue_reporter.ts (Cloudflare R2)@aws-sdk/client-s3 npm (R2/S3-compatible).write(), .presign(), .exists()@aws-sdk/lib-storage + Upload/GetObjectCommand10. Redis - bun:redis
11. Cookies
lib/cookies.ts, auth routes)cookie npm package (parse/serialize)12. Archive & images
lib/s3/proxy.ts, prepare_images.ts)sharp npm package (or jimp); vips CLI13. import.meta - module metadata
path.dirname(fileURLToPath(import.meta.url))ssg.ts, release.ts)process.argv[1] === fileURLToPath(import.meta.url)apps/main/routes.ts)import.meta.resolve() (Node 20.6+) or createRequire(...).resolve()14. Test runner - bun:test
describe / expect / test / mock in ~90 test filesnode:test + node:assert (Node 18+), or vitest/jestnode --test (concurrency via --test-concurrency); coverage via --test-coverage (stable v20.12+)node:test mock.fn() / mock.method() (stable v20+), or vi.fn() / jest.fn()15. CLI / runtime flags
node --watch (Node 18.11+; restarts the process)tw via Bun-managed vendor
css:build)tailwindcss npm CLI16. Cloudflare Workers - reeweb production runtime
reeweb is the only part that does not run on Bun in production: wrangler deploy uploads cf-worker.ts to Cloudflare's edge runtime (V8 isolates + web-standard fetch).fetch(req, env) + env.ASSETS.fetch()
cf-worker.ts static asset handlerhttp.createServer + static file servingHTMLRewriter
[data-cf-edge])cheerio / htmlrewriter npm, or regex replacereq.cf
cf-worker.ts17. The .ree template syntax
apps/main/*.ree, src/public/*.ree), compiled by lib/template/compiler.ts.{#layout("layout")}
layout.ree, docs.layout.ree, ...){#layout("layout")} at the top of every page{#include('path', { data }) }
{#include('$components/svgs/github.svg')} with $components / $lib / $config / $root path aliases{{ ... }}
{{ const t = props.translations; props.page_title = t.title; }}{= expr }
{= project.title}{~ expr }
{~ helpers.md(project.desc)}; layouts emit the page via {~ props.body}{_ path }
props.translations{_ nav.theme_toggle}{- path }
{- intro_p}, <md-text>{- features_h}</md-text>{@ path }
{@ md_content }{#each list }
as item, index, key); {:else} renders the empty state{#each props.translations.sections as section} ... {/each}{#if cond }
{:else} for the fallback branch{#if section.note} ... {/if}{#with expr }
with semantics){#with props.row} ... {/with}<component>
src/components/*.ree<page-hero ...hero></page-hero>, <md-text type="h2" as="h2">, <link-button href="/docs">...obj spread
<page-hero ...hero>Locale variants
name.en-us.ree / name.sl-si.ree resolve per locale, falling back to name.reeabout.sl-si.reeBuilt-in helpers
localized_path(), url(), md(), nav_label(), is_current(), display_currency(), ...helpers.* (e.g. helpers.md_inline(), helpers.highlight())Key takeaways
No production Node story exists today - both projects use Bun-native primitives end-to-end: Bun.serve (HTTP + WebSockets), SQL (DB), S3Client, RedisClient, Cookie, Bun.Archive, Bun.Image, Bun.markdown. Porting to Node means swapping ~10 Bun globals for npm packages; the biggest rewrites are Bun.serve→http, SQL→per-dialect drivers (SQLite via the built-in node:sqlite, MySQL via mysql2, Postgres via pg), and Bun.file/Bun.write→fs/promises.
Node-compat is used heavily already - process.*, node:path, crypto.randomUUID and node:os run natively under Bun, so those parts would be free on Node.
Named from "bun" imports (Bun 1.2+ style) carry the production data layer: SQL, S3Client, RedisClient, redis, Cookie, BunRequest, file, write, spawnSync, Glob, ServerWebSocket.