# Front, back e banco

Objetivo: um projeto com três serviços.

```mermaid
flowchart LR
  U[Internet] --> W[web: Next.js, public]
  W -- API_URL=${{api.PRIVATE_URL}} --> A[api: Go, private]
  A -- DATABASE_URL=${{db.DATABASE_URL}} --> D[(db: Postgres)]
```

Escopos do token: `projects:write`, `databases:write`, `env:write`, `deploy`, `projects:read`. As variáveis do shell (`H`, `P`, `DB`, `API`, `WEB`) vivem no mesmo bloco; ao repetir só um passo, recapture os ids com `GET` (lista de projetos e serviços). Os ids são opacos (ULID de 26 caracteres); os exemplos mostram o formato real.

O caminho completo para código **próprio** é: banco, back, front (passos 1 a 3), **enviar o código de cada serviço** (passo 4), **deploy do grupo** (passo 5) e **polling** (passo 6). Se você já tem imagens publicadas, use a variante com imagens, no fim do passo 5.

## 1. Criar o projeto e o banco

`POST .../databases` cria um serviço `kind=database`. Os motores são `postgres`, `mariadb`, `sqlite`; `quota_mb` é a cota do banco.

```bash
SUF=$(date +%s)-$RANDOM
H="Authorization: Bearer $NEBULAR_TOKEN"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" -H "$H" -H "Content-Type: application/json" \
  -d "{\"name\": \"loja-$SUF\"}" | jq -r .id)
curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases" -H "$H" -H "Content-Type: application/json" \
  -H "Idempotency-Key: fbd-db-$SUF" \
  -d '{"name": "db", "quota_mb": 1024, "engine": "postgres"}'
```
```json
{"id": "01j9zkff8y1dmcwa49fanx9qak", "name": "db", "kind": "database", "engine": "postgres", "status": "provisioning"}
```

Em erro: `503 unavailable` (repita com backoff), `422 quota_exceeded` (reduza `quota_mb` ou veja [Limites e cotas](/operacao/limites-e-cotas/)), `403` (falta `databases:write`).

## 2. Criar o back (Go, privado) e ligar ao banco

O back não tem rota pública (`private`); o `DATABASE_URL` vem do banco por referência.

```bash
SUF=$(date +%s)-$RANDOM
H="Authorization: Bearer $NEBULAR_TOKEN"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" -H "$H" -H "Content-Type: application/json" \
  -H "Idempotency-Key: fbd2-proj-$SUF" -d "{\"name\": \"loja2-$SUF\"}" | jq -r .id)
API=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/services" -H "$H" -H "Content-Type: application/json" \
  -H "Idempotency-Key: fbd2-api-$SUF" \
  -d '{"name": "api", "kind": "app", "visibility": "private", "port": 8080,
       "source": {"type": "upload"}, "build": {"builder": "go"}, "healthcheck_path": "/healthz"}' | jq -r .id)
curl -sS -X PUT "$NEBULAR_API/v1/projects/$P/services/$API/variables/DATABASE_URL" -H "$H" \
  -H "Content-Type: application/json" -d '{"value": "${{db.DATABASE_URL}}"}'
```

A referência só é resolvida no deploy. Se `db` não existir no projeto, o `POST` do deploy responde `422 unresolved_reference` (nada é criado) com `details.references`.

## 3. Criar o front (Next.js, público) apontando para o back

Todo projeto já nasce com um serviço `web`, **público** e com `slug` e `public_url` gerados (por exemplo `web-br5pg0`): o `PATCH` abaixo o adapta (fonte, build, porta) em vez de criar outro. Para um front **privado** (só alcançável na rede do projeto), mande `{"visibility": "private"}`; o `slug` e a URL pública deixam de existir. O `PATCH` com `"visibility": "public"` e sem `slug` devolve o serviço público com um `slug` derivado do nome.

