Lompat ke konten utama
vourdev
Kembali ke Blog

Dev Notes

Workflow AI Saya untuk Bikin Boilerplate Fullstack dalam 15 Menit

Panduan praktis merangkai prompt chaining, template Prisma schema, dan AI tools untuk scaffolding micro-SaaS tanpa setup manual yang berulang.

Gambar Cover Workflow AI Saya untuk Bikin Boilerplate Fullstack dalam 15 Menit
Ditulis olehvourdev11 menit baca

Berapa kali dalam setahun Anda membuat project baru dari nol? Kalau Anda tipe engineer yang sering bereksperimen dengan ide micro-SaaS atau internal tools, ritualnya selalu sama dan membosankan: setup Next.js, konfigurasi Tailwind, pasang ORM, bikin skema autentikasi, set up session handler, pasang Zod, bikin form validation, pasang komponen UI, lalu setup error boundary.

Ritual manual ini biasanya menghabiskan waktu 2 sampai 3 hari kerja sebelum Anda benar-benar menulis satu baris pun business logic yang unik untuk produk Anda.

Banyak engineer mencoba menyelesaikan masalah ini dengan dua cara ekstrem:

  1. Membeli atau cloning *fat starter kit* serba ada: Starter kit 10.000 baris kode yang membengkak dengan 40 library berbeda yang setengahnya tidak Anda butuhkan dan susah di-maintenance.
  2. Meminta LLM membuatkan satu aplikasi utuh dari satu prompt: "Tolong buatkan SaaS invoice lengkap dengan Next.js dan database." Hasilnya? Kode berantakan, relasi database cacat, file raksasa 800 baris tanpa modularitas, dan dependensi usang yang langsung error saat di-build.

Di artikel ini, saya akan membedah workflow personal yang saya pakai sehari-hari di Vour Studio. Dengan menggabungkan deterministic scaffolding (fondasi minimalis yang pasti) dan prompt chaining berbasis konteks schema, kita bisa melahirkan boilerplate fullstack production-ready yang type-safe dari database sampai UI dalam waktu kurang dari 15 menit.

Perbandingan Pendekatan Bootstrapping Fullstack SaaSFat Starter KitTerlalu banyak bloat &library asingSulit disesuaikandengan arsitektur spe…Setup cepat di awal,lambat saat refactoringKetergantungan padapembuat templatePrompt Sekali Jadi(Zero-Shot)AI berhalusinasiarsitektur & dependen…Relasi DB sering patah/ inkonsistenFile monolitik tanpamodularitasDebugging build errormemakan waktu lamaAI + Schema-FirstPipelineStruktur minimalis &deterministicType-safetyend-to-end terjaminAI bekerja denganbatasan konteks ketatSiap dideploy dalam 15menit
Perbandingan Pendekatan Bootstrapping Fullstack SaaS

Fondasi: Anatomi Stack yang Dipilih

Sebelum menyentuh AI, Anda harus menentukan opinionated baseline stack. AI bekerja optimal bukan saat diberi kebebasan tanpa batas, melainkan saat diberi pagar pembatas (guardrails) yang sangat jelas.

Stack yang saya gunakan untuk workflow ini:

  • Framework: Next.js (App Router, Server Actions).
  • Database & ORM: PostgreSQL + Prisma.
  • Validasi: Zod + next-safe-action.
  • UI Library: Tailwind CSS + shadcn/ui.
  • Auth: Auth.js v5 atau Supabase Auth (berbasis session cookies).

Semua komponen di atas memiliki sifat yang sama: TypeScript-first dan memiliki skema deklaratif. Hal ini memudahkan AI memahami batas-batas tipe data tanpa perlu menebak-nebak runtime state.

Lapisan Arsitektur Boilerplate Type-SafePresentation LayerReact Server Components + shadcn/uiLogic & Mutation LayerNext Safe Actions + Session ContextValidation & Data ContractZod Schemas + TypeScript Inferred TypesDatabase LayerPostgreSQL + Prisma Migration Engine
Lapisan Arsitektur Boilerplate Type-Safe

