diff --git a/sites/browserpod/src/content/docs/12-tutorials/00-expressjs.md b/sites/browserpod/src/content/docs/12-tutorials/00-expressjs.md
index 0e8fefcd..750648eb 100644
--- a/sites/browserpod/src/content/docs/12-tutorials/00-expressjs.md
+++ b/sites/browserpod/src/content/docs/12-tutorials/00-expressjs.md
@@ -28,6 +28,8 @@ It is a simple Express.js application that serves "hello world" over HTTP.
## NPM Project
+The package.json is very minimal. express is the only dependency:
+
```js title="package.json"
{
"name": "expressjs-tutorial",
@@ -44,6 +46,10 @@ It is a simple Express.js application that serves "hello world" over HTTP.
}
```
+The application itself is just a basic web server that responds with "hello world".
+
+It listens to tcp port 3000, which will be reachable via a Portal.
+
```js title="main.js"
const express = require("express");
const app = express();
@@ -60,6 +66,115 @@ app.listen(port, () => {
## BrowserPod setup: `index.html`
+The index.html file is more complex, so we will describe it in multiple steps,
+and leave out irrelevant boilerplate. you can see it in full at the end.
+
+### UI elements
+
+Our simple page has 3 main UI elements:
+
+```html title="index.html"
+
Waiting for portal...
+
+
+```
+
+The `url` div will be populated with the URL of the live view of our server.
+
+The `portal` iframe will contain an embedded view of our server.
+
+The `console` div will show the console output of our application.
+
+### Import and initialize BrowserPod
+
+```js
+import { BrowserPod } from "https://rt.browserpod.io/%BP_LATEST%/browserpod.js";
+
+const pod = await BrowserPod.boot({ apiKey: "your-api-key" });
+```
+
+This code imports BrowserPod and boots a Pod.
+
+You will need a valid API key with at least 10 tokens available for the boot to succeed.
+
+### Hook the pod to the UI
+
+```js
+// Create a terminal and hook it to the console div.
+const terminalDiv = document.getElementById("console");
+const terminal = await pod.createDefaultTerminal(terminalDiv);
+
+// Hook the portal to the preview iframe on creation
+const portalIframe = document.getElementById("portal");
+const urlDiv = document.getElementById("url");
+pod.onPortal(({ url, port }) => {
+ urlDiv.innerHTML = `Portal available at ${url} for local server listening on port ${port}`;
+ portalIframe.src = url;
+});
+```
+
+This code creates a Terminal to use for console output, and sets up a callback
+that populates the `url` div and `portal` iframe with the Portal data.
+
+### Copy the project files into the Pod's filesystem
+
+```js
+// Utility function to copy files from the HTTP server into the Pod's
+// filesystem
+async function copy_file(pod, path) {
+ const f = await pod.createFile("/" + path, "binary");
+ const resp = await fetch(path);
+ const buf = await resp.arrayBuffer();
+ await f.write(buf);
+ await f.close();
+}
+// Copy our project files
+await pod.createDirectory("/project");
+await copy_file(pod, "project/main.js");
+await copy_file(pod, "project/package.json");
+```
+
+This code copies the project files into the Pod's filesystem.
+
+In this case the files are served alongiside the index.html page, but you can
+get them from other sources, or embed them directly as strings.
+
+### Install dependencies
+
+```js
+// Install dependencies
+await pod.run("/npm/bin/npm.js", ["install"], {
+ terminal,
+ cwd: "/project",
+ echo: true,
+});
+```
+
+We finally execute some code in the Pod.
+
+This runs `npm install` to fetch the project's dependencies from the internet.
+
+You might want to bundle the `node_modules` directory and the `package-lock.json`
+file directly alongside the project files instead.
+
+### Run the application
+
+```js
+// Run the server
+await pod.run("/project/main.js", [], {
+ terminal,
+ cwd: "/project",
+ echo: true,
+});
+```
+
+This runs our application.
+
+Once the tcp socket starts listening, the `onPortal()` callback will execute,
+and the `hello world` will show up in the iframe.
+
+### Full code listing
+
```html title="index.html"
@@ -73,7 +188,7 @@ app.listen(port, () => {