Architecture and Technologies of Makerkit Next.js Prisma

Understand the technologies, architecture, and patterns used in Makerkit. This foundation will help you navigate the codebase and build features effectively.

Before building TeamPulse, let's understand what Makerkit provides and how it's structured. This foundation will help you navigate the codebase and understand the patterns you'll use throughout the course.


What is Makerkit?

Makerkit is a SaaS starter kit that provides the foundational features every SaaS application needs:

  • Authentication - Sign up, sign in, password reset, OAuth, MFA
  • Multi-tenancy - Organizations, team members, invitations
  • Billing - Subscriptions, checkout, customer portal
  • Admin Dashboard - User management, metrics, impersonation
  • Email - Transactional emails with templates
  • Settings - User profile, organization settings, preferences

Instead of building these from scratch, you start with a working foundation and customize for your specific product.

B2B vs B2C Modes

Makerkit supports two account modes:

ModeDescriptionUse Case
B2B (organizations-only)Users belong to organizationsTeam collaboration tools, business software
B2C (personal-accounts-only)Users have individual accountsConsumer apps, personal tools
HybridUsers can have bothPlatforms with personal and team features

In this course, we use B2B mode for TeamPulse - a team feedback tool.


Core Technologies

The Stack

┌─────────────────────────────────────────────────────────┐
│ Frontend │
│ Next.js 16 + React 19 + TypeScript + Tailwind CSS 4 │
├─────────────────────────────────────────────────────────┤
│ UI Components │
│ shadcn/ui + Base UI + Lucide Icons │
├─────────────────────────────────────────────────────────┤
│ Authentication + Subscriptions + Multi-tenancy │
│ Better Auth │
├─────────────────────────────────────────────────────────┤
│ Database │
│ Prisma ORM + PostgreSQL │
├─────────────────────────────────────────────────────────┤
│ Payments │
│ Stripe │
└─────────────────────────────────────────────────────────┘

Next.js 16 with App Router

Next.js is the React framework that powers the application:

  • App Router - File-based routing with layouts and loading states
  • Server Components - Fetch data on the server, reduce client JavaScript
  • Server Actions - Call server functions directly from components
  • Streaming - Progressive rendering with Suspense

React 19

The latest React version with:

  • Server Components - Default for new components
  • Server Actions - Form handling with automatic pending states

TypeScript

Full type safety from database to UI:

  • Type-safe database queries with Prisma
  • Type-safe API with Zod validation
  • Type-safe forms with react-hook-form

Tailwind CSS 4 + shadcn/ui

Styling approach:

  • Tailwind CSS 4 - Utility-first CSS framework
  • shadcn/ui - Pre-built accessible components
  • Base UI - Headless component primitives
  • Lucide Icons - Icon library

Better Auth

Authentication framework that handles:

  • Email/password authentication
  • OAuth providers (Google, GitHub, etc.)
  • Multi-factor authentication (TOTP)
  • Session management
  • Organizations and teams
  • Role-based access control
  • Subscriptions and billing

Prisma ORM

Type-safe database toolkit:

  • Schema defined in Prisma schema language (packages/database/src/prisma/schema.prisma)
  • SQL-like query builder
  • Automatic migrations
  • Direct PostgreSQL access

PostgreSQL

As our relational database, we will be using PostgreSQL by default. Postgres is the most popular open-source relational database in the world and it's a great choice for most applications.

Stripe

As our payment processor, we will be using Stripe by default. Stripe is the most popular payment processor in the world and it's a great choice for most applications.


Turborepo & Monorepo Structure

Makerkit uses a monorepo - multiple packages in one repository, managed by Turborepo.

Why a Monorepo?

  • Code Sharing - Reuse packages across apps
  • Consistent Tooling - Same linting, formatting, TypeScript config
  • Atomic Changes - Update related code together
  • Simplified Dependencies - One pnpm install for everything

How Turborepo Works

Turborepo orchestrates tasks across packages:

