> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.hyjal.cloud/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.hyjal.cloud/_mcp/server.

# Build a full-stack app

> Deploy the acme app end to end (a Vite front end and a Go API joined by same-origin mounts) with one hyjal.toml and one deploy.

This tutorial deploys the `acme` app end to end: a Vite front end (`web`) and a
Go API (`api`), joined by a mount so that `/api/*` on the front end forwards to
the API over in-process dispatch. One [`hyjal.toml`](/deploy/hyjal-toml)
describes both Resources; one `hyjal deploy` ships them together.

Time: about twenty minutes. You need the [`hyjal` CLI](/reference/cli), a
logged-in session, Node.js, and TinyGo for the [Go lane](/languages/go).

## The shape

The front end is public. The API is private and reachable only through the
mount. A request to `https://web--acme.hyjal.cloud/api/health` lands on `web`,
matches the `/api/*` mount, and dispatches to `api` in-process: same origin, no
CORS, requests traced end to end.

## 1. Lay out the repository

```
acme/
  hyjal.toml
  web/        # Vite front end
  api/        # Go API
```

## 2. Build the front end

Scaffold the front end under `web/` and add a call to the mounted API.

```js
// web/src/main.js
const res = await fetch("/api/health");
const data = await res.json();
document.querySelector("#status").textContent = data.status;
```

The front end calls `/api/health` on its own origin. The mount handles routing;
the browser never sees a cross-origin request.

## 3. Build the Go API

Write the API under `api/` as a `wasi:http` handler.

```go
// api/main.go
package main

import (
	"encoding/json"
	"net/http"

	"go.wasmcloud.dev/component/net/wasihttp"
)

func handler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("content-type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}

func main() {
	wasihttp.HandleFunc(handler)
}
```

## 4. Write the manifest

Declare both Resources in one manifest. The `mounts` field on `web` forwards the
`/api/*` prefix to `api`. Marking `api` as `public = false` keeps it off the
public internet; it is reachable only through the mount.

```toml
[project]
name = "acme"

[resource.web]
lane = "static"
build = "npm run build"
output = "dist"
workdir = "web"
spa = true
mounts = { "/api/*" = "api" }

[resource.api]
lane = "go"
public = false
workdir = "api"
run_limit_secs = 30
```

The `workdir` field points each Resource at its subdirectory. See
[Monorepos](/deploy/builds/monorepos) for larger layouts.

## 5. Deploy both Resources

`hyjal deploy` builds every Resource, uploads both artifacts, applies policy,
and flips routes atomically. The front end and API go live together.

```bash
$ hyjal deploy
Building resource web (lane: static, workdir: web)...
  npm run build ✓
  compressing assets (brotli, gzip) ✓
Building resource api (lane: go, workdir: api)...
  tinygo build -target=wasip2 ✓
Uploading artifacts...
  web  digest sha256:4d0b8a…1f77
  api  digest sha256:e2c96b…a530
Applying policy... ✓
Flipping routes... ✓

Deployed acme
  web  https://web--acme.hyjal.cloud
  api  (private, mounted at web:/api/*)
```

## 6. Verify the mount

Call the front end, then the mounted API path on the same origin:

```bash
$ curl https://web--acme.hyjal.cloud/api/health
{"status":"ok"}
```

The API responds through the front-end origin. A direct request to the private
API endpoint is refused, because `api` is not public:

```bash
$ curl -i https://api--acme.hyjal.cloud/health
HTTP/2 404
```

## 7. Trace a request end to end

Reload the front end and stream logs. The request to `web` and the dispatched
call to `api` are correlated.

```bash
$ hyjal logs -f
[16:41:02] web  GET  /               200   8ms
[16:41:02] web  GET  /api/health     200   3ms
[16:41:02] api  GET  /health         200   1ms
```

Drawing an edge between `web` and `api` on the [Board](/platform/the-board)
configures the same mount visually.

## What you built

* The `acme` app: a public `web` front end and a private `api` Go service.
* A `/api/*` mount joining them over same-origin in-process dispatch.
* One manifest and one deploy that ship both Resources atomically.

## Next steps

* Give the API a database: [Connect Postgres](/get-started/tutorials/connect-postgres)
* Store app state without an external DB: [Hyjal Tables tutorial](/get-started/tutorials/hyjal-tables-tutorial)
* Learn the mount model: [Full-stack apps](/app-types/full-stack-apps)