A multi-tenant workflow automation platform built on nhost (Postgres + Hasura + Auth + Serverless Functions) with a Next.js frontend. Supports real-time execution tracking, LLM-powered steps, cross-org isolation, and approval gates.
┌─────────────────────────────────────────────────────────┐
│ Next.js Frontend │
│ (React + Apollo Client + nhost Auth + Subscriptions) │
└─────────────┬───────────────────────────┬───────────────┘
│ queries/mutations │ subscriptions (WSS)
▼ ▼
┌─────────────────────────────────────────────────────────┐
│ Hasura GraphQL Engine │
│ ┌──────────────────────┐ ┌────────────────────┐ │
│ │ Layer 1 Permissions │ │ Actions (→ Funcs) │ │
│ │ (org-scoped RLS) │ │ Event Triggers │ │
│ └──────────────────────┘ └────────────────────┘ │
└─────────────┬───────────────────────────┬───────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────────────┐
│ PostgreSQL │ │ nhost Serverless Functions │
│ (9 tables + view) │ │ ┌─ trigger-workflow-run.js │
│ │ │ ├─ approve-step.js │
│ │ │ ├─ webhook-trigger.js │
│ │ │ └─ utils/ (shared code) │
└──────────────────────┘ │ │ │
│ ├─→ Groq API (LLM) │
│ ├─→ Open-Meteo (HTTP) │
│ └─→ ntfy.sh (Notify) │
└──────────────────────────────┘
- nhost: An open-source backend-as-a-service bundling Postgres, Hasura, Auth, and Serverless Functions. Locally runs via Docker with
nhost up. - Hasura: A GraphQL engine that auto-generates a GraphQL API from your Postgres schema. We use it for queries, mutations, subscriptions, permissions, Actions (custom business logic endpoints), and Event Triggers.
- Hasura Actions: Custom GraphQL mutations backed by serverless functions. When you call
triggerWorkflowRun()in GraphQL, Hasura forwards the request to our function handler, which does the actual work. - Subscriptions: Real-time WebSocket connections. When our handler updates a
step_runstatus, Hasura pushes the change to all subscribed clients automatically — no page refresh needed. - Computed Field/View:
org_monthly_usageis a Postgres VIEW (not a table) that aggregates workflow run stats per org. Hasura tracks it as a read-only table so it's queryable via GraphQL.
| Tool | Version | Install |
|---|---|---|
| Docker & Docker Compose | Latest | docker.com |
| Node.js | 18+ | nodejs.org |
| nhost CLI | 1.50+ | curl -L https://raw.githubusercontent.com/nhost/cli/main/get.sh | bash |
| Git | Latest | pre-installed on most systems |
| Variable | Purpose | Where to Get | Free? | Required? |
|---|---|---|---|---|
HASURA_GRAPHQL_ADMIN_SECRET |
Admin access to Hasura | Auto-generated by nhost init (in .secrets) |
N/A | Yes (auto) |
GROQ_API_KEY |
LLM calls via Groq API | console.groq.com — sign up, create API key | ✅ Free, no card | Optional* |
NEXT_PUBLIC_NHOST_SUBDOMAIN |
Frontend → nhost connection | local for dev, your project subdomain for prod |
N/A | Yes |
NEXT_PUBLIC_NHOST_REGION |
nhost region | Empty for local, e.g. us-east-1 for prod |
N/A | Yes |
*If GROQ_API_KEY is not set, LLM calls use a stubbed response with an artificial delay. This is intentional — see "Stubbed Services" below.
git clone <repo-url>
cd flowpulse
# Install function dependencies
cd functions && npm install && cd ..
# Install frontend dependencies
cd frontend && npm install --legacy-peer-deps && cd ..# (Optional) Add your Groq API key for real LLM calls
# Add this line to the .secrets file in the project root:
# GROQ_API_KEY = 'your-key-here'
# Frontend is pre-configured for local dev
# frontend/.env.local already contains:
# NEXT_PUBLIC_NHOST_SUBDOMAIN=local# This starts the entire backend stack via Docker
nhost up
# First run will:
# 1. Pull Docker images
# 2. Apply migrations (create tables)
# 3. Apply metadata (permissions, relationships, actions)
# 4. Apply seed data (demo orgs + workflows)
# 5. Start serverless functionsWait for "ready" output. The following services will be available:
- Hasura Console: http://localhost:1337 (admin secret:
nhost-admin-secret) - GraphQL endpoint: http://localhost:1337/v1/graphql
- Auth: http://localhost:1337/v1/auth
- Functions: http://localhost:1337/v1/functions
# Users must be created via Auth API (not raw SQL) for proper password hashing
node functions/seed-users.jsThis creates 6 users across 2 orgs:
| Password | Org | Role | |
|---|---|---|---|
owner_a@demo.com |
password123 |
Acme Corp | owner |
editor_a@demo.com |
password123 |
Acme Corp | editor |
viewer_a@demo.com |
password123 |
Acme Corp | viewer |
owner_b@demo.com |
password123 |
Beta Inc | owner |
editor_b@demo.com |
password123 |
Beta Inc | editor |
viewer_b@demo.com |
password123 |
Beta Inc | viewer |
cd frontend
npm run dev
# → http://localhost:3000- Open http://localhost:3000
- Log in as
owner_a@demo.com/password123 - You should see the "Acme Corp" dashboard with the "Weather Analysis Pipeline" workflow
- Click "▶ Run" on the workflow
- Watch step statuses update in real-time on the run viewer page
| Service | What It Does | Open Source? | Cost | Notes |
|---|---|---|---|---|
| Groq API | LLM inference (Llama 3.3 70B) | Models are open-weight; API service is proprietary | Free tier, no card required | ~30 RPM limit. Falls back to stub if unavailable. |
| Open-Meteo | Weather data for HTTP request demo | ✅ Fully open source | Free, no API key | No signup needed. |
| ntfy.sh | Push notifications | ✅ Open source (self-hostable) | Free public instance, no signup | Messages are public; use unique topic names. |
If GROQ_API_KEY is not set, the llm_call step uses a stubbed response with a 1.5-second artificial delay. This is intentional and disclosed:
- The stub returns
{ classification: "NORMAL", summary: "Stubbed response" } - The workflow still exercises the full execution pipeline (status updates, subscriptions, conditional branching)
- Set the env var for real LLM calls
flowpulse/
├── nhost/
│ ├── nhost.toml # nhost service configuration
│ ├── migrations/default/1_init/ # SQL schema (all tables)
│ ├── metadata/ # Hasura metadata
│ │ ├── actions.yaml # Action definitions (triggerWorkflowRun, approveStep, webhookTrigger)
│ │ ├── actions.graphql # Action type definitions
│ │ └── databases/default/tables/ # Table tracking, relationships, permissions
│ └── seeds/default/1_seed.sql # Demo data (orgs, workflows, steps, triggers)
├── functions/
│ ├── trigger-workflow-run.js # ⭐ Core: workflow execution engine
│ ├── approve-step.js # ⭐ Approval gate handler
│ ├── webhook-trigger.js # Inbound webhook endpoint
│ ├── seed-users.js # Demo user creation script
│ └── _utils/
│ ├── graphql.js # Admin GraphQL client
│ └── step-executors.js # All 6 step type implementations
├── frontend/
│ └── src/
│ ├── app/
│ │ ├── page.js # Login/signup
│ │ ├── dashboard/page.js # Workflow list + run controls
│ │ ├── workflow/[id]/page.js # Workflow builder
│ │ └── workflow/[id]/run/[runId]/page.js # ⭐ Live run viewer
│ ├── components/Navbar.js # Nav with org selector + quota
│ └── lib/
│ ├── nhost.js # nhost client config
│ ├── apollo.js # Apollo Client with WebSocket subscriptions
│ └── graphql.js # All GraphQL operations
├── README.md # ← You are here
├── DEPLOYMENT.md # Deployment guide
├── WRITEUP.md # Schema & permission reasoning
└── DEMO.md # Final Task walkthrough script
Every table has permission rules that scope access via the org_members relationship. Even if an Org B user guesses an Org A workflow ID and queries it directly, Hasura will return empty results.
Example (from public_workflows.yaml):
filter:
organization:
org_members:
user_id:
_eq: X-Hasura-User-IdSome restrictions can't be expressed as row-level permissions:
- Step-type gating: Only owners can add
db_writeornotifysteps (checked intrigger-workflow-run.js) - Approval role check: Only owners/editors can approve paused steps (checked in
approve-step.js) - Quota enforcement: Org quota is checked before allowing a new run
See WRITEUP.md for detailed reasoning.