# Deploy a React frontend and Express API together on Yeeted

The **project-in-project** sample: two services deployed together with
`deploy_group`, exercising the platform's group machinery end to end.

```
notes-group
├── api   Express + managed postgres (self-migrating) + redis cache
└── web   Vite + React SPA, served by nginx, talking to the api
```

## Why this shape matters

Deployed with `deploy_group(group_id: "notes", services: [api, web])`, the
platform names the services `notes-api` and `notes-web`, and injects
`.yeeted-group.json` into each build. For the static `web` service that means
its nginx **proxies `/api/` to `notes-api` over the owner mesh** - so the
browser calls `/api/notes` as a relative URL and it works, with no baked-in
API hostname, no CORS, and no rebuild when the api redeploys. The web code
therefore contains exactly zero configuration about where the api lives.

Deploy order is dependency order: `api` first, then `web`.

## What each service demonstrates

- **api**: `DATABASE_URL` postgres with startup migrations tracked in
  `schema_migrations`; a redis cache (`REDIS_URL`) on the note-list read path,
  invalidated on writes; in-memory fallback for zero-service dev and unit
  tests.
- **web**: a Vite SPA the build service detects and serves via nginx with the
  SPA fallback; all api access through relative `/api/...` paths.

## Tests

- `cd api && npm test` - unit, node:test, no services
- `cd web && npm test` - unit tests on the pure client logic
- `NOTES_API_URL=https://notes-api... node --test test/integration.test.js`
- `./api/smoke.sh https://notes-web...` - smoke through the WEB service,
  proving the mesh proxy path, not just the api

## Run locally

Use Node.js 20 or newer. From `node/notes-group/api`:

```sh
npm ci
npm test
npm start
```

In another terminal, from `node/notes-group/web`:

```sh
npm ci
npm test
npm run dev
```

Open the URL printed by Vite. Its development proxy forwards `/api` to
`http://127.0.0.1:3000`. Without connection strings, the API uses memory and
its cache is disabled. Set `DATABASE_URL` for PostgreSQL and `REDIS_URL` for
Redis when deliberately testing those paths.

## Deploy the two services

Follow the [deployment walkthrough](getting-started.md). Ask your agent:

> Deploy notes-group as a Yeeted development group with api and web members.
> Use api/ as the API source root and web/ as the frontend source root. Attach
> PostgreSQL to the API. Preserve group topology so the frontend proxies /api
> to its sibling API. Deploy the API before the web service, then verify note
> creation, tag filtering, and deletion through the web service URL.

Do not upload the parent folder as one Node application. The API listens on
port 3000; the frontend becomes static files served by nginx. Browser requests
use relative paths such as `/api/notes`. Test that path through the web URL,
not just the API URL, to exercise the group routing.

From the `notes-group` directory, with `PREVIEW_URL` set to the web URL:

```sh
sh api/smoke.sh "$PREVIEW_URL"
```

The script lists notes, creates one tagged `smoke`, finds a matching tag, and
deletes the created note. For a stronger manual check, confirm the returned
note ID appears in the filtered response. From `api/`, use
`NOTES_API_URL="$API_PREVIEW_URL" node --test test/integration.test.js`
to check the API directly as well.

## What to change before real use

This is a shared notes demo without authentication or per-user ownership. Do
not put private notes into it. Cache behavior needs a real Redis instance;
the general platform examples suite does not test that path. Startup SQL
migrations are not coordinated across replicas, so arrange one migration
runner before scaling the API.

[All examples](index.md) · [Deploy an example](getting-started.md)
