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
- Start your frontend dev server (React/Vue/Svelte/etc.) with
VITE_GATEWAY_URLset. - Authenticate via Auth0 and exchange token at
POST /api/auth/login. - Open SSE
GET /eventsand captureclientId. - 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" },
},
}),
});
3) Async Results via SSE (Recommended)
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.devonly - Cloudflare cache bypass rule for:
gateway.mestumre.dev/api/*OPTIONSrequests
Authorization: Bearer <session_token>added for/graphqland/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)