Skip to content

leonardocouy/modern-monorepo-fullstack-kit

Repository files navigation

Modern Monorepo Fullstack Kit

A production-ready TypeScript monorepo starter kit with React 19, Express 5, Better Auth, and SQLite

✨ Features

  • πŸ—οΈ Modern Monorepo Architecture - Turborepo + pnpm workspaces for blazing-fast builds
  • βš›οΈ React 19 - Latest React with Vite 7 for lightning-fast development
  • πŸš€ Express 5 - Modern Express.js backend with TypeScript + ESM support
  • πŸ” Authentication - Better Auth with email/password + Google OAuth out of the box
  • πŸ’Ύ Database - SQLite + Drizzle ORM with type-safe queries
  • βœ… Type Safety - Strict TypeScript everywhere with Zod validation
  • 🎨 Styling - Tailwind CSS 4.x for rapid UI development
  • πŸ“ Code Quality - ESLint 9 (flat config) + Prettier pre-configured
  • πŸ”„ State Management - Tanstack Query for server state, React hooks for UI state
  • πŸ“ File-Based Routing - Tanstack Router with auto-generated route tree
  • πŸ›‘οΈ Security - Helmet.js security headers + CORS configured
  • ⚑ Developer Experience - Single command to run everything (pnpm dev)

πŸš€ Quick Start

Prerequisites

  • Node.js: v24.3.0+ (v22.13.1 minimum)
  • pnpm: 10.13.1 (enforced via packageManager field)
  • Platform: Linux/macOS/WSL (SQLite file-based database)

Installation

# Clone the repository
git clone <your-repo-url>
cd modern-monorepo-fullstack-kit

# Install dependencies
pnpm install

# Copy environment files
cp .env.example .env
cp apps/api/.env.example apps/api/.env
cp apps/app/.env.example apps/app/.env

# Generate database schema and run migrations
pnpm db:generate
pnpm db:migrate

# Start development servers (frontend + backend)
pnpm dev

The application will be available at:

πŸ“¦ Project Structure

modern-monorepo-fullstack-kit/
β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ api/                    # Express.js backend (port 8080)
β”‚   β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”‚   β”œβ”€β”€ routes/         # API route handlers
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ health.ts   # Health check endpoint
β”‚   β”‚   β”‚   β”‚   └── user.ts     # Protected user routes
β”‚   β”‚   β”‚   └── server.ts       # Express app setup
β”‚   β”‚   └── index.ts            # Entry point
β”‚   β”‚
β”‚   └── app/                    # React frontend (port 5173)
β”‚       β”œβ”€β”€ src/
β”‚       β”‚   β”œβ”€β”€ routes/         # File-based routes
β”‚       β”‚   β”‚   β”œβ”€β”€ __root.tsx  # Root layout
β”‚       β”‚   β”‚   β”œβ”€β”€ index.tsx   # Home page
β”‚       β”‚   β”‚   β”œβ”€β”€ auth/       # Auth pages (login, signup)
β”‚       β”‚   β”‚   └── _protected/ # Protected routes
β”‚       β”‚   β”œβ”€β”€ modules/
β”‚       β”‚   β”‚   └── auth/       # Auth module (hooks, layouts, pages)
β”‚       β”‚   └── lib/
β”‚       β”‚       └── api.ts      # Axios client
β”‚       β”‚
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ auth/                   # Better Auth configuration
β”‚   β”‚   β”œβ”€β”€ server.ts           # Auth server setup
β”‚   β”‚   └── client.ts           # Auth client (React)
β”‚   β”‚
β”‚   β”œβ”€β”€ config/                 # Shared configuration
β”‚   β”‚   └── index.ts            # Environment config
β”‚   β”‚
β”‚   β”œβ”€β”€ db/                     # Database layer
β”‚   β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”‚   β”œβ”€β”€ db.ts           # SQLite connection
β”‚   β”‚   β”‚   β”œβ”€β”€ schemas/        # Drizzle schemas
β”‚   β”‚   β”‚   β”‚   └── auth.ts     # Auth tables (user, session, etc.)
β”‚   β”‚   β”‚   └── queries/        # Database queries
β”‚   β”‚   └── drizzle.config.ts   # Drizzle Kit config
β”‚   β”‚
β”‚   β”œβ”€β”€ validator/              # Validation utilities
β”‚   β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”‚   β”œβ”€β”€ error.ts        # Custom error classes
β”‚   β”‚   β”‚   β”œβ”€β”€ validator.ts    # Zod middleware
β”‚   β”‚   β”‚   └── neverthrow.ts   # Result type helpers
β”‚   β”‚   └── index.ts
β”‚   β”‚
β”‚   └── eslint-config/          # Shared ESLint configs
β”‚       β”œβ”€β”€ base.js             # Base rules
β”‚       β”œβ”€β”€ typescript.js       # TypeScript rules
β”‚       β”œβ”€β”€ react.js            # React rules
β”‚       └── node.js             # Node.js rules
β”‚
β”œβ”€β”€ turbo.json                  # Turborepo configuration
β”œβ”€β”€ pnpm-workspace.yaml         # pnpm workspace config
└── package.json                # Root package.json