pnpm dev # Starts all apps in development
pnpm typecheck # Type-checks all packages
pnpm lint:fix # Lints all packages
pnpm format:fix # Formats all code

Key features:

  • Task Caching - Skips unchanged packages
  • Parallel Execution - Runs independent tasks concurrently
  • Dependency Graph - Builds packages in correct order

Package Structure

makerkit/
├── apps/
│ ├── web/ # Main Next.js application
│ └── e2e/ # Playwright end-to-end tests
├── packages/
│ ├── ui/ # shadcn/ui components
│ ├── database/ # Prisma schema & migrations
│ ├── better-auth/ # Auth configuration & plugins
│ ├── auth/ # Auth utilities & middleware
│ ├── billing/ # Payment provider abstraction
│ │ ├── api/ # Billing API endpoints
│ │ ├── core/ # Core billing logic
│ │ ├── hooks/ # Billing React hooks
│ │ ├── stripe/ # Stripe integration
│ │ └── ui/ # Billing UI components
│ ├── email-templates/ # React Email templates
│ ├── mailers/ # Email sending (Resend, Nodemailer)
│ ├── rbac/ # Role-based access control
│ ├── account/ # Account features (hooks, settings, components)
│ ├── team-accounts/ # Team account features
│ ├── admin/ # Admin dashboard
│ ├── i18n/ # Internationalization
│ ├── monitoring/ # Error tracking (Sentry)
│ ├── analytics/ # Analytics integration
│ ├── storage/ # File storage
│ ├── otp/ # One-time passwords
│ ├── policies/ # Access policies
│ ├── cms/ # Content management
│ └── shared/ # Shared utilities
├── turbo.json # Turborepo configuration
├── pnpm-workspace.yaml # Workspace definition
└── package.json # Root dependencies

Package Dependencies

The diagram below is simplified to show key relationships. The actual dependency graph includes all 24+ packages.

┌─────────────┐
│ apps/web │
└──────┬──────┘
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐
│ ui │ │ admin │ │ billing │
└─────────┘ └────┬─────┘ └────┬─────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ auth │ │ database │
└────┬─────┘ └──────────┘
┌────────────┐
│better-auth │
└────────────┘

Application Architecture

Request Flow

┌──────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────┐
│ Browser │────▶│ Next.js │────▶│ Better Auth │────▶│ Database │
│ │◀────│ App Router │◀────│ Session │◀────│PostgreSQL│
└──────────┘ └──────────────┘ └─────────────┘ └──────────┘
  1. Browser makes request to Next.js route
  2. Next.js handles routing, renders Server Components
  3. Better Auth validates session, provides user context
  4. Database stores and retrieves data via Prisma

Multi-Tenancy Model

┌─────────────────────────────────────────────────────────┐
│ User │
│ (authentication) │
└─────────────────────────┬───────────────────────────────┘
│ belongs to (via membership)
┌─────────────────────────────────────────────────────────┐
│ Organization │
│ (tenant boundary) │
├─────────────────────────────────────────────────────────┤
│ Members │ Invitations │ Subscription │
├─────────────────────────────────────────────────────────┤
│ Application Data │
│ (scoped to organization) │
└─────────────────────────────────────────────────────────┘
  • Users can belong to multiple organizations
  • Organizations are the tenant boundary
  • All data is scoped to organizations
  • Members have roles (owner, admin, member)

Billing Flow

The billing flow is as follows:

┌──────────────┐ ┌─────────────┐ ┌──────────┐
│ Organization │────▶│ Stripe │────▶│ Checkout │
│ Billing │ │ Checkout │ │ Page │
└──────────────┘ └─────────────┘ └────┬─────┘
│ payment
┌──────────────┐ ┌─────────────┐ ┌──────────┐
│ Database │◀────│ Webhook │◀────│ Stripe │
│ Subscription │ │ Handler │ │ Events │
└──────────────┘ └─────────────┘ └──────────┘
  1. User initiates checkout from billing page
  2. Stripe Checkout handles payment
  3. Stripe sends webhook events
  4. Webhook handler updates subscription in database

