← Back to Blog
saasgonextjsfeature-flagsarchitecture

How I Built Rollgate: From Side Project to Feature Flags SaaS

Domenico Giordano··13 min read
How I Built Rollgate: From Side Project to Feature Flags SaaS

How I Built Rollgate: From Side Project to Feature Flags SaaS

A year ago, Rollgate was a folder on my laptop: a handful of Go files, a Postgres schema I kept rewriting, and the stubborn idea that feature flags did not need to cost $70,000 a year.

Today it is a feature flags SaaS running in production, with the capabilities I wanted from LaunchDarkly (gradual rollouts, user targeting, A/B testing, kill switches) and none of the enterprise invoice.

This is the story of how that folder became a product: the architecture I chose, the features that took longer than I planned, the mistakes, and the real numbers most people keep to themselves.


The Problem

If you have shipped in a team, you know the spot. The feature is ready, you are fairly sure it works, but "fairly sure" across every user and edge case is doing a lot of lifting. Without flags, a deploy is all-or-nothing. It is live for everyone or for no one.

Feature flags split deployment from release. The code ships to production turned off, then you ramp it: 1% of users, then 5%, then a quarter, watching as you go. If it misbehaves, you flip it off. No revert commit, no hotfix at 2am.

LaunchDarkly does all of this, and does it well. It also bills somewhere between $25k and $150k a year, which is a non-starter for most startups and mid-size teams. So the question I kept circling back to was whether one person could build something close and sell it for a fraction of that.

Choosing the Tech Stack

The stack came first. Two things mattered more than anything else. Latency, because flags get evaluated on basically every request, and operational simplicity, because I am the only one on call and fewer moving parts means fewer 3am pages.

Backend: Go

The API is Go. A flag lookup has to finish in microseconds, and Go compiles to a native binary with no GC pauses worth worrying about, chewing through thousands of concurrent connections on goroutines without much fuss. Deployment is a single static binary, no runtime to install, and the final Docker image is 20MB. The standard library covers most of what I need (HTTP, JSON, crypto, testing), so I am not pulling in a dependency for every little thing.

Routing is Chi. It is thin and plays nice with net/http. I skipped the big frameworks; I would rather compose small pieces than learn someone's conventions.

Frontend: Next.js

The dashboard is Next.js with React and Tailwind. SSR and static generation handle the marketing pages for SEO, the API routes proxy to the Go backend, and day to day the dev experience is pleasant enough that I do not fight it.

Database: PostgreSQL + Redis

Postgres holds the source of truth. JSONB for flag metadata, partial indexes where they earn their keep, LISTEN/NOTIFY to invalidate caches. It has grown to 29 tables, 67 indexes, 29 foreign keys.

Redis sits in front as the evaluation cache. When an SDK asks whether flag X is on for user Y, Redis answers in milliseconds, and pub/sub keeps it fresh.

Rollgate's Feature Set

There is more under the hood than an on/off switch. The feature set covers most of the release lifecycle, so here is the tour.

Feature Flags with Multiple Types

A flag can return one of four types:

  • Boolean, the classic on/off, which is what you want for kill switches and plain toggles.
  • String, handy for A/B variants like "variant-a" / "variant-b", or for swapping copy without a deploy.
  • Number, for limits, percentages, or a knob on some algorithm.
  • JSON, when you need a whole config object: a layout, a plan's feature set, a chunk of business rules.

Gradual Rollouts

Percentage rollout turns a flag on for a slice of users. The trick is consistent hashing (MurmurHash3) over the user ID, so a given user lands the same way every time and I do not have to store who is in and who is out:

hash = MurmurHash3(flagKey + userId) % 10000
isEnabled = hash < rolloutPercentage * 100

That gets you 0.01% precision. Ramp 1, 5, 25, 100 at whatever pace your nerves allow, watching the dashboards between steps.

User Targeting and Segments

Targeting switches a flag on for specific groups based on attributes:

  • Match on user attributes: email, plan, country, app version, or anything custom you send.
  • Operators include equals, contains, startsWith, endsWith, regex, in, greaterThan, lessThan.
  • Define a segment like "Beta Users" or "Enterprise Customers" once and reuse it across flags.
  • Combine conditions with AND/OR when one rule is not enough.

Kill Switches

If I had to keep one feature, it would be the kill switch. Something breaks in production (a bug, a slow query, a payment provider having a bad day) and you need that feature gone now, not after a deploy.

