cd /news/developer-tools/building-high-throughput-micro-saas-… · home › topics › developer-tools › article
[ARTICLE · art-156351] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Building High-Throughput Micro-SaaS in 2026: The Modular Stack Architecture (FlutterFlow, Supabase & Celery)

A developer published an implementation blueprint for building micro-SaaS applications using FlutterFlow for the client, Supabase for authentication and PostgreSQL, and Celery for background job execution. The design places a server-side API between the client and privileged job creation, using a transactional outbox pattern where an authenticated request validates ownership, quota, and idempotency, then commits the job and outbox event in PostgreSQL before a dispatcher publishes to the broker and a Celery worker executes it. The author notes the blueprint is not a measured throughput benchmark and that the included migration has not been executed and must be validated in staging.

by read6 min views1 publishedOct 23, 2026

Use FlutterFlow for the client, Supabase for authentication and PostgreSQL, and Celery for background execution. Put a server-side API between the client and privileged job creation.

The important boundary is the transaction:

Authenticated request
→ validate ownership, quota, and idempotency key
→ commit job + outbox event in PostgreSQL
→ dispatcher publishes event to the broker
→ Celery worker executes and persists the result
→ client reads authorized status

This is an implementation blueprint, not a measured throughput benchmark. Capacity depends on workload, database connections, broker configuration, and worker concurrency. The migration below has not been executed as part of this article and must be validated in staging.

Disclosure: This article was prepared with AI assistance for AZAN Digital. We maintain AZAN App Nexus, a related SaaS architecture reference, and Azan Dev Hub, a developer utility reference for JSON formatting and campaign tracking workflows. They are supplementary resources, not dependencies of this design.

The client can authenticate, manage its own saved workflow definitions, request execution through the API, and read its own job status.

The backend controls validated job creation, quotas, outbox delivery, execution state, results, and privileged third-party credentials. Never place Supabase service-role credentials, broker credentials, or provider secrets inside a FlutterFlow application.

This migration targets a fresh Supabase project. Backend-owned tables intentionally have no client write grants. Run it through an administrative deployment connection; provision narrowly scoped API, dispatcher, and worker roles separately. Do not reuse the migration owner for normal application execution.

begin;

create table public.workflow_definitions (
  id uuid primary key default gen_random_uuid(),
  owner_id uuid not null default auth.uid()
    references auth.users(id),
  name text not null
    check (char_length(name) between 1 and 200),
  definition jsonb not null default '{}'::jsonb
    check (jsonb_typeof(definition) = 'object'),
  created_at timestamptz not null default now()
);

create index workflow_definitions_owner_created_idx
  on public.workflow_definitions (owner_id, created_at desc);

alter table public.workflow_definitions enable row level security;

revoke all on public.workflow_definitions
  from public, anon, authenticated;
grant select, delete
  on public.workflow_definitions to authenticated;
grant insert (name, definition)
  on public.workflow_definitions to authenticated;
grant update (name, definition)
  on public.workflow_definitions to authenticated;

create policy workflows_select
  on public.workflow_definitions
  for select to authenticated
  using ((select auth.uid()) = owner_id);

create policy workflows_insert
  on public.workflow_definitions
  for insert to authenticated
  with check ((select auth.uid()) = owner_id);

create policy workflows_update
  on public.workflow_definitions
  for update to authenticated
  using ((select auth.uid()) = owner_id)
  with check ((select auth.uid()) = owner_id);

create policy workflows_delete
  on public.workflow_definitions
  for delete to authenticated
  using ((select auth.uid()) = owner_id);

create table public.execution_jobs (
  id uuid primary key default gen_random_uuid(),
  owner_id uuid not null references auth.users(id),
  idempotency_key text not null
    check (char_length(idempotency_key) between 1 and 200),
  request_hash text not null
    check (request_hash ~ '^[0-9a-f]{64}$'),
  input_snapshot jsonb not null
    check (jsonb_typeof(input_snapshot) = 'object'),
  status text not null default 'queued'
    check (status in ('queued', 'running', 'succeeded', 'failed')),
  attempts integer not null default 0 check (attempts >= 0),
  lease_until timestamptz,
  result jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  unique (owner_id, idempotency_key)
);