Webhooks are managed by Better Auth - so you don't need to worry about them.


Key Patterns

Server Components vs Client Components

// Server Component (default) - fetches data on server
// renders only on the server side
export default async function DashboardPage() {
const data = await fetchDashboardData(); // Direct DB access
return <Dashboard data={data} />;
}
// Client Component - interactive UI
// renders both on the server and the client side (hydrated)
'use client';
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

Use Server Components for:

  • Data fetching
  • Accessing backend resources
  • Keeping sensitive logic server-side

Use Client Components for:

  • Interactivity (onClick, onChange)
  • Browser APIs
  • React hooks (useState, useEffect)

Server Actions with next-safe-action

// Define action with validation
export const updateProfileAction = authenticatedActionClient
.inputSchema(UpdateProfileSchema)
.action(async ({ parsedInput, ctx }) => {
const { user } = ctx;
await db.user.update({
where: { id: user.id },
data: { name: parsedInput.name },
});
revalidatePath('/settings');
return { success: true };
});
// Use in component
const { execute, status } = useAction(updateProfileAction);
const isPending = status === 'executing';

Data Loading Pattern

For data loading, we will be using the cache function from react. This function is used to cache the data on the server side on a per-request basis. This means that if you fetch the same data across multiple components, the data will be fetched only once and then cached for the duration of the request.

// Loader function (cached, server-side)
export const loadDashboardData = cache(async () => {
const orgId = await requireActiveOrganizationId();
return db.board.findMany({
where: { organizationId: orgId },
});
});
// Page component
export default async function DashboardPage() {
const data = await loadDashboardData();
return <DashboardView data={data} />;
}

Feature Overview

Authentication

FeatureImplementation
Email/PasswordBetter Auth credential provider
OAuthGoogle, GitHub, etc. via Better Auth
Magic LinksPasswordless sign-in
MFA/2FATOTP with authenticator apps
Password ResetEmail-based recovery
Email VerificationRequired before access

Organizations & Teams

FeatureImplementation
Create OrganizationAuto on signup or manual
Invite MembersEmail invitations with role
Role HierarchyOwner > Admin > Member
Remove MembersWith permission checks
Transfer OwnershipOwner can transfer

Billing & Subscriptions

FeatureImplementation
CheckoutStripe Checkout sessions
SubscriptionsMonthly/yearly plans
Plan LimitsSeat-based or feature-based
Customer PortalManage payment methods
WebhooksAutomatic status updates

Admin Dashboard

FeatureImplementation
User ManagementList, search, filter users
Ban/UnbanBlock user access
ImpersonationAct as user temporarily
MetricsUser counts, session stats
Organization ViewBrowse all organizations

Project Files Overview

Key Directories

PathPurpose
apps/web/app/Next.js routes and pages
apps/web/config/Application configuration
apps/web/lib/App-specific utilities
packages/ui/src/Shared UI components
packages/database/src/Database schema

Important Files

FilePurpose
apps/web/.env.localEnvironment variables (secrets)
apps/web/config/app.config.tsApp settings validation
apps/web/config/auth.config.tsOAuth providers config
apps/web/config/account-mode.config.tsAccount mode (B2B/B2C) settings
apps/web/config/feature-flags.config.tsFeature toggles
apps/web/config/team-account-navigation.config.tsxTeam sidebar navigation
apps/web/config/personal-account-navigation.config.tsxPersonal sidebar navigation
packages/database/src/prisma/schema.prismaDatabase schema

Route Structure

Note: This shows the key route groups. Each group contains additional sub-routes.