Every flag has a toggle that propagates over SSE in seconds. No deploy, no SSH, no developer required. A product manager can kill a feature from their phone on the train.

I have watched teams burn thirty to forty-five minutes on a traditional rollback: revert the commit, wait for CI, deploy, verify it actually took. A kill switch is two seconds. Mid-incident, the difference is real money.

Scheduled Changes

Scheduled changes let you queue a flag change for a date and time:

  • Line up a launch: enable the feature Friday at 2pm, right when marketing publishes the blog post.
  • Run a promo on rails: Black Friday banner on from Friday, off Monday.
  • Automate the ramp: 1% Monday, 10% Wednesday, 50% Friday, 100% the Monday after.
  • Sunset something on schedule: turn the old API off on April 1st and forget about it.

Each one shows up in the flag's timeline as pending, executed, or cancelled. A cron job on the server wakes up every minute and applies whatever is due.

Instant Rollback

Every change to a flag gets saved with a timestamp, who did it, and a full snapshot of the state. Rollback puts the flag back exactly as it was at some earlier point. The value, sure, but also the targeting rules, the percentages, the scheduled changes.

It is more than an undo. You can jump back five changes, or to a specific version from last Tuesday. The audit log keeps the who and the when.

A/B Testing

String variant flags give you native A/B testing. Instead of true/false, the flag hands back a variant ("control", "variant-a", "variant-b") with whatever split you set.

Evaluation tracking records which variant each user saw, with timestamp and context, so you can line variants up against business metrics in whatever analytics tool you already use.

I deliberately left out a built-in stats engine. Teams already have analytics they trust, and I did not want to be one more data silo to reconcile.

Multi-Environment

Every project gets separate environments: development, staging, production, plus any others you spin up. Flags live independently in each, so a flag can be on in staging and off in prod.

Each environment carries its own API keys (server and client), its own rules, its own history. It is the guardrail against the classic "oh no, that was prod, not staging".

Audit Log

Everything lands in an immutable audit log. Who created, changed, or deleted a flag, the before/after diff, the exact timestamp, the IP and user agent it came from.

Retention follows the plan: 3 days on Free, 14 on Starter, 90 on Pro, a year on Growth. When you are chasing a bug or answering a compliance question, that trail is the thing that saves you.

Webhooks

Webhooks tell other systems when a flag moves. Create, update, delete: each one fires an HTTP POST to your URLs with the full event payload.

People wire them to ping Slack on a prod change, kick off a deploy, nudge monitoring, sync a project tool. There is automatic retry with exponential backoff, and every delivery is logged so you can see what landed.

Analytics and Usage Tracking

Evaluations get tracked as they happen: total count, unique users, the true/false split, how it trends over time. Usage caps go by plan: 500K requests a month on Free, 1M on Starter, 3M on Pro, 12M on Growth.

12 SDKs

There are 12 SDKs (plus a shared core package), which is probably the part that ate the most evenings:

  • JavaScript and TypeScript: sdk-node, sdk-browser, sdk-react, sdk-vue, sdk-angular, sdk-svelte, all built on a shared sdk-core.
  • Mobile: sdk-react-native, sdk-flutter.
  • Backend: sdk-go, sdk-java, sdk-python, sdk-dotnet.

They all share the same machinery: a local cache, a circuit breaker, retry with backoff, event buffering, live SSE updates. If the API goes dark, the SDK keeps serving the last values it had instead of falling over.

import { RollgateProvider, useFlag } from '@rollgate/sdk-react';

function App() {
  return (
    <RollgateProvider apiKey="rg_client_your_key">
      <Dashboard />
    </RollgateProvider>
  );
}

function Dashboard() {
  const showNewFeature = useFlag('new-dashboard', false);

  return showNewFeature ? <NewDashboard /> : <LegacyDashboard />;
}

System Architecture

Client SDK → API Server (Go) → Redis Cache → PostgreSQL
              ↑
          SSE Connection (real-time updates)

A flag evaluation lands around 200μs. P99 stays under a millisecond. The system holds tens of thousands of SSE connections open at once.

Technical Challenges

Race Conditions on Updates

Two people editing the same flag at once used to worry me. Optimistic locking with a version counter handles it: if the flag changed under you, the write comes back 409 Conflict and you reconcile.

Circuit Breaker in SDKs

If the API is down, an SDK absolutely cannot take the host app down with it. The circuit breaker runs the usual three states (Closed, Open, Half-Open), with exponential retry and request dedup so a thundering herd does not pile on.

