# First preview with the CLI

Start a frontend and API from your terminal. Open the local URL, read logs, update the app, and stop it.

## Create the example

Use macOS with Node.js 22.23 or later. This example needs no Docker, MCP registration, or application packages.
Install the CLI:

```sh
npm install -g previewhost
```

For other installation choices, see [Installation](/installation/index.md). Create a new directory outside an existing Git repository:

```sh
mkdir previewhost-demo
cd previewhost-demo
```

Save this as `backend.mjs`:

```js
import { createServer } from 'node:http';

createServer((request, response) => {
  if (request.url !== '/message') { response.writeHead(404).end(); return; }
  response.writeHead(200, { 'content-type': 'application/json' });
  response.end(JSON.stringify({ message: 'Hello from the backend.' }));
}).listen(Number(process.env.PORT), process.env.HOST);
```

Save this as `frontend.mjs`:

```js
import { createServer } from 'node:http';

const page = `<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Previewhost demo</title>
<h1>Frontend + backend</h1>
<p id="message" role="status">Connecting…</p>
<script type="module">
  const message = document.querySelector('#message');
  try {
    const response = await fetch('/message');
    if (!response.ok) throw new Error('Backend unavailable');
    message.textContent = (await response.json()).message;
  } catch {
    message.textContent = 'Backend request failed.';
  }
</script>`;

createServer(async (request, response) => {
  if (request.url === '/message') {
    try {
      const reply = await fetch(new URL('/message', process.env.BACKEND_URL), {
        signal: AbortSignal.timeout(2000),
      });
      response.writeHead(reply.status, {
        'content-type': 'application/json', 'cache-control': 'no-store',
      });
      response.end(await reply.text());
    } catch {
      response.writeHead(503).end('Backend unavailable');
    }
  } else if (request.url === '/') {
    response.writeHead(200, { 'content-type': 'text/html' });
    response.end(page);
  } else response.writeHead(404).end();
}).listen(Number(process.env.PORT), process.env.HOST);
```

The frontend forwards `/message` to the backend. Browser requests use the frontend's origin, so this example needs no CORS configuration.

## Describe the services

Save this as `preview.yaml` beside the two server files:

```yaml
name: hello
type: environment
primary: frontend
services:
  backend:
    type: command
    cwd: .
    command: [node, backend.mjs]
    readyPath: /message
  frontend:
    type: command
    cwd: .
    command: [node, frontend.mjs]
    readyPath: /message
    env:
      BACKEND_URL: {service: backend}
```

`primary` selects the frontend for the environment URL.
The `service` binding supplies the backend URL and starts the frontend after the backend is ready.
Both servers use Previewhost's `PORT` and `HOST` values.

## Start and inspect

Check the configuration:

```sh
previewhost inspect
```

Start the preview:

```sh
previewhost start --allow-exec
```

`--allow-exec` permits these commands to run with your user permissions. It provides no sandbox.
The CLI finds or starts a persistent background owner for this directory.

Open the `url` from the JSON result. The page shows **Frontend + backend**, then **Hello from the backend.**
Readiness checks HTTP headers. The page check also covers the request from the frontend to the backend.

If the result says `starting`, wait for the returned attempt ID:

```sh
previewhost wait hello ATTEMPT_ID
```

Replace `ATTEMPT_ID` with the ID from the result. A wait timeout does not cancel startup.
To inspect services and logs in the browser, run `previewhost dashboard` in another terminal.

## Try a replacement

Change the backend message in `backend.mjs`. Then run:

```sh
previewhost replace
```

Reload the same URL after the replacement is ready. The new message appears.
These example servers do not reload source automatically. Development servers can have their own reload behavior.

## Stop the example

```sh
previewhost stop hello
previewhost shutdown
```

Stop ends the preview. Shutdown ends this project's owner and all its previews.
The source files remain. For a project with databases, stop also retains managed data.

Next, [write preview.yaml](/configuration/index.md) for your own application.
