Starter template: Micro-app architecture for group decision-making (polls, recommendations)
templatesmicroappsarchitecture

Starter template: Micro-app architecture for group decision-making (polls, recommendations)

ffrees
2026-02-04
11 min read
Advertisement

Ship a serverless micro-app for group decisions: polls, recommendations and realtime updates. Includes architecture, code, and free-tier scaling advice.

Ship a tiny, reliable group-decision micro-app that fits free tiers — fast

Decision fatigue kills velocity: a team can't pick lunch, users can't agree on a movie, volunteers can't choose a time. You want a small, maintainable micro-app (polls, recommendations, a restaurant picker) that is: deployable in hours, runs inside popular free cloud limits, and scales without big rewrites. This guide gives a shipable architecture, runnable code snippets, real-time options, webhook integrations, and clear upgrade paths for 2026.

Why build a micro-app this way in 2026?

Micro-apps (a.k.a. personal or ephemeral apps) became mainstream in 2024–2026 thanks to AI-assisted development and zero-config deployments. In late 2025 cloud providers sharpened free-tier caps and pushed edge/serverless offerings, so the best pattern now is static frontend + serverless API + managed minimal DB + pub/sub. That lets you stay within free tiers while supporting real-time features and safe scale-outs.

Micro-apps are small by design — aim for smallest viable backend and push signal to the client for responsiveness and cost control.

High-level architecture (shipable, free-tier-first)

Keep it minimal: a static frontend on Vercel/Netlify/Cloudflare Pages, serverless functions for writes, a managed Postgres (Supabase) or serverless MySQL (PlanetScale) for state, and a realtime layer via Supabase Realtime, Pusher, or WebSockets if you need immediacy.

  • Frontend: Static SPA (React, Svelte, or plain JavaScript). Deploy on Vercel/Netlify/Cloudflare Pages free tiers.
  • API (serverless): HTTP endpoints for creation, voting, and webhooks. Deploy as Vercel Serverless Functions, Netlify Functions, or Cloudflare Workers.
  • Database: Start with Supabase Postgres (free) or PlanetScale (MySQL). Use row-level retention and TTLs to keep costs down.
  • Realtime: Supabase Realtime or Pusher Channels for live updates. For very small audiences, client polling (5–10s) keeps you in free tiers.
  • Integrations: Slack, MS Teams, SMS via Twilio (or free-tier alternatives). Use webhooks for notifications and bot messages.

Why this combo?

Static hosting is effectively free for low to medium traffic. Serverless functions handle bursts without idle costs. Managed DBs remove ops. And using a hosted realtime service avoids long-lived connections on serverless, which are expensive or unsupported in many free tiers.

Data model: small and cheap

Design for transient data. Micro-app polls rarely need infinite history. Use simple tables and TTLs to expire old polls and attachments.

-- Postgres schema (Supabase / Postgres)
  CREATE TABLE polls (
    id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    title text NOT NULL,
    metadata jsonb DEFAULT '{}',
    created_at timestamptz DEFAULT now(),
    expires_at timestamptz
  );

  CREATE TABLE options (
    id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    poll_id uuid REFERENCES polls(id) ON DELETE CASCADE,
    label text NOT NULL,
    meta jsonb DEFAULT '{}'
  );

  CREATE TABLE votes (
    id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    poll_id uuid REFERENCES polls(id) ON DELETE CASCADE,
    option_id uuid REFERENCES options(id) ON DELETE CASCADE,
    voter_id text, -- anonymous id or user id
    created_at timestamptz DEFAULT now()
  );

  -- Index for quick counts
  CREATE INDEX ON votes (poll_id, option_id);
  

Key policies:

  • TTL on polls: set expires_at and run a daily serverless job or use DB background worker (Supabase scheduled function) to delete old polls.
  • Soft limits: max options per poll (e.g., 20) and max rows per poll to keep storage low.
  • Anonymous-friendly: allow voter_id as a hashed ephemeral ID (client-generated) to avoid auth complexity and reduce provider auth costs.

Minimal shipable template: API endpoints and client

Below is a practical, minimal stack you can deploy today. It uses a static frontend + Vercel functions + Supabase Postgres + Supabase Realtime for updates. This fits the free tier footprint for small groups.

Serverless API: create poll and vote (Node.js / TypeScript)

// api/create-poll.ts (Vercel serverless)
  import { NextRequest, NextResponse } from 'next/server';
  import { createClient } from '@supabase/supabase-js';

  const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE!);

  export async function POST(req: NextRequest) {
    const { title, options, expiresAt } = await req.json();
    if (!title || !Array.isArray(options) || options.length === 0) {
      return NextResponse.json({ error: 'Invalid payload' }, { status: 400 });
    }

    const { data: poll } = await supabase.from('polls').insert({ title, expires_at: expiresAt }).select().single();
    const opts = options.map((label: string) => ({ poll_id: poll.id, label }));
    await supabase.from('options').insert(opts);

    // Notify realtime (optional) is handled by DB triggers or server event
    return NextResponse.json({ poll_id: poll.id });
  }
  
