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 went from blank repo to production in under 5 weeks, 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

Five MVPs, four months, one engineer

I was the only engineer at the agency. 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 permanently on call.

This is a breakdown of what I built, the patterns I relied on, and what shipping at that pace 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:

Vendor isolation was handled at the database level with a vendor_id foreign key on every resource. AdonisJS middleware enforced scope on every request. No vendor could ever touch another's data, not through bugs and 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 built only the frontend and auth layer.

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 it required a solid permissions system: three roles (admin, dispatcher, driver), each with different views and write access.

I enforced role-based access at the database query level rather than in the UI. Every query filters by the authenticated user's role and scope. Drivers see only their own assignments. Dispatchers see their zone. Admins see 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 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. 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.

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 mattered.