Skip to main content

Build a Custom UI (Gateway-Only)

This guide shows how to build your own frontend (React, Vue, Svelte, plain JS) that talks to NoETL Gateway only.

Architecture Contract

Client UI must call:

  • POST /api/auth/login (exchange Auth0 token for gateway session token)
  • POST /api/auth/validate (validate session token)
  • POST /graphql (playbook execution and GraphQL operations)
  • GET /events (SSE stream for async playbook callbacks)
  • /{gateway}/noetl/* (authenticated REST proxy to NoETL /api/*)

Client UI must not call NoETL server directly (for example :8082).

Environment Variables

Recommended frontend env settings:

VITE_GATEWAY_URL=https://gateway.mestumre.dev
VITE_AUTH0_DOMAIN=<your-auth0-domain>
VITE_AUTH0_CLIENT_ID=<your-auth0-client-id>
VITE_AUTH0_REDIRECT_URI=https://mestumre.dev/gateway/login

Developer End-to-End Setup (UI + Playbooks)

Use this loop when building your own UI and testing your own playbooks against Gateway.

0) Connect local tools to Gateway and NoETL server

If Gateway and NoETL run in GKE, port-forward both services:

# Terminal 1: Gateway for UI/API calls
kubectl port-forward -n gateway svc/gateway 8091:80

# Terminal 2: NoETL server for register/exec CLI calls
kubectl port-forward -n noetl svc/noetl 8082:8082

Set UI and CLI targets:

export VITE_GATEWAY_URL=http://localhost:8091
export NOETL_SERVER_URL=http://localhost:8082

Start your frontend locally:

cd <your-ui-project>
npm install
npm run dev

Optional context setup:

noetl context add gke-dev --server-url=http://localhost:8082 --runtime=distributed --set-current
noetl context current

1) Create and register credentials (with noetl binary)

Example credential file (credentials/pg_dev.json):

mkdir -p credentials
{
"name": "pg_dev",
"type": "postgres",
"description": "Developer Postgres credential",
"tags": ["dev", "postgres"],
"data": {
"db_host": "postgres.postgres.svc.cluster.local",
"db_port": "5432",
"db_user": "demo",
"db_password": "demo",
"db_name": "demo_noetl"
}
}

Save this JSON to credentials/pg_dev.json.

Register credential:

noetl --server-url "$NOETL_SERVER_URL" register credential --file credentials/pg_dev.json

Verify:

noetl --server-url "$NOETL_SERVER_URL" catalog list Credential

2) Create and register your playbook

Example playbook (playbooks/dev/dev_gateway_check.yaml):

mkdir -p playbooks/dev
apiVersion: noetl.io/v2
kind: Playbook
metadata:
name: dev_gateway_check
path: dev/dev_gateway_check
description: Verify custom UI -> Gateway -> NoETL execution path

workload:
query: "select 1 as ok"

workflow:
- step: db_check
tool:
kind: postgres
auth: pg_dev
query: "{{ workload.query }}"

Save this YAML to playbooks/dev/dev_gateway_check.yaml.

Register playbook:

noetl --server-url "$NOETL_SERVER_URL" register playbook --file playbooks/dev/dev_gateway_check.yaml

Verify:

noetl --server-url "$NOETL_SERVER_URL" catalog list Playbook

3) Run playbook directly with noetl binary

noetl --server-url "$NOETL_SERVER_URL" exec dev/dev_gateway_check -r distributed --json

Check status by execution ID:

noetl --server-url "$NOETL_SERVER_URL" status <EXECUTION_ID> --json

4) Run the same playbook from your custom UI through Gateway

  1. Start your frontend dev server (React/Vue/Svelte/etc.) with VITE_GATEWAY_URL set.
  2. Authenticate via Auth0 and exchange token at POST /api/auth/login.
  3. Open SSE GET /events and capture clientId.
  4. Execute GraphQL mutation:
mutation ExecuteAsync($vars: JSON!, $clientId: String!) {
executePlaybook(
name: "dev/dev_gateway_check"
variables: $vars
clientId: $clientId
) {
executionId
requestId
status
}
}

Variables:

{
"vars": {
"query": "select now() as ts"
},
"clientId": "<SSE_CLIENT_ID>"
}

This validates the full path: Custom UI -> Gateway (/graphql) -> NoETL playbook -> SSE callback.

1) Login and Session Token

After obtaining Auth0 ID token in your UI:

const gatewayBase = import.meta.env.VITE_GATEWAY_URL;

const loginRes = await fetch(`${gatewayBase}/api/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
auth0_token: idToken,
auth0_domain: import.meta.env.VITE_AUTH0_DOMAIN,
}),
});

const loginData = await loginRes.json();
const sessionToken = loginData.session_token;

Validate and reuse the token:

await fetch(`${gatewayBase}/api/auth/validate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ session_token: sessionToken }),
});

2) Execute Playbook via GraphQL

const query = `
mutation Execute($name: String!, $vars: JSON!) {
executePlaybook(name: $name, variables: $vars) {
executionId
status
}
}`;

const gqlRes = await fetch(`${gatewayBase}/graphql`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${sessionToken}`,
},
body: JSON.stringify({
query,
variables: {
name: "api_integration/amadeus_ai_api",
vars: { origin: "SFO", destination: "JFK" },
},
}),
});

Open SSE first:

const sse = new EventSource(
`${gatewayBase}/events?session_token=${encodeURIComponent(sessionToken)}`
);

Read init message to capture clientId, then call GraphQL with clientId:

mutation ExecuteAsync($name: String!, $vars: JSON!, $clientId: String!) {
executePlaybook(name: $name, variables: $vars, clientId: $clientId) {
executionId
requestId
status
}
}

Gateway sends callback events on SSE when playbook completes.

4) Use REST Proxy for NoETL APIs

Gateway forwards /noetl/<path> to NoETL /api/<path>.

Example:

await fetch(`${gatewayBase}/noetl/catalog/list`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${sessionToken}`,
},
body: JSON.stringify({ resource_type: "Playbook" }),
});

Production Checklist

  • UI origin is included in gateway CORS (for example https://mestumre.dev)
  • UI calls https://gateway.mestumre.dev only
  • Cloudflare cache bypass rule for:
    • gateway.mestumre.dev/api/*
    • OPTIONS requests
  • Authorization: Bearer <session_token> added for /graphql and /noetl/*

DNS Quick Copy

Use one of these Cloudflare layouts:

Option A (GUI on apex mestumre.dev):

  • A @ -> 35.226.162.30 (Proxied)
  • A gateway -> 34.46.180.136 (Proxied)

Option B (keep existing apex CNAME, GUI on subdomain):

  • CNAME @ -> c.storage.googleapis.com (Proxied)
  • A gui -> 35.226.162.30 (Proxied)
  • A gateway -> 34.46.180.136 (Proxied)