-- api/vote.ts
  import { NextRequest, NextResponse } from 'next/server';
  import { createClient } from '@supabase/supabase-js';

  const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE!);

  export async function POST(req: NextRequest) {
    const { pollId, optionId, voterId } = await req.json();
    if (!pollId || !optionId) return NextResponse.json({ error: 'Bad request' }, { status: 400 });

    await supabase.from('votes').insert({ poll_id: pollId, option_id: optionId, voter_id: voterId });
    return NextResponse.json({ ok: true });
  }
  

Notes:

  • Use the Supabase service role key on server-side only.
  • Client-side only uses anon public key for reads and realtime subscriptions.

Client (React) with Supabase Realtime subscription

import { createClient } from '@supabase/supabase-js';
  import { useEffect, useState } from 'react';

  const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY);

  export default function Poll({ pollId }) {
    const [options, setOptions] = useState([]);

    useEffect(() => {
      async function load() {
        const { data: opts } = await supabase.from('options').select('id,label').eq('poll_id', pollId);
        setOptions(opts);
      }
      load();

      const sub = supabase
        .from(`votes:poll_id=eq.${pollId}`)
        .on('INSERT', payload => {
          // fetch fresh aggregated counts, or increment locally
          // Simple approach: re-query counts
          refreshCounts();
        })
        .subscribe();

      return () => { supabase.removeSubscription(sub); };
    }, [pollId]);

    return (
{/* Render options and vote buttons */}
); }

Realtime choices: simple to advanced

Choose your realtime strategy by audience size and free-tier constraints:

  • Poll every 3–10s (client polling): Easiest; stays in free quotas for many providers. Good for <=100 concurrent users.
  • Serverless push (SSE): Works for moderate usage but many serverless platforms don't allow long-lived connections on free tiers.
  • Hosted pub/sub (Supabase Realtime, Pusher): Best balance — free-tier channels and low operational burden. Use DB WAL triggers (Supabase) to publish changes.
  • Edge realtime (Cloudflare Workers + Durable Objects): Great for global low-latency but verify Durable Object costs for your volume. For mapping-heavy apps, see Real‑Time Vector Streams and Micro‑Map Orchestration.

In-app recommendations: deterministic and cheap

For group recommendations (like restaurant picks), avoid calling LLMs on every vote — that quickly exceeds free credits. Use a hybrid approach:

  1. Metadata-first: store tags (cuisine, price, distance) on options and compute scores server-side with weighted sums.
  2. Client personalization: allow users to indicate preferences that the client uses to re-rank results without server calls.
  3. LLM augmentation (optional): run a single LLM pass when a poll is created or when a user requests a recommendation snapshot. Cache results and invalidate only on big changes.
// Simple scoring function (Node)
  function scoreOption(option, votersPreferences) {
    // weights can be tuned
    const weights = { cuisine: 0.6, price: 0.3, distance: 0.1 };
    let score = 0;
    if (votersPreferences.cuisine?.includes(option.meta.cuisine)) score += weights.cuisine;
    // ...price, distance
    return score;
  }
  

Webhooks and integrations

Expose a webhook endpoint to integrate with Slack/Teams or to send SMS reminders. Keep the webhook payload small and sign it with an HMAC secret to avoid abuse.

// api/webhook/slack.ts
  import { NextRequest, NextResponse } from 'next/server';
  import fetch from 'node-fetch';

  export async function POST(req: NextRequest) {
    const { pollId, event } = await req.json();
    // Basic validation and HMAC check here
    const text = event === 'completed' ? `Poll ${pollId} finished` : `Update on ${pollId}`;
    await fetch(process.env.SLACK_WEBHOOK_URL, { method: 'POST', body: JSON.stringify({ text }) });
    return NextResponse.json({ ok: true });
  }
  

Stay inside free tiers: capacity planning and knobs

Free tiers are great but require constraints. Use these knobs:

  • Retention TTL: Keep poll data only for days or weeks, not years.
  • Connection strategy: Polling or hosted pub/sub to avoid long-lived serverless sockets.
  • Rate limits: Enforce per-IP / per-voter limits in serverless functions to prevent spam and cost spikes.
  • Asset hosting: Avoid heavy file uploads; use links or ephemeral images stored on free tiers (Cloudflare Images has a free tier for low volumes).
  • Batched writes: For high-frequency voting (unlikely for small polls), batch updates client-side and flush periodically to reduce DB ops.

Rough free-tier budget model (2026)

Assume these conservative free-usage caps for a micro-app prototype:

  • Static page views: 50k/month — fine on Vercel/Netlify free tiers
  • Serverless function invocations: 100k/month — common free tier threshold
  • Postgres rows: 1–10k rows free (Supabase Lite): enough for thousands of polls and votes if you expire data
  • Realtime connections: 100–500 concurrent on free Supabase/Pusher tiers

These numbers vary by provider and change frequently; design with headroom and telemetry so you can react before hitting limits.

Scaling paths and migration strategy