Mari kita pecah 15 menit tersebut menjadi empat tahap eksekusi terstruktur.


Tahap 1: Setup Workspace & System Prompt Context Engine (Menit 0–3)

Langkah pertama bukan membuka ChatGPT di browser, melainkan menyiapkan workspace dengan file .cursorrules atau System Instructions di IDE (Cursor / VS Code Copilot).

Tanpa instruksi arsitektural di level workspace, AI akan mencampuradukkan pola lama (seperti Next.js Pages Router atau fetch() manual di useEffect) dengan pola modern.

Bikin project dasar menggunakan package manager favorit Anda:

bash
pnpm create next-app@latest my-saas --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
cd my-saas
pnpm add prisma @prisma/client zod next-safe-action lucide-react clsx tailwind-merge
pnpm add -D prisma
pnpm dlx shadcn@latest init

Setelah scaffolding dasar terpasang, buat file .cursorrules (atau .windsurfrules / .github/copilot-instructions.md) di root directory project Anda.

markdown
# Architecture & Coding Standards

Anda adalah Senior Fullstack Engineer yang membangun micro-SaaS modern. Patuhi aturan berikut tanpa pengecualian:

## 1. Stack & Paradigma
- Next.js App Router (React 19 / Server Components secara default).
- Gunakan 'use client' HANYA jika komponen membutuhkan event listener, browser API, atau client state.
- Database access HANYA dilakukan melalui Prisma Client di Server Components atau Server Actions.

