The Real Cost of Startup Infrastructure (And How We Eliminated It)

The most expensive decision a startup makes is not which cloud provider to use or how much to pay engineers. It is which infrastructure to build on. Get it right and every feature flows naturally from the foundation. Get it wrong and you spend the next two years maintaining adapter layers between your domain and someone else's abstractions.

We got it wrong the first time. Then we built Jenga.

The Infrastructure Tax

Every startup pays an infrastructure tax. Not in dollars (though that too), but in decisions. You need authentication. Do you use Auth0, Firebase, Cognito, or roll your own? You need email. SendGrid? SES? Mailgun? You need error handling. Sentry? Rollbar? Console.log and hope?

Each choice is fine in isolation. The problem is the gaps between them. Your auth provider does not know about your email system. Your error tracker does not know about your auth tokens. Your database ORM does not know about any of it. So you build glue code. Adapter layers. Translation functions. And every time you change one system, the glue breaks.

This is the infrastructure tax: not the cost of any individual tool, but the cost of making them all talk to each other. For a two-person startup, this tax can consume 40% of engineering time. Not building features. Not shipping product. Just keeping the plumbing from leaking.

What We Built Instead

Jenga is a full-stack Haskell framework. Not just auth (though it handles auth). Not just error handling (though it handles that too). It is the entire infrastructure layer that sits between your domain logic and the outside world: authentication, authorization, email dispatch, error classification, configuration management, database utilities, frontend API clients, and static site generation. All of it sharing the same types, the same database connection, the same error hierarchy.

The result is that writing a new feature looks like this: you define your request type, write your handler, and Jenga wires up the auth check, the JSON serialization, the error reporting, and the database access. One function call. No glue.

Here is what a typical backend endpoint looks like with Jenga:

withConstrainedPrivateJSONRequestResponse @Db
  (Self :| Admin : [])
  $ \acctID requestBody -> do
    -- Your domain logic here. Auth, JSON parsing,
    -- error handling, and logging are already done.
    result <- doBusinessThing acctID requestBody
    pure (Right result)

That single combinator handles: extract the auth cookie, verify the cryptographic signature, look up the user's role in the database, reject unauthorized roles, parse the JSON request body, catch all exceptions, classify errors by severity, email admins for critical failures, log everything, and serialize the response. You write the two lines in the middle. Jenga writes the rest.

Refactoring Is Not a Nightmare

This is the business case for Jenga, and honestly for building on Haskell in general.

Most startups are terrified of refactoring. Not because refactoring is hard, but because the consequences are invisible. You rename a field in your API response. The backend compiles fine. The frontend compiles fine. Three days later, a customer reports that the settings page is blank. You trace it to a JSON key mismatch that nobody caught because the frontend and backend are in different languages with different type systems.

In Jenga, the frontend and backend share types through a common package. Rename a field in your API response and the frontend fails to compile until it is updated. Change an error type and every handler that matches on it fails to compile until it handles the new case. Add a new config value and every function that reads config is verified against the new shape.

This means you can refactor as aggressively and as often as you want. Not "carefully, with a migration plan and a feature flag." Just change it. The compiler tells you every file that needs to update. You fix them. You ship. Nothing breaks in production because everything that could break was caught at build time.

For a startup, this changes the economics of technical decisions. You do not have to get the architecture right on day one, because changing it on day ninety is cheap. You do not have to over-engineer for hypothetical future requirements, because adding them later is safe. You can move fast without accumulating the kind of technical debt that kills companies.

Errors That Tell You What Happened

At 3am, something breaks. In most stacks, you open your error tracker and see: "500 Internal Server Error." You dig through logs. You find a stack trace. You spend an hour figuring out whether this is a user typo or a system failure.

Jenga classifies every error at the type level:

data LoginError
  = UnrecognizedEmail
  | IncorrectPassword
  | NoUserTypeFound
  | SubscriptionExpired

These are not log messages. They are types. When a login fails, the system knows exactly why. User errors (wrong password, invalid email) are logged quietly. System errors (database timeout, unhandled exception) trigger an admin notification email with the full context: what handler failed, what the input was, what exception was thrown.

You do not configure this per-endpoint. It is built into the framework. Every handler wrapped in withErrorReporting gets automatic error classification, admin notification, and structured logging. The severity is encoded in the type, not decided at the call site.

For a startup, this means your first hire does not need to set up an error monitoring system. It is already there. And it is better than most third-party solutions because it understands your domain types, not just HTTP status codes.

Infrastructure You Own

The standard startup advice is "buy, don't build." For most things, this is right. But auth, email, error handling, and configuration are different. They are your domain boundary. Every interesting feature you will ever build touches at least two of them.

When these systems are external services, every feature requires an adapter layer. "Add organization management" means: figure out how Auth0 models organizations, write a translation layer to your domain model, handle the edge cases where their model does not match yours, and maintain that adapter forever.

When these systems are your code, with your types, sharing your database: "Add organization management" means: add a table, add a handler, wire it up. The integration cost is zero because there is nothing to integrate. It is all the same system.

Jenga gives you:

None of this is locked behind a vendor dashboard. It compiles into your binary. You can read the source, modify the behavior, and debug production issues by reading Haskell, not by submitting a support ticket to a SaaS provider.

The Ecosystem

Jenga is the infrastructure layer, but it is part of a larger development platform:

ClasshSS provides type-safe CSS built on Tailwind. Every color, spacing value, breakpoint, and transition is a Haskell type checked at compile time. You cannot write bg-blu-500 (a typo that silently produces no styling). The compiler catches it.

Lamarckian generates static HTML pages at compile time using the same Reflex DOM builder your app uses. Your landing page and your application share components. Change a shared component and both are recompiled and verified.

Templates provides pre-built UI components (buttons, modals, inputs, dropdowns) built on ClasshSS, so you are not reinventing form elements for every project.

The point is not that each library is individually impressive. The point is that they all share the same type system. A change in your color palette propagates through ClasshSS to every component in templates to every page generated by Lamarckian. One change, verified everywhere, automatically.

From MVP to Production

Here is what building on Jenga looks like at different stages:

Week 1: You have login, signup, OAuth, email verification, a landing page, and a protected API. You did not build any of that. You wrote your domain logic.

Month 3: You need admin roles and organization management. You add a user type, a route guard, and a handler. The existing error reporting and email systems work with the new code automatically because they are generic over your domain types.

Month 6: You need to change your database schema. You modify the Haskell type, beam-automigrate generates the SQL, and the compiler shows you every query that needs to update. You do not grep for raw SQL strings and hope you found them all.

Month 12: You need to restructure your API. You rename types, reorganize modules, change request shapes. The compiler produces a list of every file that needs to change. You work through the list. You deploy. Nothing breaks. Your users do not notice. This is the moment where the investment in types pays for itself ten times over.

Year 2: A potential customer asks about your security practices. You can show them typed auth tokens, structured error logs, role-based access control verified at compile time, and an email audit trail stored in your database. These are not features you added for compliance. They are features you got for free because the framework was designed correctly.

Open Source

Jenga is available on GitHub. It is built on Obelisk, Reflex-FRP, Rhyolite, and Beam. If you are building a full-stack Haskell application, Jenga provides the infrastructure layer so you can focus on the product layer.

If you are not building in Haskell, the ideas still transfer: typeclass-based dependency injection, structured error hierarchies that distinguish user mistakes from system failures, full-stack type sharing to eliminate protocol bugs, and async work queues backed by your existing database instead of a separate message broker. These patterns make infrastructure disappear into the type system, which is exactly where infrastructure belongs.

The best framework is one where you forget it exists. You just write your product, and everything else is handled.