Skip to main content

Overview

Featul uses Turborepo to manage a monorepo containing multiple applications and shared packages. This allows for code reuse, consistent tooling, and efficient builds.

Package Manager

Featul uses Bun as its primary package manager for development:
{
  "packageManager": "bun@1.2.21"
}
Bun is preferred for its speed and native TypeScript support, though the project remains compatible with npm/pnpm.

Workspace Structure

Apps

Applications that can be deployed independently:
apps/
├── app/     # Main SaaS application (Next.js 16)
└── web/     # Marketing and documentation site
apps/app - Main Application
  • Next.js 16 with App Router
  • Turbopack for fast dev experience
  • Multi-tenant workspace interface
  • Feedback boards, roadmaps, changelogs
apps/web - Marketing Site
  • Public website and documentation
  • Landing pages and pricing
  • Blog and changelog feed

Packages

Shared code used across applications:
packages/
├── api/          # API layer (jstack routers)
├── auth/         # Authentication (better-auth)
├── db/           # Database schema and ORM
├── editor/       # Tiptap rich text editor
├── ui/           # Shared UI components
├── tsconfig/     # TypeScript configurations
└── eslint-config/# ESLint configurations

Package Details

@featul/api
  • jstack-based RPC API
  • Router definitions per domain
  • Middleware and validators
  • Type-safe client exports
@featul/auth
  • better-auth configuration
  • Session management
  • OAuth providers setup
  • Organization plugin
@featul/db
  • Drizzle ORM setup
  • Database schemas
  • Migration scripts
  • Database client singleton
@featul/editor
  • Tiptap editor configuration
  • Custom extensions
  • Markdown support
  • Mentions and rich formatting
@featul/ui
  • Radix UI components
  • Custom form components
  • Tailwind-styled primitives
  • Design system tokens
@featul/tsconfig
  • Base TypeScript configurations
  • Shared compiler options
  • Path aliases and module resolution
@featul/eslint-config
  • Shared ESLint rules
  • TypeScript linting
  • React/Next.js specific rules

Workspace Configuration

Root package.json

{
  "name": "featul",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "app:dev": "turbo run dev --filter=app",
    "web:dev": "turbo run dev --filter=web",
    "lint": "turbo run lint",
    "format": "prettier --write \"**/*.{ts,tsx,md}\"",
    "db:generate": "turbo run db:generate --filter=@featul/db",
    "db:push": "turbo run db:push --filter=@featul/db",
    "db:migrate": "turbo run db:migrate --filter=@featul/db",
    "db:studio": "turbo run db:studio --filter=@featul/db"
  }
}

Dependency Management

Packages reference each other using workspace:* protocol:
{
  "dependencies": {
    "@featul/api": "workspace:*",
    "@featul/auth": "workspace:*",
    "@featul/db": "workspace:*"
  }
}
This ensures local packages are always linked during development.

Turborepo Configuration

turbo.json

{
  "$schema": "https://v2-8-0.turborepo.dev/schema.json",
  "ui": "tui",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "!.next/cache/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "dependsOn": ["transit"]
    },
    "check-types": {
      "dependsOn": ["transit"]
    }
  }
}

Task Pipeline

build
  • Depends on dependencies being built first (^build)
  • Caches .next/ output (excluding cache directory)
  • Respects environment variables for builds
dev
  • No caching (always runs fresh)
  • Persistent task (long-running dev server)
  • Runs in parallel across workspaces
lint
  • Depends on transit (package compilation)
  • Caches results for speed
check-types
  • TypeScript type checking
  • Runs after transit phase
  • Parallelized across workspaces

Transit Phase

The transit task handles package compilation and preparation:
{
  "transit": {
    "dependsOn": ["^transit"]
  }
}
This ensures packages are built before dependent tasks run.

Development Workflow

Starting Development

Run all apps:
bun dev
This starts both app and web in parallel with hot reloading. Run specific app:
bun run app:dev  # Only main app
bun run web:dev  # Only marketing site
Using Turbo filters:
turbo run dev --filter=app          # Run app only
turbo run dev --filter=@featul/api  # Run API package

Building for Production

Build all:
bun run build
Build specific app:
turbo run build --filter=app

Type Checking

Check all packages:
bun run check-types
Check specific package:
turbo run check-types --filter=@featul/api

Linting and Formatting

Lint:
bun run lint
Format:
bun run format

Database Operations

All database commands are scoped to the @featul/db package:
bun run db:generate  # Generate migrations from schema changes
bun run db:push      # Push schema directly to database (dev)
bun run db:migrate   # Run migrations (production)
bun run db:studio    # Open Drizzle Studio (GUI)

Adding New Packages

Create Package Structure

mkdir -p packages/my-package/src
cd packages/my-package

Package Configuration

package.json:
{
  "name": "@featul/my-package",
  "version": "0.0.0",
  "private": true,
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts"
  },
  "scripts": {
    "check-types": "tsc --noEmit"
  },
  "devDependencies": {
    "@featul/tsconfig": "workspace:*",
    "typescript": "^5.7.3"
  }
}
tsconfig.json:
{
  "extends": "@featul/tsconfig/base.json",
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Using the Package

Add to consuming app’s package.json:
{
  "dependencies": {
    "@featul/my-package": "workspace:*"
  }
}
Then install dependencies:
bun install

Performance Tips

Cache Management

Turbo caches build outputs aggressively. Clear cache if needed:
turbo run build --force  # Ignore cache

Parallel Execution

Turborepo runs tasks in parallel where possible. Maximize parallelism by:
  • Keeping packages independent
  • Using ^task for dependency ordering
  • Avoiding circular dependencies

Selective Builds

Only build what changed:
turbo run build --filter=[main]  # Only changed packages since main branch

Troubleshooting

Type Errors in IDE

If TypeScript can’t find workspace packages:
bun install  # Re-link workspaces
Restart your TypeScript server in VS Code: Cmd+Shift+P → “TypeScript: Restart TS Server”

Dependency Issues

Clear and reinstall:
rm -rf node_modules
rm bun.lockb
bun install

Build Failures

Check build order. Packages must build before apps that depend on them. Verify dependsOn in turbo.json.

Best Practices

Package Boundaries

  • apps/ should consume packages, not provide them
  • packages/ should be framework-agnostic where possible
  • Avoid circular dependencies between packages

Versioning

All packages use 0.0.0 and are private. The monorepo is versioned as a whole.

Code Sharing

Extract shared code to packages:
  • UI components@featul/ui
  • API utilities@featul/api
  • Database queries@featul/db
  • Type definitions → Appropriate package

Testing

While not currently configured, testing should follow this structure:
packages/my-package/
├── src/
│   └── index.ts
└── tests/
    └── index.test.ts
Run tests with:
turbo run test

Build docs developers (and LLMs) love