apps/web/app/[locale]/
├── (public)/ # Public pages
│ ├── page.tsx # Home page
│ ├── blog/ # Blog pages
│ ├── docs/ # Documentation
│ ├── faq/ # FAQ page
│ ├── changelog/ # Changelog pages
│ └── (legal)/ # Legal pages (privacy, terms)
├── auth/ # Auth pages (sign-in, sign-up, verify, password-reset)
├── (internal)/ # Protected app pages
│ ├── dashboard/ # User dashboard
│ ├── settings/ # Account settings
│ │ ├── billing/ # Subscription management
│ │ ├── members/ # Team members
│ │ ├── organization/ # Organization settings
│ │ ├── preferences/ # User preferences
│ │ └── security/ # Security settings (MFA, sessions)
│ ├── admin/ # Admin pages (users, organizations)
│ └── password-reset/ # Password reset flow
└── (invitation)/ # Invitation acceptance
apps/web/app/api/ # API routes (NOT under [locale])
├── auth/[...all]/ # Better Auth endpoints
└── version/ # Version endpoint

Environment Variables

Environment variables control application behavior. See apps/web/.env and apps/web/.env.development for defaults.

Public Variables (exposed to browser)

All variables that are prefixed with NEXT_PUBLIC_ are exposed to the browser. This makes it a good practice to use them for public information like the site URL, the account mode, and the Stripe publishable key - but never use them for sensitive information.

NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_ACCOUNT_MODE=organizations-only # or: personal-only, hybrid
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...

The NEXT_PUBLIC_ACCOUNT_MODE values control the app mode:

  • organizations-only - B2B mode, users must belong to an organization
  • personal-only - B2C mode, personal accounts only
  • hybrid - Mixed mode, supports both

Private Variables (server-only)

All variables that are not prefixed with NEXT_PUBLIC_ are exposed to the server only and won't be available on the client side.

DATABASE_URL=postgresql://...
BETTER_AUTH_SECRET=random-32-char-secret
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

Common Commands

Here are the commands you'll use most frequently during development.

Development

# Start Docker services first (PostgreSQL via Supabase, Mailpit)
pnpm compose:dev:up
# Start the web app
pnpm dev
# Start only the web app (if services already running)
pnpm --filter web dev

Note: pnpm dev starts the Next.js development server but does not start Docker services. Run pnpm compose:dev:up first to start the database and email services.

Code Quality

# Type-check all packages
pnpm typecheck
# Type-check specific package
pnpm --filter web typecheck
pnpm --filter @kit/database typecheck
# Lint and auto-fix issues
pnpm lint:fix
# Format code with Prettier
pnpm format:fix

Database

# Generate Prisma client
pnpm --filter @kit/database prisma:generate
# Push schema changes to database
pnpm --filter @kit/database prisma db push
# Run migrations
pnpm --filter @kit/database prisma:migrate
# Open Prisma Studio (database UI)
pnpm --filter @kit/database prisma:studio
# Seed the database with mock data
pnpm seed

Testing

# Run E2E tests
pnpm --filter e2e test
# Run E2E tests with UI
pnpm --filter e2e test:ui

Installing Dependencies

# Install all dependencies
pnpm install
# Add dependency to specific package
pnpm --filter web add package-name
pnpm --filter @kit/ui add package-name

Useful Shortcuts

CommandWhat it does
pnpm devStart everything for development
pnpm typecheckVerify TypeScript is happy
pnpm lint:fixFix linting issues automatically
pnpm format:fixFormat all code consistently
pnpm buildBuild for production

Development Workflow

A typical workflow after making changes:

# 1. Make your code changes
# 2. Check for TypeScript errors
pnpm typecheck
# 3. Fix linting issues
pnpm lint:fix
# 4. Format code
pnpm format:fix
# 5. Test locally
pnpm dev

Ready to Build

You now understand:

  • What Makerkit provides out of the box
  • The technology stack and why each piece was chosen
  • How the monorepo is structured with Turborepo
  • The application architecture and data flow
  • Key patterns for building features
  • Where to find things in the codebase

Next: In Module 1, you'll set up your development environment and configure TeamPulse as a B2B application.


Next: Setup & Configuration