## 2. Mutasi Data & Keamanan
- DILARANG membuat API Routes (/api/*) untuk mutasi data internal. Gunakan Server Actions dengan wrapper `next-safe-action`.
- Setiap Server Action WAJIB memiliki skema input Zod yang ketat.
- Selalu periksa autentikasi session pengguna sebelum mengeksekusi mutasi.

## 3. UI & Styling
- Gunakan komponen shadcn/ui dan Tailwind CSS.
- Hindari inline style dan jangan membuat file `.module.css`.
- Tangani loading state menggunakan React `useTransition` atau Suspense boundaries.

Dengan file aturan ini aktif, AI tidak akan lagi menyarankan axios, tidak akan membuat route handler lama, dan langsung menulis kode sesuai konvensi App Router.


Tahap 2: Skema Database sebagai Single Source of Truth (Menit 3–7)

Visualisasi skema relasi database PostgreSQL dan Prisma ORM
Skema database yang solid adalah fondasi paling krusial sebelum AI menghasilkan kode lainnya. · Luke Chesser (Unsplash)

Kesalahan terbesar developer saat menggunakan AI adalah meminta AI membuatkan UI terlebih dahulu. Itu terbalik.

Dalam rekayasa perangkat lunak, data mendikte logika, dan logika mendikte antarmuka. Ketika Anda memiliki skema database yang valid dan terdefinisi dengan baik, 80% pekerjaan AI berikutnya tinggal melakukan inferensi tipe data.

Berikut adalah base template prisma/schema.prisma yang selalu saya gunakan sebagai pondasi SaaS multi-tenant:

prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

enum Role {
  OWNER
  MEMBER
}

enum SubscriptionStatus {
  TRIAL
  ACTIVE
  PAST_DUE
  CANCELED
}

model User {
  id            String          @id @default(cuid())
  name          String?
  email         String          @unique
  emailVerified DateTime?
  image         String?
  memberships   OrgMembership[]
  createdAt     DateTime        @default(now())
  updatedAt     DateTime        @updatedAt
}

model Organization {
  id                 String              @id @default(cuid())
  name               String
  slug               String              @unique
  subscriptionStatus SubscriptionStatus  @default(TRIAL)
  members            OrgMembership[]
  projects           Project[]
  createdAt          DateTime            @default(now())
  updatedAt          DateTime            @updatedAt
}

model OrgMembership {
  id             String       @id @default(cuid())
  role           Role         @default(MEMBER)
  userId         String
  organizationId String
  user           User         @relation(fields: [userId], references: [id], onDelete: Cascade)
  organization   Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  createdAt      DateTime     @default(now())

  @@unique([userId, organizationId])
  @@index([userId])
  @@index([organizationId])
}

model Project {
  id             String       @id @default(cuid())
  name           String
  description    String?
  organizationId String
  organization   Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  createdAt      DateTime     @default(now())
  updatedAt      DateTime     @updatedAt

  @@index([organizationId])
}

Eksekusi Prompt Chaining 1: Domain Modeling

Sekarang, berikan konteks schema di atas ke AI Anda (misal di Cursor Composer atau Chat) dengan prompt terarah berikut:

"Lihat `prisma/schema.prisma`. Saya ingin menambahkan fitur Feedback Board untuk SaaS ini. Setiap `Project` bisa memiliki banyak `FeedbackItem`. Setiap `FeedbackItem` memiliki title, content, status (OPEN, IN_PROGRESS, RESOLVED), upvotes count, dan dikirim oleh User. Tambahkan juga model `FeedbackUpvote` dengan constraint unique user-feedback. Kembalikan HANYA blok model Prisma baru yang valid lengkap dengan index dan onDelete cascade."

AI akan mengeluarkan ekstensi skema yang langsung klop dengan model yang sudah ada tanpa merusak relasi User maupun Organization.

Jalankan migrasi lokal:

bash
npx prisma db push

Tahap 3: Safe Server Actions & Type Contracts (Menit 7–11)

Editor kode menampilkan Server Action TypeScript dengan Zod validator
Pola Server Action berbasis safe-action mencegah AI mengabaikan validasi dan session check. · Florian Olivo (Unsplash)

Langkah berikutnya adalah membangun lapisan mutasi data. Kita tidak ingin AI menulis try/catch manual di 20 file berbeda dengan handling error yang inkonsisten. Kita buat satu safe action client sebagai middleware.

Buat file src/lib/safe-action.ts:

typescript
import { createSafeActionClient } from "next-safe-action";

export class ActionError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "ActionError";
  }
}

// Client dasar tanpa auth
export const actionClient = createSafeActionClient({
  handleServerError(error) {
    if (error instanceof ActionError) {
      return error.message;
    }
    console.error("Unhandled Action Error:", error);
    return "Terjadi kesalahan internal pada server.";
  },
});

// Client terproteksi dengan mock/real session check
export const authActionClient = actionClient.use(async ({ next }) => {
  // Ganti dengan session logic Anda (misal: auth() dari Auth.js)
  const session = { user: { id: "user_mock_123", email: "adhinugroho@vour.dev" } };

  if (!session?.user?.id) {
    throw new ActionError("Anda harus login untuk melakukan aksi ini.");
  }

  return next({
    ctx: {
      userId: session.user.id,
      userEmail: session.user.email,
    },
  });
});

Penjelasan Implementasi:

  • createSafeActionClient membungkus eksekusi server action agar tidak pernah melempar unhandled exception ke client yang bisa mengekspos detail database.
  • Middleware .use() menyuntikkan konteks ctx (userId dan userEmail) secara otomatis ke setiap action turunan, sehingga kita tidak perlu memanggil auth helper berulang kali di setiap controller.

Eksekusi Prompt Chaining 2: Generating Mutation Actions

Sekarang, minta AI membuatkan server action lengkap dengan Zod schema:

"Berdasarkan `prisma/schema.prisma` dan utility `authActionClient` di `src/lib/safe-action.ts`, buatkan file `src/actions/feedback.ts`. Buat dua actions: `createFeedbackAction` dan `upvoteFeedbackAction`. Gunakan Zod untuk validasi input, pastikan projectId divalidasi, dan gunakan Prisma transaction untuk upvote."

Berikut kode yang dihasilkan dan harus kita verifikasi:

typescript
"use server";

import { z } from "zod";
import { prisma } from "@/lib/prisma";
import { authActionClient, ActionError } from "@/lib/safe-action";
import { revalidatePath } from "next/cache";

const createFeedbackSchema = z.object({
  projectId: z.string().cuid("Project ID tidak valid."),
  title: z.string().min(3, "Judul minimal 3 karakter.").max(100, "Judul maksimal 100 karakter."),
  content: z.string().min(10, "Deskripsi minimal 10 karakter.").max(2000),
});

export const createFeedbackAction = authActionClient
  .schema(createFeedbackSchema)
  .action(async ({ parsedInput, ctx }) => {
    const { projectId, title, content } = parsedInput;
    const { userId } = ctx;

    // Pastikan project ada
    const project = await prisma.project.findUnique({
      where: { id: projectId },
      select: { id: true, organizationId: true },
    });

    if (!project) {
      throw new ActionError("Project tidak ditemukan.");
    }

    const feedback = await prisma.feedbackItem.create({
      data: {
        title,
        content,
        projectId,
        userId,
      },
    });

    revalidatePath(`/projects/${projectId}/feedbacks`);
    return { success: true, data: feedback };
  });

const toggleUpvoteSchema = z.object({
  feedbackId: z.string().cuid(),
});

export const toggleUpvoteAction = authActionClient
  .schema(toggleUpvoteSchema)
  .action(async ({ parsedInput, ctx }) => {
    const { feedbackId } = parsedInput;
    const { userId } = ctx;

    return await prisma.$transaction(async (tx) => {
      const existingUpvote = await tx.feedbackUpvote.findUnique({
        where: {
          userId_feedbackId: { userId, feedbackId },
        },
      });

      if (existingUpvote) {
        await tx.feedbackUpvote.delete({
          where: { id: existingUpvote.id },
        });
        await tx.feedbackItem.update({
          where: { id: feedbackId },
          data: { upvotesCount: { decrement: 1 } },
        });
        return { upvoted: false };
      } else {
        await tx.feedbackUpvote.create({
          data: { userId, feedbackId },
        });
        await tx.feedbackItem.update({
          where: { id: feedbackId },
          data: { upvotesCount: { increment: 1 } },
        });
        return { upvoted: true };
      }
    });
  });
Tips Senior: Selalu pastikan mutasi yang melibatkan increment/decrement counter seperti upvotes atau balance dibungkus dalam prisma.$transaction. AI sering lupa hal ini dan mengeksekusinya secara terpisah, yang memicu race condition saat traffic tinggi.

Tahap 4: UI Scaffolding dengan shadcn & Type Inference (Menit 11–15)

Tahap terakhir adalah mengaitkan UI form ke Server Action. Karena kita sudah punya validasi Zod di backend, kita bisa memanfaatkan tipe inferensi Zod untuk form frontend tanpa menduplikasi definisi tipe data.

Alur Request Form Frontend Menuju Safe Server ActionClient UI Formreact-hook-form + zodResolverSafe Action HandlerauthActionClient.schema()Prisma ORMprisma.$transaction()Database EnginePostgreSQLexecute(values)validated parsedInputSQL QueryrevalidatePath & response
Alur Request Form Frontend Menuju Safe Server Action

Pasang komponen UI yang dibutuhkan dari shadcn:

bash
pnpm dlx shadcn@latest add button input textarea form card toast

Eksekusi Prompt Chaining 3: Form Component Generation

Prompt ke AI:

"Buat client component `src/components/create-feedback-form.tsx`. Gunakan `react-hook-form`, `@hookform/resolvers/zod`, komponen shadcn/ui (Form, Input, Textarea, Button), dan eksekusi `createFeedbackAction` menggunakan hook `useAction` dari `next-safe-action/hooks`. Tangani status isPending dan error toast."

Komponen yang dihasilkan:

tsx
"use client";

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useAction } from "next-safe-action/hooks";
import { createFeedbackAction } from "@/actions/feedback";

import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";

const formSchema = z.object({
  title: z.string().min(3, "Judul minimal 3 karakter.").max(100),
  content: z.string().min(10, "Deskripsi minimal 10 karakter.").max(2000),
});

type FormValues = z.infer<typeof formSchema>;

interface CreateFeedbackFormProps {
  projectId: string;
  onSuccess?: () => void;
}

export function CreateFeedbackForm({ projectId, onSuccess }: CreateFeedbackFormProps) {
  const form = useForm<FormValues>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      title: "",
      content: "",
    },
  });

  const { execute, isExecuting } = useAction(createFeedbackAction, {
    onSuccess: ({ data }) => {
      if (data?.success) {
        form.reset();
        onSuccess?.();
      }
    },
    onError: ({ error }) => {
      alert(error.serverError || "Gagal membuat feedback.");
    },
  });

  function onSubmit(values: FormValues) {
    execute({
      projectId,
      title: values.title,
      content: values.content,
    });
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="title"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Judul Masukan</FormLabel>
              <FormControl>
                <Input placeholder="Contoh: Tambahkan dark mode..." {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />

        <FormField
          control={form.control}
          name="content"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Detail Deskripsi</FormLabel>
              <FormControl>
                <Textarea
                  placeholder="Jelaskan kebutuhan fitur atau bug yang dialami..."
                  rows={4}
                  {...field}
                />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />

        <Button type="submit" className="w-full" disabled={isExecuting}>
          {isExecuting ? "Menyimpan..." : "Kirim Masukan"}
        </Button>
      </form>
    </Form>
  );
}

Detail Implementasi:

  1. useAction(createFeedbackAction) mengikat state eksekusi (isExecuting, result, error) secara reaktif ke lifecycle React.
  2. Form tidak memerlukan handling e.preventDefault() manual karena ditangani oleh wrapper form.handleSubmit.
  3. Validasi berjalan ganda: secara instan di sisi browser (via zodResolver) dan secara absolut di sisi server (via authActionClient.schema()).

Anti-Pattern yang Harus Dihindari

Saat mengintegrasikan AI ke dalam daily engineering workflow, ada tiga jebakan utama yang sering membuat developer junior membuang-buang waktu:

  1. Context Window Pollution: Memasukkan 50 file ke dalam context prompt AI. Semakin banyak noise yang tidak relevan (seperti file config tailwind, CSS global, atau aset gambar), semakin tinggi kemungkinan LLM membuat halusinasi kode. Cukup lampirkan schema.prisma dan file action yang relevan.
  2. Accepting Without Verification: Menerima kode AI tanpa membaca relasi foreign key dan logic auth. AI sering lupa menambahkan userId check saat mutasi UPDATE atau DELETE, yang berpotensi menimbulkan celah IDOR (Insecure Direct Object Reference).
  3. Over-Prompting UI Layout: Meminta AI mendesain layout dashboard yang kompleks dari nol lewat teks. Jauh lebih cepat membuat struktur flex/grid sendiri dengan Tailwind, lalu meminta AI mengisi data binding komponen di dalamnya.

Kesimpulan & Action Items

AI tidak menggantikan pemahaman fundamental software architecture kita. Justru sebaliknya: semakin kuat pemahaman Anda tentang data modeling, abstraction layers, dan type system, semakin dahsyat output yang bisa dihasilkan oleh AI tooling Anda.

Dengan membagi workflow menjadi 4 tahapan deterministik:

  1. Pasang System Rules & Scaffolding Minimalis.
  2. Susun Skema Database Relasional (Prisma) sebagai Single Source of Truth.
  3. Generate Safe Server Actions dengan kontrak Zod.
  4. Binding UI Komponen via Hook & Safe Actions.

Anda memangkas waktu setup repetitif dari 2 hari menjadi 15 menit tanpa mengorbankan kualitas arsitektur kode.

Action Items untuk Anda Hari Ini:

  1. Simpan template .cursorrules di atas ke dalam catatan atau gist lokal Anda.
  2. Buat satu repositori template kosong berisi skema Prisma multi-tenant di GitHub.
  3. Coba ikuti urutan 4 tahap prompt chaining ini untuk fitur micro-SaaS Anda berikutnya. Ukur berapa menit yang Anda hemat.

Butuh penyesuaian khusus untuk project Anda?

Jika situasi operasional atau arsitektur sistem bisnis Anda membutuhkan solusi kustom, diskusikan langsung bersama tim engineer kami. Anda juga bisa melihat rincian layanan vour.dev atau menghitung estimasi biaya project lebih dulu.

Mulai Project