```bash
SUF=$(date +%s)-$RANDOM
H="Authorization: Bearer $NEBULAR_TOKEN"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" -H "$H" -H "Content-Type: application/json" \
  -H "Idempotency-Key: fbd3-proj-$SUF" -d "{\"name\": \"loja3-$SUF\"}" | jq -r .id)
curl -sS -X POST "$NEBULAR_API/v1/projects/$P/services" -H "$H" -H "Content-Type: application/json" \
  -d '{"name": "api", "kind": "app", "visibility": "private", "port": 8080, "source": {"type": "upload"}, "build": {"builder": "go"}}' >/dev/null
WEB=$(curl -sS -X PATCH "$NEBULAR_API/v1/projects/$P/services/web" -H "$H" -H "Content-Type: application/json" \
  -d "{\"visibility\": \"public\", \"slug\": \"loja3-$SUF\", \"port\": 3000,
       \"source\": {\"type\": \"upload\"}, \"build\": {\"builder\": \"node\"}}" | jq -r .id)
curl -sS -X PUT "$NEBULAR_API/v1/projects/$P/services/$WEB/variables/API_URL" -H "$H" \
  -H "Content-Type: application/json" -d '{"value": "${{api.PRIVATE_URL}}"}'
```

No código do Next.js, chame o back **no servidor** (route handler ou server component) com `process.env.API_URL`: a URL é privada e não funciona no browser. Segredos nunca em variável `NEXT_PUBLIC_*`.

## 4. Enviar o código (upload) e implantar cada serviço

Serviço com `source.type = "upload"` só tem o que implantar depois que **recebe um arquivo**. Não existe rota de "enviar sem implantar": o envio é o próprio deploy do serviço (`POST .../services/{s}/deployments` com `multipart/form-data`, campo `file`, `.tar.gz` ou `.zip`). Por isso a ordem é:

1. espere o banco ficar `ready` (`GET .../databases/{db}`);
2. envie o código do **back** e espere `ready`;
3. envie o código do **front** e espere `ready`.

O exemplo cria tudo do zero com dois pacotes mínimos (troque pelos seus). O arquivo recebido fica guardado como a fonte atual do serviço.

```bash
SUF=$(date +%s)-$RANDOM
H="Authorization: Bearer $NEBULAR_TOKEN"
espera() { # espera() URL: polling com backoff até ready; falha em qualquer terminal ruim
  local delay=2 st
  for i in $(seq 1 40); do
    st=$(curl -sS "$1" -H "$H" | jq -r .status)
    case "$st" in
      ready) return 0 ;;
      failed|blocked|canceled|superseded) echo "terminou em $st" >&2; return 1 ;;
    esac
    sleep "$delay"; delay=$(( delay * 2 > 30 ? 30 : delay * 2 ))
  done
  return 1
}
API_URL_BASE="$NEBULAR_API/v1/projects"

P=$(curl -sS -X POST "$API_URL_BASE" -H "$H" -H "Content-Type: application/json" \
  -d "{\"name\": \"loja7-$SUF\"}" | jq -r .id)
DB=$(curl -sS -X POST "$API_URL_BASE/$P/databases" -H "$H" -H "Content-Type: application/json" \
  -H "Idempotency-Key: fbd7-db-$SUF" -d '{"name": "db", "engine": "postgres", "quota_mb": 1024}' | jq -r .id)
API=$(curl -sS -X POST "$API_URL_BASE/$P/services" -H "$H" -H "Content-Type: application/json" \
  -d '{"name": "api", "visibility": "private", "port": 8080, "source": {"type": "upload"},
       "build": {"builder": "go"}, "healthcheck_path": "/healthz"}' | jq -r .id)
WEB=$(curl -sS -X PATCH "$API_URL_BASE/$P/services/web" -H "$H" -H "Content-Type: application/json" \
  -d '{"port": 3000, "source": {"type": "upload"}, "build": {"builder": "node"}}' | jq -r .id)
curl -sS -X PUT "$API_URL_BASE/$P/services/$API/variables/DATABASE_URL" -H "$H" \
  -H "Content-Type: application/json" -d '{"value": "${{db.DATABASE_URL}}"}' >/dev/null
curl -sS -X PUT "$API_URL_BASE/$P/services/$WEB/variables/API_URL" -H "$H" \
  -H "Content-Type: application/json" -d '{"value": "${{api.PRIVATE_URL}}"}' >/dev/null

# pacotes mínimos (no uso real: o código do seu back e do seu front)
T=$(mktemp -d)
printf 'module api\n\ngo 1.22\n' > "$T/go.mod"
printf 'package main\n\nfunc main() {}\n' > "$T/main.go"
tar -czf "$T/api.tgz" -C "$T" go.mod main.go
printf '{"name":"web","scripts":{"start":"node server.js"}}\n' > "$T/package.json"
printf 'require("http").createServer((q,r)=>r.end("ok")).listen(3000)\n' > "$T/server.js"
tar -czf "$T/web.tgz" -C "$T" package.json server.js

espera "$API_URL_BASE/$P/databases/$DB"
D=$(curl -sS -X POST "$API_URL_BASE/$P/services/$API/deployments" -H "$H" \
  -H "Idempotency-Key: fbd7-api-$SUF" -F "file=@$T/api.tgz" | jq -r .id)
espera "$API_URL_BASE/$P/services/$API/deployments/$D"
D=$(curl -sS -X POST "$API_URL_BASE/$P/services/$WEB/deployments" -H "$H" \
  -H "Idempotency-Key: fbd7-web-$SUF" -F "file=@$T/web.tgz" | jq -r .id)
espera "$API_URL_BASE/$P/services/$WEB/deployments/$D"
curl -sS "$API_URL_BASE/$P/services/$WEB/deployments/$D" -H "$H"
```

