Skip to content
cd ../
March 10, 20263 min readEngineering

The Case for Clean Architecture in Early-Stage Startups

ArchitectureTypeScriptStartupsBest Practices

The conventional wisdom says: "Don't over-architect a startup. Move fast. You'll rewrite later anyway."

I've seen this play out three times now. The rewrite never comes. Instead, the codebase hardens into a shape that made sense for 2 engineers but actively resists 10.

What Clean Architecture Actually Means Here

I'm not talking about six layers of abstraction with dependency injection containers. I'm talking about one rule:

Business logic should not depend on infrastructure.

That's it. If your pricing calculation calls Stripe SDK directly inside the handler function, you've coupled pricing logic to a payment provider. If your user creation flow writes directly to PostgreSQL, switching databases means rewriting user creation.

The Practical Implementation

Instead of abstracting everything, abstract at every infrastructure boundary:

// ❌ Tight coupling
app.post('/checkout', async (req, res) => {
  const session = await stripe.checkout.sessions.create({
    line_items: [/* ... */],
    mode: 'payment',
    success_url: 'https://example.com/success',
  });
  res.json({ url: session.url });
});

// ✅ Loosely coupled
app.post('/checkout', async (req, res) => {
  const url = await paymentService.createCheckoutSession(cart);
  res.json({ url });
});

The paymentService is an interface that happens to use Stripe today. Tomorrow it can be any provider. You test paymentService in isolation by mocking the provider call.

Where the Boundary Goes

Practical boundaries worth protecting:

BoundaryWhyWhen to Draw
Payment providerYou switch processors regularlyDay 1
Email providerSES → SendGrid → ResendFirst email sent
Database queriesRead model != write modelSecond schema change
File storageLocal → S3 → CDNFirst upload
Auth providerClerk → Auth0 → customFirst SSO request

When to Skip the Abstraction

Not every external call needs an interface. If you're calling:

  • A single endpoint of a single API you control
  • A well-known CLI that won't change (ffmpeg, ImageMagick)
  • A database with a stable access pattern

...just call it directly. The cost of the abstraction outweighs the benefit.

The Rewrite That Doesn't Happen

Startups that hit 10x growth don't stop to rewrite. They hire 8 new engineers, put them in a growing codebase, and hope the conventions hold.

Clean architecture is insurance against this moment. It doesn't slow down early development — it prevents the compounding-interest problem of infrastructure coupling. One decision per boundary, made intentionally, saves weeks of untangling later.

The startups that survive to Series B aren't the ones that rewrote. They're the ones that never needed to.