create index execution_jobs_owner_created_idx
  on public.execution_jobs (owner_id, created_at desc);

create index execution_jobs_recovery_idx
  on public.execution_jobs (lease_until)
  where status = 'running';

alter table public.execution_jobs enable row level security;

revoke all on public.execution_jobs
  from public, anon, authenticated;
grant select on public.execution_jobs to authenticated;

create policy jobs_select
  on public.execution_jobs
  for select to authenticated
  using ((select auth.uid()) = owner_id);

create schema app_private;
revoke all on schema app_private from public, anon, authenticated;

create table app_private.outbox (
  id uuid primary key default gen_random_uuid(),
  job_id uuid not null unique
    references public.execution_jobs(id),
  created_at timestamptz not null default now(),
  available_at timestamptz not null default now(),
  lease_until timestamptz,
  delivery_attempts integer not null default 0
    check (delivery_attempts >= 0),
  published_at timestamptz
);

revoke all on app_private.outbox from public, anon, authenticated;

create index outbox_pending_idx
  on app_private.outbox (available_at, created_at)
  where published_at is null;

commit;

Owner indexes support owner-scoped queries. Column-level grants prevent clients from changing ownership, identifiers, or creation timestamps. WITH CHECK validates ownership of inserted or updated rows. For client inserts and updates, send only the granted fields: name and definition.

The job status and result remain backend-owned. The backend must maintain updated_at, leases, and state transitions explicitly; the schema does not implement those handlers. Review result payloads before making them client-readable.

This example uses individual ownership. Organization tenancy requires membership-based policies and tests for every role. Foreign keys also require a deliberate account-deletion and data-retention workflow.

Require an Idempotency-Key on execution requests. The server should:

202 Accepted with the job ID. The uniqueness constraint scopes the key to the owner. If the same owner repeats a key with the same request hash, return the original job. If the hash differs, return 409 Conflict.

Store an immutable input snapshot. A retry must not execute a workflow that changed after submission. Include execution-relevant options and workflow version in the hashed request.

After an insert conflict, fetch the existing row in a subsequent statement and compare hashes. Handle concurrent requests explicitly. Enforce quota reservations in the transaction rather than using a race-prone read-then-write check.

Calling task.delay() after committing a job leaves a failure window: the process can crash after saving the job but before publishing its message. Commit the outbox event in the same transaction as the job.

A dispatcher should:

SKIP LOCKED. Keep network calls outside long database transactions. Use a claim token or equivalent fencing condition so an expired dispatcher cannot overwrite a newer claim.

A crash after publication but before saving published_at can produce duplicate messages. The worker must tolerate them. The outbox provides recoverable publication, not exactly-once execution.

Send identifiers through the broker rather than full customer payloads. At task start, atomically claim the job, return if it already succeeded, and avoid taking over a valid execution lease. Increment the attempt count and assign an execution lease or fencing token before the immutable snapshot.

Retry transient failures such as rate limits and temporary network errors. Use bounded exponential backoff with jitter. Permanent authorization errors and invalid inputs should reach a terminal state.

Late acknowledgements can help with some failure cases, but they do not make side effects idempotent or guarantee recovery from every worker termination. Review Celery's documented worker-loss behavior before configuring redelivery.

Protect external effects separately:

Run a recovery process for expired execution leases and stuck jobs. Long-running jobs need lease renewal; reject stale worker writes using the current lease token.

A database transaction cannot atomically include an arbitrary external API call. Model that boundary explicitly.

Treat imported workflow JSON as configuration, not trusted executable code. When consulting a reference library such as App Nexus, verify each blueprint's schema, supported versions, required secrets, and deployment assumptions before importing it.

For every workflow:

For manual inspection, Azan Dev Hub is a related reference for JSON formatting and campaign-tracking workflows. Use sanitized examples; keep customer payloads, tokens, and signed URLs out of public utilities.

409. Deploy when these failure paths have been demonstrated, not merely when the happy-path job completes.

── more in #developer-tools 4 stories · sorted by recency
── more on @flutterflow 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/building-high-throug…] indexed:0 read:6min 2026-10-23 · —