Ao enviar o arquivo, o `source_ref` do deploy mostra `upload:<nome-do-arquivo>`. Em erro: `400 invalid_request` (arquivo vazio, grande demais ou sem o campo `file`), `409 deploy_in_progress` (já há um deploy do serviço rodando: acompanhe-o), `422 unresolved_reference` (a variável aponta para algo que não existe).

## 5. Deploy do grupo

`POST /v1/projects/{p}/deploy` reimplanta todos os serviços em ordem topológica das referências: `db`, depois `api`, depois `web`. Use-o **depois** do passo 4, por exemplo após mudar uma variável compartilhada ou referenciada. As regras para serviços `upload`:

- **Já recebeu um arquivo antes**: o grupo **reimplanta o último arquivo enviado** (o item vem com `deployment_id`).
- **Nunca recebeu arquivo (sem fonte)**: o item vem `skipped`, sem `deployment_id` e com `reason` (`sem fonte: nenhum arquivo foi enviado para este serviço`). O resto do grupo segue; se **tudo** é `skipped`, o grupo termina `ready` sem implantar nada. Citar o serviço em `services` não o pula: sem fonte, o deploy dele falha.
- Se o build de um serviço falhar, os dependentes ficam `blocked`; os já ativos continuam no ar.

O corpo é `{}` (ou `{"services": ["api", "web"]}` para um subconjunto) com `Content-Type: application/json`; também é aceito sem corpo e sem `Content-Type` (veja [Convenções](/comecar/convencoes-da-api/)).

```bash
# projeto novo: o web padrão é upload e ainda não recebeu arquivo
SUF=$(date +%s)-$RANDOM
H="Authorization: Bearer $NEBULAR_TOKEN"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" -H "$H" -H "Content-Type: application/json" \
  -d "{\"name\": \"loja8-$SUF\"}" | jq -r .id)
curl -sS -X POST "$NEBULAR_API/v1/projects/$P/deploy" -H "$H" -H "Content-Type: application/json" \
  -H "Idempotency-Key: fbd8-grp-$SUF" -d '{}'
```
```json
{"id": "01j9zk0dst6zttk3bamcgshaxf", "project_id": "01j9zkk07fhzv5zsmn996cdwpb", "status": "ready", "items": [{"service_id": "01j9zkff8y1dmcwa49fanx9qak", "status": "skipped", "layer": 0, "reason": "sem fonte: nenhum arquivo foi enviado para este serviço"}]}
```

Em erro: `unresolved_reference` ou `reference_cycle` (corrija as variáveis, veja [Variáveis e referências](/conectados/variaveis-e-referencias/)), `403` (falta `deploy`), `429` (espere `Retry-After`).

### Variante com imagens publicadas