πŸ› οΈ Tech Stack

Frontend

Backend

Tooling

πŸ“ Available Scripts

Development

# Start both frontend and backend
pnpm dev

# Start individually
pnpm dev:api          # Backend only (http://localhost:8080)
pnpm dev:app          # Frontend only (http://localhost:5173)

Database

# Generate migration from schema changes
pnpm db:generate

# Apply migrations to database
pnpm db:migrate

# Open Drizzle Studio (visual DB editor)
pnpm db:studio        # http://localhost:8082

Code Quality

# Linting
pnpm lint             # Check all workspaces
pnpm lint:fix         # Auto-fix issues

# Formatting
pnpm format           # Format all files
pnpm format:check     # Check formatting (CI)

# Type checking
pnpm type-check       # TypeScript validation

Build

# Build all packages for production
pnpm build

πŸ” Authentication Setup

This starter kit comes with Better Auth pre-configured with:

Email & Password Authentication

Ready to use out of the box. Users can sign up and sign in with email/password.

Google OAuth (Optional)

To enable Google OAuth:

  1. Create a Google Cloud Project at console.cloud.google.com
  2. Enable Google OAuth and create credentials
  3. Add your credentials to .env:
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret

Protected Routes

Use the _protected layout to protect routes:

// apps/app/src/routes/_protected/dashboard.tsx
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/_protected/dashboard')({
  component: Dashboard,
});

function Dashboard() {
  return <div>Protected Dashboard</div>;
}

Protected API Routes

Use the authentication middleware:

// apps/api/src/routes/example.ts
import { authentication } from '@starter/auth/server';

router.get('/protected', async (req, res, next) => {
  try {
    const { user } = await authentication(req);
    res.json({ user });
  } catch (error) {
    next(error);
  }
});

🎨 Adding Features

Create a New Page

# Create new route file
touch apps/app/src/routes/about.tsx
// apps/app/src/routes/about.tsx
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/about')({
  component: AboutPage,
});

function AboutPage() {
  return <div>About Page</div>;
}

Route automatically available at: http://localhost:5173/about

Create a Database Schema

// packages/db/src/schemas/posts.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';

export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey(),
  title: text('title').notNull(),
  content: text('content'),
  createdAt: integer('created_at', { mode: 'timestamp' })
    .notNull()
    .$defaultFn(() => new Date()),
});
// packages/db/src/schemas/index.ts
export * from './auth';
export * from './posts'; // Add this line
# Generate and apply migration
pnpm db:generate
pnpm db:migrate

Create an API Endpoint

// apps/api/src/routes/posts.ts
import { Router, type Request, type Response } from 'express';
import { db } from '@starter/db';
import { posts } from '@starter/db/schemas';

const router = Router();

router.get('/', async (req: Request, res: Response) => {
  const allPosts = await db.select().from(posts);
  res.json(allPosts);
});

export { router as postsRouter };
// apps/api/src/server.ts
import { postsRouter } from './routes/posts';

// Add to routes section
app.use('/api/posts', postsRouter);

πŸ”’ Environment Variables

Backend (.env)

NODE_ENV=development
PORT=8080
HOST=localhost
APP_NAME=Starter Kit
APP_VERSION=0.1.0

FRONTEND_URL=http://localhost:5173
DATABASE_URL=./dev.db

# Optional - Google OAuth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

Frontend (.env)

VITE_API_URL=http://localhost:8080

πŸ“š Key Patterns

Error Handling

The project uses class-based errors for operational errors:

import { NotFoundError, ValidationError } from '@starter/validator';

// In route handler
if (!user) {
  throw new NotFoundError('User not found');
}

Request Validation

All endpoints should validate requests using Zod schemas:

import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  name: z.string().min(2),
});

// Validate in handler
const data = schema.parse(req.body);

Result Type Pattern

For service layer code, use Result types:

import { ok, err, type AsyncResult } from '@starter/validator';

async function getUserById(id: string): AsyncResult<User> {
  const user = await db.select().from(users).where(eq(users.id, id));
  if (!user) return err(new NotFoundError('User not found'));
  return ok(user);
}

🚒 Deployment

Build for Production

pnpm build

Recommended Platforms

Frontend:

Backend:

Database:

  • Turso - SQLite in the cloud
  • Or migrate to PostgreSQL with minimal changes

πŸ“– Documentation

For detailed documentation, see:

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

πŸ“„ License

MIT License - see LICENSE file for details

πŸ™ Acknowledgments

Built with amazing open-source projects:

  • React, Express, TypeScript
  • Better Auth, Drizzle ORM, Tanstack Router
  • Turborepo, pnpm, Vite

Happy Coding! πŸŽ‰

Made with ❀️

About

Production-ready fullstack TypeScript monorepo with React 19, Express 5, Better Auth & SQLite

Resources

License

Contributing

Stars

4 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors