Express Web Server
A small Express 5 blog: a JSON API, server-rendered article pages, and a static frontend. Express comes straight from npm with no fork and no shim in your code.
One src/index.ts serves on all four runtimes with no branch anywhere in it, GJS included. If you already have an Express service, this is what porting it looks like: nothing.
Run it
Section titled “Run it”It declares all four runtimes, so pick the one you have:
gjsify showcase express-webserver --runtime gjsgjsify showcase express-webserver --runtime nodegjsify showcase express-webserver --runtime bungjsify showcase express-webserver --runtime denoThere is no window. It listens on port 3000, on a GLib main loop under gjs and on the host loop under the other three. Open http://localhost:3000/ in a browser, or hit the API:
curl http://localhost:3000/api/posts# → {"count":…,"posts":[…]}
curl http://localhost:3000/api/posts/hello-gjs
curl http://localhost:3000/api/runtime# → {"runtime":"GJS","platform":"linux","version":"…","time":"…"} (--runtime gjs)# → {"runtime":"Node.js","platform":"linux","version":"…","time":"…"} (--runtime node)Set PORT to listen somewhere else.
/api/runtime is the interesting one. It reports which JavaScript engine is serving the request, from runtimeName in @gjsify/runtime. Run the same source on another runtime and that one field changes. Nothing else does, including the routes, the rendered pages and the JSON.
From showcases/node/express-webserver/ in a checkout you can build both bundles and run them side by side:
gjsify run buildgjsify run start # the --app gjs bundlegjsify run start:node # the --app node bundleSame src/index.ts, two bundles: dist/index.gjs.js and dist/index.node.mjs. The second is what node, bun and deno all run.
What it uses
Section titled “What it uses”The build target decides which server implementation Express lands on, and this is the one place the four runtimes genuinely diverge.
Under --app node the node: imports stay as they are, so Express gets the host runtime’s own http, stream, events and url. Under --app gjs there are no such builtins, so gjsify build rewrites each import to a GNOME-backed package:
| Package | What Express needs it for |
|---|---|
@gjsify/http | http.createServer(), Server.listen(), IncomingMessage and ServerResponse |
@gjsify/stream | req.pipe(res) and body parsing |
@gjsify/events | EventEmitter, which Express and its middleware lean on throughout |
@gjsify/url | URL parsing in the route matcher |
| libsoup 3.0 | The TCP listener and HTTP/1.1 parser under @gjsify/http |
You install none of them, and you import none of them. How it works walks through the rewrite.
Copy the structure
Section titled “Copy the structure”showcases/node/express-webserver/
src/index.tsis the whole app: middleware, the JSON API, the server-rendered pages andapp.listen().src/data/posts.tsholds the in-memory post fixtures the API and the pages both read.src/public/is the static frontend, served withexpress.static().
There is no shell to write here, because a server has no UI. The only per-target file is the bundle.
Related
Section titled “Related”- Node API packages lists every Node builtin gjsify implements and the GNOME library behind each one.
- Distributing GJS apps covers shipping a server bundle once it works.