WendyOS Docs
Guides & TutorialsTutorialsTypeScript

Hello World in TypeScript

Create your first WendyOS application using TypeScript

Creating Your First TypeScript Application

This guide will walk you through creating your first WendyOS application using TypeScript. The Wendy template scaffolds a small long-running Express server that responds with {"message":"hello-world"}. It's a great way to prove that you can send your first app to your device and verify your development environment is set up correctly.

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

Containerized Deployment: TypeScript applications run inside containers on your WendyOS device. This guide shows you how to package the Node.js runtime for deployment with the Wendy CLI.

Creating the Project

Initialize the Project

Start from the Wendy TypeScript template:

wendy init hello-world --target wendyos --language node --template simple-api --var APP_ID=hello-world --var PORT=5001 --assistant skip --git-init no
cd hello-world
wendy init scaffolding the hello-world TypeScript project from the simple-api template

The template creates package.json, tsconfig.json, src/index.ts, Dockerfile, and wendy.json. Continue below to understand the generated project.

Run on WendyOS

wendy run
wendy run building and deploying the hello-world TypeScript app to a WendyOS deviceAnimated walkthrough of wendy run building, deploying, and starting the hello-world app

Wendy will build the app, ask you to select a device if one is not already configured, deploy the app, and show the run output. Because this is an HTTP server, the app keeps running and the generated postStart hook opens your browser to the device.

Code Breakdown

Generated Application

The template includes src/index.ts, a small Express server. The root route returns a JSON greeting, and there's a /health check plus an example POST /items route:

// src/index.ts
import express, { Request, Response } from "express";

const app = express();
const port = 5001;
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}`);
});

Generated package.json

The generated package.json includes build and start scripts, plus Express as the one runtime dependency:

{
  "name": "hello-world",
  "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

The generated tsconfig.json specifies the output directory and module settings:

{
  "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 Dockerfile

The generated project includes a multi-stage Dockerfile that builds the TypeScript and ships only the compiled output plus dependencies:

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 5001

CMD ["node", "dist/index.js"]

Generated wendy.json

The generated wendy.json requests the network entitlement, declares a TCP readiness check on the server port, and wires a postStart hook that opens your browser once the app is ready:

{
    "appId": "hello-world",
    "platform": "linux",
    "version": "0.1.0",
    "entitlements": [
        {
            "type": "network"
        }
    ],
    "readiness": {
        "tcpSocket": { "port": 5001 },
        "timeoutSeconds": 30
    },
    "hooks": {
        "postStart": {
            "cli": "wendy utils open-browser http://${WENDY_HOSTNAME}:5001"
        }
    }
}

Run Again on WendyOS

Deploy your containerized application to your WendyOS device:

wendy run

Verifying Deployment on Your Device

After deploying your application to a WendyOS device, you can verify it is running by listing the applications on your device:

wendy device apps list
✔︎ Searching for WendyOS devices [5.3s]
✔︎ Listing applications: True Probe [USB, Ethernet, LAN]
╭───────────────┬─────────┬─────────┬──────────╮
 App Version State Failures
├───────────────┼─────────┼─────────┼──────────┤
 hello-world 0.1.0 Running 0
╰───────────────┴─────────┴─────────┴──────────╯
wendy device apps list showing the hello-world app in the Running state

Expected State: The app shows as "Running" because it's a long-running HTTP server that stays up to serve requests. The "Failures" column showing 0 confirms it started cleanly. The generated postStart hook opens your browser to http://<device>:5001, where the root route returns {"message":"hello-world"}.

Next Steps

Now that you have a basic TypeScript application running:

  • Learn how to build a Simple Web Server with Express
  • Explore environment variables and configuration
  • Build more complex applications with networking and data processing
  • Integrate with WendyOS device features via HTTP APIs

On this page