Start serverless and keep your code vendor-agnostic. Use an adapter layer and environment variables for provider specifics.

  • Abstract your DB access: keep queries in a single module so you can switch from Supabase to Postgres on a VM without touching the client.
  • Feature flags: toggle realtime provider or LLM augmentation with a config flag. If you're iterating quickly, see the 7-Day Micro App Launch Playbook for a rapid path to first users.
  • Data portability: prefer standard SQL and avoid provider-only JSON functions when possible.
  • Upgrade plan: When you exceed free tiers, upgrade the managed DB first (larger instance), then move to a paid realtime plan, and finally add a small compute instance for intense workloads.

Security, privacy, and compliance

Even small apps must be responsibly built. Prioritize:

  • Rate limiting and verification to prevent vote stuffing. Use short-lived client tokens if authenticating lightly.
  • Minimal PII — prefer anonymous IDs. If you store emails or phone numbers for notifications, encrypt them and use hashed references.
  • HMAC-signed webhooks to avoid rogue posts to Slack or other integrations.
  • GDPR/CCPA mindset — allow deletion of poll data on request and expire data automatically.

Operational tips & troubleshooting

  • Monitor free-tier quotas with provider alerts (Supabase, Vercel, Cloudflare have quota APIs). For a note on hidden platform economics, read about the hidden costs of free hosting.
  • Log in a cost-aware way: only log errors or aggregate metrics; avoid per-request verbose logs on production free tiers.
  • Provision a staging project separate from production so tests don't consume production free quota.
  • Use feature flags to disable realtime heavy paths if you suddenly spike in traffic.

In 2026 the micro-app pattern has matured. Key trends to leverage:

  • Edge-first deployments: More providers offer edge functions (Vercel Edge, Cloudflare Workers) for low-latency APIs. Use them for recommendation endpoints if latency matters.
  • LLM augmentation on demand: Teams use LLMs to summarize group preferences and generate choice rationales. Do this sparingly and cache results to control cost.
  • Composable infra: Git-based deploys + managed DBs + real-time pub/sub enable micro-apps to be built and retired quickly. The Micro-App Template Pack is handy for reusable patterns.
  • Privacy-by-default: Growing user expectations mean anonymous-first defaults pay off.

Example case: Where2Eat-style app (experience)

Practical example from builders in 2024–2026: one creator built a dining micro-app in a week. Their pattern matches ours: a static frontend, a serverless API for writes, Supabase for storage and realtime, and Slack webhook integration for announcements. They kept the data TTL at 7 days and enforced a 10-option maximum per poll — eliminating storage surprises and keeping the app free for months of casual use. For a no-code variant, check this No-Code Micro-App + One-Page Site Tutorial.

Actionable checklist to ship in a day

  1. Create Supabase project and seed tables (polls, options, votes).
  2. Bootstrap a static UI (Tailwind + React template) and deploy to Vercel/Netlify.
  3. Add serverless endpoints: create poll, list polls, vote.
  4. Hook up Supabase Realtime (or polling) to update clients after votes.
  5. Add Slack webhook endpoint and HMAC validation for bot posts.
  6. Set up TTL job (Supabase scheduled function) to delete old polls.
  7. Monitor quotas and set alerts in Supabase/Vercel/Netlify.

Takeaways

Ship small, iterate quickly. Use a static frontend + serverless API + managed DB + hosted realtime. Optimize for retention and rate limits so you stay inside free tiers. Cache LLM work and prefer deterministic scoring for recommendations. Build vendor-agnostic adapters to avoid future lock-in. If you want a short checklist for launching fast, try the 7-Day Micro App Launch Playbook.

Advanced next steps

  • Integrate an LLM for final-snapshot recommendations with chain-of-thought disabled to reduce tokens and cost.
  • Swap polling for edge pub/sub when growth justifies it.
  • Expose an embeddable widget so other teams can drop the poll into chats or sites. If you need mapping, read Beyond Tiles: Real‑Time Vector Streams.

Ready to build: starter repo & templates

Use this guide as your blueprint. Start with a minimal repo containing:

  • /frontend — static React app
  • /api — serverless endpoints for create/vote/webhook
  • /db — SQL migrations and TTL scripts
  • /infra — simple deployment docs for Vercel + Supabase

Want a ready-made starter? Clone a template that wires up Supabase + Vercel, seeds sample data, and includes webhook samples. Iteration time: 1–2 days. For reusable building blocks, the Micro-App Template Pack and the No-Code tutorial are good places to start.

Call to action

Build your first group-decision micro-app today with this starter template. Clone the starter, deploy to Vercel, connect Supabase, and invite your team. If you'd like, I can generate a minimal repo with the endpoints above and CI-ready deploy files tailored to your provider choices — tell me which stack you want (React/Vue, Supabase/PlanetScale, Vercel/Cloudflare) and I’ll scaffold it for you.

Advertisement

Related Topics

#templates#microapps#architecture
f

frees

Contributor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

Advertisement
2026-02-04T10:56:50.624Z