DEV Community

Tobechukwu Ejiofor
Tobechukwu Ejiofor

Posted on

Building the "shadcn/ui for Backend": A Contract-First Approach

Hi everyone! I'm Tobechukwu. I'm currently building an open-source project called Plughub. It's a CLI and registry for pluggable backend services.

The idea is simple: instead of adopting a massive BaaS (like Supabase or Firebase), you should be able to pick and choose backend capabilities. You run a command like backkit add email, and it generates the actual provider code (SMTP, Resend, etc.) directly into your project.

Here is what I learned while building the Email and Storage capabilities.

The Problem: Transport Lock-in

Recently, I was testing the SMTP provider for the email capability. I tried to send a real email, but my mobile carrier actively blocks outbound connections on port 587 (a common anti-spam measure).

I couldn't test my own code. And it made me realize a bigger problem: what happens if a user of my CLI is on an enterprise network that blocks SMTP? Are they just stuck?

The Solution: Transport-Agnostic Contracts

I decided to build an HTTP-based provider (Resend) that sends emails via port 443. But the real magic was how I structured the architecture to allow this.

  1. The capability defines the contract: sendEmail(input) -> result.
  2. The user's application code only imports this contract.
  3. The provider (SMTP or Resend) handles the actual transport.

When I swapped the SMTP provider for the Resend provider, the application code didn't change. It just kept calling email.sendEmail().

The Same Applies to Storage

I applied the exact same logic to build a Storage capability. Using the AWS SDK v3, the s3 provider can connect to AWS, Cloudflare R2, or a local MinIO instance.

The app doesn't care where the file goes; it just calls storage.uploadFile().

What's Next?

I'm currently polishing the Developer Experience (auto-installing dependencies, generating .env files) before I make the repository public.

If you're interested in the "shadcn/ui for backend" concept, follow me here or on X (@TAGdev278) to see when the repo goes live!

Top comments (0)