Se o back e o front já são imagens, o grupo dispara tudo de uma vez, sem passo de upload. Fixe a versão por digest (`repo:tag@sha256:...`, veja [Imagem pública](/guia/imagem-publica/#fixar-por-digest-e-como-obter-o-digest)); o exemplo usa tags só para ser curto.

```bash
# preparo (passos 1 a 3): banco, back e front do projeto
SUF=$(date +%s)-$RANDOM
H="Authorization: Bearer $NEBULAR_TOKEN"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" -H "$H" -H "Content-Type: application/json" \
  -d "{\"name\": \"loja4-$SUF\"}" | jq -r .id)
curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases" -H "$H" -H "Content-Type: application/json" \
  -d '{"name": "db", "engine": "postgres", "quota_mb": 1024}' >/dev/null
API=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/services" -H "$H" -H "Content-Type: application/json" \
  -d '{"name": "api", "visibility": "private", "port": 8080,
       "source": {"type": "image", "ref": "ghcr.io/org/api:1.0"}, "healthcheck_path": "/healthz"}' | jq -r .id)
curl -sS -X PUT "$NEBULAR_API/v1/projects/$P/services/$API/variables/DATABASE_URL" -H "$H" \
  -H "Content-Type: application/json" -d '{"value": "${{db.DATABASE_URL}}"}' >/dev/null
WEB=$(curl -sS -X PATCH "$NEBULAR_API/v1/projects/$P/services/web" -H "$H" -H "Content-Type: application/json" \
  -d "{\"visibility\": \"public\", \"slug\": \"loja4-$SUF\", \"port\": 3000,
       \"source\": {\"type\": \"image\", \"ref\": \"ghcr.io/org/web:1.0\"}}" | jq -r .id)
curl -sS -X PUT "$NEBULAR_API/v1/projects/$P/services/$WEB/variables/API_URL" -H "$H" \
  -H "Content-Type: application/json" -d '{"value": "${{api.PRIVATE_URL}}"}' >/dev/null

curl -sS -X POST "$NEBULAR_API/v1/projects/$P/deploy" \
  -H "$H" -H "Content-Type: application/json" \
  -H "Idempotency-Key: fbd-deploy-$SUF" -d '{}'
```
```json
{"id": "01j9zk0dst6zttk3bamcgshaxf", "project_id": "01j9zkk07fhzv5zsmn996cdwpb", "status": "running", "items": [{"service_id": "01j9zkff8y1dmcwa49fanx9qak", "deployment_id": "01j9zkxg2h4dbcfe129fwypfsb", "status": "queued", "layer": 0}]}
```

## 6. Esperar com backoff (um GET para o grupo todo)

Acompanhe o **grupo** com `GET /v1/projects/{p}/group-deployments/{g}` (`G` = `id` devolvido pelo `POST .../deploy`): um único `GET` mostra o `status` do grupo (`running` até terminar; depois `ready`, `failed`, `canceled` ou `superseded`) e o `status` de cada item (`queued`, `building`, `deploying`, `ready`, `failed`, `blocked`, `skipped`). Não é preciso listar os deploys de cada serviço. Para o motivo de uma falha, pegue o `deployment_id` do item `failed` e leia o [log de build](/operacao/logs/).

```bash
# preparo: projeto com o front (imagem) e um deploy de grupo em andamento
SUF=$(date +%s)-$RANDOM
H="Authorization: Bearer $NEBULAR_TOKEN"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" -H "$H" -H "Content-Type: application/json" \
  -d "{\"name\": \"fbd5-$SUF\"}" | jq -r .id)
curl -sS -X PATCH "$NEBULAR_API/v1/projects/$P/services/web" -H "$H" -H "Content-Type: application/json" \
  -d '{"source": {"type": "image", "ref": "ghcr.io/org/app:1.0"}}' >/dev/null
G=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/deploy" -H "$H" \
  -H "Content-Type: application/json" -H "Idempotency-Key: fbd5-grp-$SUF" -d '{}' | jq -r .id)

delay=2
for i in $(seq 1 40); do
  st=$(curl -sS "$NEBULAR_API/v1/projects/$P/group-deployments/$G" -H "$H" | jq -r .status)
  echo "tentativa $i: $st"
  case "$st" in
    ready) break ;;
    failed|canceled|superseded) echo "grupo terminou em $st" >&2; exit 1 ;;
  esac
  sleep "$delay"; delay=$(( delay * 2 > 30 ? 30 : delay * 2 ))
done
[ "$st" = ready ]
curl -sS "$NEBULAR_API/v1/projects/$P/group-deployments/$G" -H "$H"
```

Estado `failed` no grupo: algum item falhou; leia o log de build dele. Item `blocked`: um serviço do qual ele depende falhou; corrija o dependente primeiro. Para acompanhar **um** serviço isolado, use `GET .../services/{s}/deployments/{d}` ([Deploys e status](/operacao/deploys-e-status/)).

## 7. Verificar

```bash
curl -sS -o /dev/null -w '%{http_code}\n' "https://loja3-1760000000.nebularws.com/"
```

Se der erro `5xx`, leia o [log de runtime](/operacao/logs/) do `web` e do `api`. Para voltar a versão, [rollback](/operacao/rollback/).

## Ver também

[Rede privada](/conectados/rede-privada/), [PostgreSQL](/bancos/postgres/), [Next.js](/guia/nodejs-next/), [Go](/guia/go/).