SEO for a SaaS App

I split marketing and app across domains: rollgate.io for the SEO pages (Server Components) and app.rollgate.io for the authenticated dashboard.

Resilience and Monitoring

  • Backups: a full PostgreSQL dump every night, shipped to a server in a different datacenter.
  • Monitoring: Prometheus, Grafana, Alertmanager, with alerts that actually page me.
  • Health checks: API, Web, DB, and Redis get poked every 60 seconds.
  • Disaster recovery: one script rebuilds the whole stack in about 20 minutes, and I have tested it more than once.
  • SDK resilience: local cache plus circuit breaker means clients survive an outage.

The Numbers

MetricValue
Go files (non-test)147
TypeScript/TSX files168
API endpoints114
DB tables29
Total tests~850
Published SDKs13
Codebase score7.86/10

What I Learned

1. Simplicity Wins

No Kubernetes. Docker Compose runs the whole thing and I sleep fine. No microservices either; a modular monolith is plenty for one person to reason about. Every bit of complexity bills you in maintenance forever, and most of it is not worth the rent.

2. Tests Save Lives

Around 850 tests is what lets me refactor without holding my breath. CI runs the lot on every PR and will not let a deploy through if anything is red.

3. Billing Is Harder Than the Product

Payments with Paddle were harder than half the product. Webhooks, subscription lifecycle, proration, dunning, tax. Take the provider that swallows all of it (Paddle, Lemon Squeezy) instead of wiring raw Stripe yourself, unless billing is the thing you want to spend your life on.

4. SEO Requires Constant Attention

If Google does not index you, you do not exist. I lost weeks to SSR, JSON-LD, sitemaps, meta tags, and it is never actually finished.

5. Building Solo Is Sustainable (With Limits)

Writing the code turned out to be the comfortable part. Finding people who want it is the hard one. Marketing, support, growth: different muscles, and I am still building them.

What's Next

Rollgate is live at rollgate.io with a generous free tier (500K requests/month). On the bench: smarter A/B testing that calls the winner on its own, teams with SSO, the self-hosted build, and a full OpenAPI spec.

The Market: Competitors and Positioning

A handful of players own the feature flags market, most of them priced for enterprise:

PlatformPricingStrengthsLimitations
LaunchDarklyFrom $833/moMarket leader, enterprise-ready, vast integrationsProhibitive for startups, excessive complexity
FlagsmithFrom $45/moFree self-hosted, API-firstLess polished UI, fewer SDKs
UnleashFrom $80/moMature self-hosted, good docsComplex setup, dated UI
PostHogUsage-basedComplete suite (analytics + flags), open-sourceFlags are not the focus, overhead
RollgateFree → €45/moAccessible pricing, 12 SDKs, 5-min setupNew to market, growing community

Choosing the Pricing Model

Pricing was one of the calls I changed my mind on the most. Looking across competitors, it came down to three shapes:

  • Per-seat, like LaunchDarkly. Scales with headcount and punishes big teams; $70k a year for 50 developers is not a weird number.
  • Usage-based, like PostHog. Scales with traffic and makes budgeting a guessing game, since a spike can blow up the bill.
  • Fixed tiers, which is what I went with. The price is set by features and limits, so a customer knows the number before the month starts.

I picked fixed tiers because for startups and small teams, a predictable invoice beats a clever one. The four plans:

PlanMonthlyAnnuallyRequests/moTeamTarget
Free€0€0500K3 membersSide projects, MVPs
Starter€45/mo€39/mo1M5 membersEarly-stage startups
Pro€119/mo€99/mo3M15 membersGrowing teams
Growth€349/mo€299/mo12M50 membersScale-ups, high traffic

The free tier is deliberately generous. 500K requests a month covers an app with thousands of active users. I would rather lower the bar to entry: if someone tries Rollgate and it just works, moving up to Starter when the project grows is the easy, obvious step.

Why Not Open Source?

I looked hard at open-core (free core, paid enterprise bits). It really needs a live community to pull its weight, and for a one-person project the failure mode is handing everything away and monetizing none of it. So it is SaaS with a generous free tier for now, and a self-hosted build under BSL (Business Source License) on the roadmap, the same route HashiCorp and Sentry took.

A year after that folder on my laptop, Rollgate is live, the SDKs are published, and the Postgres schema finally stopped changing. That is how I built it, and it is still being built.

If you are looking for a LaunchDarkly alternative that does not cost as much as an employee, try Rollgate. It is free to get started.