Full-Stack Engineer

Client MVP Suite: 5 Full-Stack Products in 4 Months

Shipped 5 full-stack MVP applications in 4 months as the sole engineer at a digital agency. Each product was delivered in under 5 weeks — from blank repo to production — using a shared architecture playbook built around Svelte, Next.js, and Drizzle ORM.

Year
2025
Stack
SvelteNext.jsDrizzle ORMAdonisJSPostgreSQLCloudflare

Client MVP Suite: 5 Full-Stack Products in 4 Months

Context

At the agency, I was the only engineer. Five clients, five MVPs, four months. Each project had real users from day one, a different domain, and a different set of requirements. There was no room for bespoke architecture decisions on every project — I needed a repeatable system that could be picked up fast, delivered clean, and maintained without me being on-call permanently.

This is a breakdown of what I built, the patterns I relied on, and what shipping at that pace actually looks like.


The Playbook

Before any client work started, I established a shared project template. Same folder structure, same tooling, same deployment pipeline. The only variable was the domain logic.

project/
├── apps/
│   ├── web/          ← SvelteKit or Next.js (per-project)
│   └── api/          ← AdonisJS REST API
├── packages/
│   ├── db/           ← Drizzle ORM schema + migrations
│   └── shared/       ← Zod schemas, shared types
└── docker-compose.yml

Having this ready meant I wasn't spending the first week on scaffolding — I was writing product code from day one.


The Five Projects

1. Multi-Vendor Marketplace — SvelteKit + AdonisJS

A local artisan goods marketplace where multiple vendors could list and manage products independently. The tricky parts:

The vendor isolation was handled at the database level using a vendor_id foreign key on every resource, with AdonisJS middleware enforcing scope on every request. No vendor could ever touch another's data — not through bugs, not through URL manipulation.

Delivered in: 4.5 weeks


2. SaaS Analytics Dashboard — Next.js App Router

A B2B dashboard for a software client tracking engagement metrics across their product suite. Their backend already existed — I was building the frontend and auth layer only.

The constraint was bundle size. Their users were often on slower connections in lower-bandwidth regions. I leaned hard into React Server Components to push as much rendering to the server as possible:

Final bundle: 68KB gzipped for the main page. No JS framework overhead on the critical path.

Delivered in: 3.5 weeks


3. Service Booking Platform — SvelteKit

A booking system for a services business — think appointment scheduling with staff availability, time slots, and confirmation emails.

The calendar was the hardest part. Naive implementations cause N+1 queries: for every rendered date cell, a query to check availability. I pre-computed the booked slot map server-side:

// One query, O(1) lookup in the template
const bookedSlots = await db
  .select({ date: bookings.date, time: bookings.time })
  .from(bookings)
  .where(
    and(
      eq(bookings.staff_id, staffId),
      between(bookings.date, rangeStart, rangeEnd),
    ),
  );
 
const slotMap = new Map<string, Set<string>>();
for (const { date, time } of bookedSlots) {
  if (!slotMap.has(date)) slotMap.set(date, new Set());
  slotMap.get(date)!.add(time);
}

One query per page load. The template checks slotMap.get(date)?.has(time) — constant time.

Delivered in: 5 weeks


4. Internal Operations Tool — Next.js + Drizzle

An internal CRUD tool for a logistics client to manage shipments, drivers, and delivery zones. Not glamorous, but required a solid permissions system — three roles (admin, dispatcher, driver) with different views and different write access.

I implemented role-based access at the database query level, not just the UI level. Every query was pre-filtered by the authenticated user's role and scope. The driver can only see their own assignments. The dispatcher can see their zone. Admin sees everything.

No custom auth library — just JWT claims, Drizzle's where clauses, and a single withUserScope(userId, role) query builder wrapper that every route used.

Delivered in: 3 weeks


5. Content Publishing Platform — SvelteKit + AdonisJS

A lightweight CMS-like tool for a media client to manage and publish articles with a custom editor, image uploads, and a public-facing reader site — all in one product.

The editor was the main challenge. Rich text editors are notoriously hard to SSR. I used Tiptap, wrapped in a <svelte:component> that only mounts client-side, while the reader-facing article pages are fully SSR with zero client JS. Two different rendering contexts, one codebase.

Delivered in: 4 weeks


What Made It Work

Drizzle as the single source of truth. Schema defined once, types inferred everywhere. No drift between the database and the application layer. Running a migration was a one-liner and the TypeScript compiler caught any shape mismatches immediately.

Presigned uploads, always. Every project that handled file uploads used direct-to-storage presigned PUTs. The API hands out a URL, the client uploads directly, the API confirms after. No proxied uploads, no memory pressure, no timeout issues on large files.

Zod at the boundary. Every API request was validated with a shared Zod schema before touching the database. Same schema used for form validation on the frontend. One definition, two uses, zero inconsistency.

Docker Compose for deployment. Every project shipped with a docker-compose.yml that the client's VPS could run with a single command. No deployment complexity, no CI/CD to set up for the client — just SSH and docker compose up -d.


Results

ProjectStackDelivery
MarketplaceSvelteKit + Adonis4.5 weeks
SaaS DashboardNext.js App Router3.5 weeks
Booking SystemSvelteKit5 weeks
Operations ToolNext.js + Drizzle3 weeks
CMS + ReaderSvelteKit + Adonis4 weeks

All five hit production inside a 4-month window. No post-launch critical bugs on any project. Every client had a working product, a Dockerised deployment, and enough documentation to hand off to their own team if needed.

The main takeaway: opinionated defaults beat flexibility when you're moving fast. The less I had to decide per project, the more energy I had for the parts that actually mattered.