Simple Web Server
Build a long-running HTTP server on WendyOS using TypeScript
Building a Web Server with Express
Source Code: The complete source code for this example is available at github.com/wendylabsinc/samples/node-typescript/simple-server
Often times you'll want a long-running server where you can make HTTP or WebSocket calls to your WendyOS device. This allows your device to accept incoming requests and respond to them, making it easy to build interactive applications or APIs that can be accessed from other devices on your network.
This guide shows you how to build a web server using Node.js with Express, the most popular and battle-tested framework for Node.js.
Prerequisites
- Wendy CLI installed on your development machine
- Node.js 22 or later installed
- A WendyOS device plugged in over USB or connectable over Wi-Fi
Setting Up Your Project
Initialize the Project
Start from the Wendy Express template:
wendy init simple-server --target wendyos --language node --template simple-api --var APP_ID=simple-server --var PORT=3000 --assistant skip --git-init no
cd simple-server

The template creates package.json, tsconfig.json, src/index.ts, Dockerfile, and wendy.json. The sections below explain the generated server and the pieces you can customize.
Run on WendyOS
wendy run

Wendy will build the app, ask you to select a device if one is not already configured, deploy the app, and print the URL or run output.
Code Breakdown
Generated package.json
Review the generated package.json:
{
"name": "simple-server",
"version": "0.1.0",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts"
},
"dependencies": {
"express": "^4"
},
"devDependencies": {
"@types/express": "^5",
"@types/node": "^22",
"typescript": "^5.7",
"tsx": "^4"
},
"engines": {
"node": ">=22"
}
}Generated tsconfig.json
Review the generated tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}Generated Web Server
The generated src/index.ts contains the server logic:
import express, { Request, Response } from "express";
const app = express();
const port = 3000;
const hostname = process.env.WENDY_HOSTNAME || "0.0.0.0";
app.use(express.json());
app.get("/", (_req: Request, res: Response) => {
console.log("Received request: GET /");
res.json({ message: "hello-world" });
});
app.get("/health", (_req: Request, res: Response) => {
res.json({ status: "ok" });
});
interface CreateItemBody {
name: string;
price: number;
}
interface ItemResponse {
id: number;
name: string;
price: number;
}
app.post(
"/items",
(req: Request<{}, ItemResponse, CreateItemBody>, res: Response) => {
console.log(`Received request: POST /items - ${req.body.name}`);
const item: ItemResponse = {
id: 1,
name: req.body.name,
price: req.body.price,
};
res.status(201).json(item);
}
);
app.listen(port, () => {
console.log(`Server running on http://${hostname}:${port}`);
});Multiple Routes: This example includes three routes:
GET /- Returns{"message":"hello-world"}GET /health- Returns{"status":"ok"}for health checksPOST /items- Creates an item from a JSON body and echoes it back
Generated Dockerfile
The generated project includes a multi-stage Dockerfile:
FROM node:22-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
FROM node:22-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]Generated wendy.json
The generated wendy.json requests the network entitlement and already includes a readiness probe plus a postStart hook that opens your browser to the device when the server is ready:
{
"appId": "simple-server",
"platform": "linux",
"version": "0.1.0",
"entitlements": [
{
"type": "network"
}
],
"readiness": {
"tcpSocket": { "port": 3000 },
"timeoutSeconds": 30
},
"hooks": {
"postStart": {
"cli": "wendy utils open-browser http://${WENDY_HOSTNAME}:3000"
}
}
}Run Again on WendyOS
Deploy your containerized web server to your WendyOS device:
wendy runTest Your Server on WendyOS
After deploying your server to your WendyOS device, you can test it from your development machine.
The generated wendy.json already includes a readiness probe and a postStart hook, so Wendy automatically opens your browser to the device once the server is ready. You can also open it manually and navigate to:
http://wendyos-true-probe.local:3000Replace the hostname: Each WendyOS device has a unique hostname. Replace wendyos-true-probe with your device's actual hostname shown in the CLI output. In addition, don't forget to add the port to the hostname.
You should see the following JSON response:
{"message":"hello-world"}This confirms your web server is successfully running on your WendyOS device and accepting requests from your network.
Verifying Deployment
You can verify the server container is running by listing the applications on your device:
wendy device apps list
You should see your containerized web server listed among the running applications.
Learn More
Express is the most popular Node.js web framework. Learn more at https://expressjs.com/
Alternative Frameworks
While this guide covers Express, many other excellent TypeScript web frameworks will work similarly on WendyOS:
- Fastify: Fast and low overhead web framework
- Koa: Expressive middleware framework by the Express team
- Hono: Works on Node.js, Bun, Deno, and edge runtimes
- NestJS: Progressive framework for building scalable server-side applications
All of these frameworks can be containerized and deployed to WendyOS following the same Docker-based approach outlined in this guide.
Next Steps
Now that you have a basic web server running:
- Add more routes to handle different endpoints
- Implement PUT and DELETE routes for a full REST API
- Add middleware for logging, authentication, and error handling
- Parse query parameters
- Implement WebSocket support for real-time communication
- Connect to databases and external APIs