hello-world example #

View this page as Markdown

A small HTTP service with a health check and a hello-world route.

These files come from the same source as the scaffold command. They are the starter files before the command adds library packages, application settings, and installed dependencies.

With a shell #

Run this in a new project's root:

turnzero scaffold --template hello-world

Follow the command's next steps. It writes the application in app/, takes the required library packages, installs dependencies, and runs the tests. The turnzero command explains setup and options.

From a chat #

Follow Deploying from a chat. Read the files below, supply the library versions in the manifest, and send the application files through deploy_files.

The scaffold can bind its manifest to an application with --application <application id>. From a chat, name the application explicitly in submit_manifest and deploy_files. An uploaded manifest may omit application; if present, it must match that application.

Keep the application paths shown in the table. The download URLs end in .txt so a browser reads the code instead of running it; that suffix is not part of the application filename. Each file is also reproduced below for tools reading this page through MCP.

The template manifest starts with an empty packages list. For each @turnzero/ dependency, read its library entry and add its current name and version to that list. Keep the file:lib/… dependencies, and omit those library folders from deploy_files so Cloud fills them. Do not send placeholder versions or hashes.

Files #

Paths are relative to the application root—the folder containing package.json. deploy_files uses these paths without an app/ prefix.

Application path Read source
.gitignore Text
AGENTS.md Text
manifest.json Text
package.json Text
src/main.mjs Text
src/server.mjs Text
test/hello.test.mjs Text

File contents #

.gitignore #

node_modules/
.env
.env.*

AGENTS.md #

# This service runs on Turn Zero Cloud

The service in this folder is hosted on Turn Zero Cloud. It started from the platform's `hello-world` template: the smallest Node.js service that deploys, with a health route and one API route. This file describes the template the service started from: keep it current as the service changes.

It started with one API route, `GET /api/hello`, which answers hello world.

## Where the rules are

Turn Zero Cloud serves its rules to a coding tool through its MCP server, `turnzero-cloud`, and they change with the platform, so none is repeated here.

- Where the server is not connected, connect it first. The platform's page "Connect your AI tool" says how.
- Before you change or deploy this service, read the server's `deploy` skill.

## What you run here

- The tests: `npm test`, in this folder.
- A local run and a deploy: the `deploy` skill gives each step and the line to run, in this order: `submit_manifest`, the local run, `deploy`.

manifest.json #

{
  "manifest_version": 1,
  "services": [],
  "health": "/health",
  "region": "usa",
  "egress": [],
  "audience": { "kind": "public" },
  "packages": []
}

package.json #

{
  "name": "hello-world",
  "private": true,
  "type": "module",
  "scripts": {
    "start": "node src/main.mjs",
    "test": "node --test"
  }
}

src/main.mjs #

// src/main.mjs, the process entry the start script runs
import { createAppServer } from './server.mjs';

const port = Number(process.env.PORT ?? 8080);
createAppServer().listen(port, () => console.log(`listening on ${port}`));

src/server.mjs #

// src/server.mjs
import { createServer } from 'node:http';

export function createAppServer() {
  return createServer((request, response) => {
    // The deployed runtime's abort signal: end the response when it fires.
    request.signal?.addEventListener('abort', () => response.destroy(), { once: true });
    const send = (status, type, body) => {
      if (response.destroyed) return;
      response.writeHead(status, { 'content-type': type });
      response.end(body);
    };
    // A target that does not parse as a URL names no route, so it is answered 404 below.
    let pathname = null;
    try {
      pathname = new URL(request.url, 'http://localhost').pathname;
    } catch {}
    if (request.method === 'GET' && pathname === '/health') return send(200, 'application/json', JSON.stringify({ ok: true }));
    if (request.method === 'GET' && pathname === '/api/hello') return send(200, 'text/plain; charset=utf-8', 'hello world');
    send(404, 'application/json', JSON.stringify({ error: 'not_found' }));
  });
}

test/hello.test.mjs #

// test/hello.test.mjs
import assert from 'node:assert/strict';
import { after, before, test } from 'node:test';
import { createAppServer } from '../src/server.mjs';

// The server on a port of this machine, for the file.
let server;
let base;

before(async () => {
  server = createAppServer();
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  base = `http://127.0.0.1:${server.address().port}`;
});

after(async () => {
  await new Promise((resolve) => server.close(resolve));
});

test('GET /health returns 200', async () => {
  const response = await fetch(`${base}/health`);
  assert.equal(response.status, 200);
  assert.deepEqual(await response.json(), { ok: true });
});

test('GET /api/hello returns the text hello world', async () => {
  const response = await fetch(`${base}/api/hello`);
  assert.equal(response.status, 200);
  assert.equal(response.headers.get('content-type'), 'text/plain; charset=utf-8');
  assert.equal(await response.text(), 'hello world');
});