An opinionated, production-ready boilerplate for building fullstack TypeScript applications with clear separation between backend and frontend. This template includes everything you need to start building a modern web application with best practices baked in.
Warning
This is intended solely for my own use. There are many problems and quirks that I'm well aware of, but I will not go into detail about them. This setup will likely not make sense to you. But feel free to fork and make it your own.
- pnpm workspaces for efficient dependency management
- Turborepo for fast, cached builds
- Clear separation:
apps/for applications,libs/for shared code - Support for multiple apps in the same repository
- Express.js with TypeScript and ESM modules
- Dependency injection pattern with
ApplicationContext - Multi-source configuration system (YAML files + environment variables)
- Schema validation with Zod
- Winston logger with JSON/text formats and rotating file support
- Graceful shutdown handling
- File system abstraction for testability
- GraphQL API with GraphQL Yoga
- WebSocket support for GraphQL subscriptions
- Type-safe resolvers via GraphQL Code Generator
- Modular GraphQL architecture with
graphql-modules - Drizzle ORM for type-safe database operations
- PostgreSQL database via
postgresdriver - Database migrations via Drizzle Kit
- Header-based authentication middleware
- Log streaming via GraphQL subscription
- Frontend static file serving (optional)
- Redis integration via BullMQ for background job processing
- Abstract queue and worker base classes for easy extension
- Bull Board UI for queue monitoring and management
- Configurable queues and workers per instance
- React 19 with React Router v7
- Vite for lightning-fast builds and HMR
- Tailwind CSS v4 (via @tailwindcss/vite plugin)
- shadcn/ui component library pre-configured
- PWA support with Workbox caching strategies
- Dark mode support via next-themes
- Sonner for toast notifications
- TanStack Form for type-safe forms
- Apollo Client for GraphQL queries, mutations, and subscriptions
- gql.tada for compile-time type-safe GraphQL operations
- Full TypeScript integration with generated schema types
- Multi-stage Dockerfile for optimized images
- Multi-architecture builds (ARM64 + AMD64)
- Separate builds for backend and frontend
- Production-ready with health checks
- Biome for lightning-fast linting and formatting
- Pre-commit hooks with Lefthook
- Consistent code style enforced across the monorepo
- TypeScript strict mode enabled
- GitHub Actions workflows included
- Automated linting, testing, and building
- Docker image publishing for develop and latest tags
- Multi-architecture Docker builds
- TypeScript path aliases (
@/for imports), resolved in the server build viatsc-alias - Watch modes for development
- Fast builds with caching
- Comprehensive npm scripts for common tasks
On GitHub:
- Click the "Use this template" button at the top of this repository
- Choose "Create a new repository"
- Name your repository and set visibility (public/private)
- Click "Create repository from template"
Or via GitHub CLI:
gh repo create my-app --private --clone --template KennethWussmann/boilerplate-fullstack-typescript
cd my-appThis repository ships Agent Skills in .agents/skills/, which any compatible coding agent picks up automatically. The project-bootstrap skill guides you through the entire customization of your new project.
- Open your coding agent in your new repository
- Ask it to bootstrap the project
- Reply to its questions
- Wait... Done!
You are now ready. Skip the next 2b. section.
The other bundled skills cover day-to-day work: backend-development for entities, repositories, services and GraphQL modules, frontend-development for pages, views, components, layouts and forms, and fullstack-feature for anything spanning both apps.
Two more keep your project and this template in touch after bootstrapping. boilerplate-sync pulls later template improvements — CI, Dockerfile, tooling, framework code — into your project without touching your product code or renaming your app. boilerplate-backport goes the other way: it takes something generic you built, strips the product-specific parts out of it, and lands it here with the documentation to match.
After creating your repository, update the following files:
{
"name": "your-project-name", // Change this
"description": "Your project description",
"author": "Your Name",
"license": "MIT" // Or your preferred license
}{
"name": "your-server-name", // Change this
"description": "Your server description",
"author": "Your Name",
"license": "MIT"
}{
"name": "your-web-app-name", // Change this
"version": "1.0.0" // Set your initial version
}Update the title and meta tags:
<title>Your App Name</title>
<meta name="description" content="Your app description">Update the PWA manifest with your app details:
{
"name": "Your App Name",
"short_name": "YourApp",
"description": "Your app description",
"theme_color": "#your-color",
"background_color": "#your-color"
}When building Docker images, use your app names:
docker build --build-arg APP_NAME=your-server-name --build-arg VERSION=1.0.0 .If you plan to use Docker image publishing workflows, update .github/workflows/*.yml:
- Update repository references
- Configure Docker registry credentials
- Update image naming conventions
- Delete or update
AGENTS.md(instructions for AI coding agents) - Update this README with your project-specific information
- Add your own license file if you changed the license
- Node.js 24
- pnpm 10.26.2+ (will be automatically used via
packageManagerfield) - PostgreSQL instance
- Redis instance
# Clone your new repository
git clone https://github.com/your-username/your-repo.git
cd your-repo
# Install dependencies (pnpm will be automatically installed if needed)
pnpm install# Build all apps
pnpm build
# Build in watch mode
pnpm build:watch
# Run the backend server (in apps/server/)
cd apps/server
pnpm dev # Runs on http://localhost:8080 by default
# GraphQL endpoint: http://localhost:8080/graphql
# GraphQL subscriptions: ws://localhost:8080/graphql
# Database operations (in apps/server/)
pnpm db:generate # Generate migration from schema changes
pnpm db:migrate # Apply migrations to database
# Run the frontend (in apps/web/)
cd apps/web
pnpm dev # Runs on http://localhost:5173 by default
# Generate GraphQL types (backend)
cd apps/server
pnpm codegen # Generates TypeScript types from .graphql files
# Generate GraphQL types (frontend)
cd apps/web
pnpm codegen # Generates gql.tada types from server schema
# Run linting and formatting
pnpm check
# Auto-fix linting issues
pnpm check:fix
# Run tests (when you add them)
pnpm test
pnpm test:watch# Build all applications for production
pnpm build
# Build Docker images
docker build --build-arg APP_NAME=server --build-arg VERSION=1.0.0 -t your-server:latest .
docker build --build-arg APP_NAME=web --build-arg VERSION=1.0.0 -t your-web:latest .
# Run Docker containers
docker run -p 8080:8080 your-server:latest
docker run -p 80:80 your-web:latest.
├── apps/
│ ├── server/ # Backend Express application
│ │ ├── src/
│ │ │ ├── config/ # Configuration management
│ │ │ ├── database/ # Database service and schema
│ │ │ ├── error/ # Error types
│ │ │ ├── file-system/ # File system abstraction
│ │ │ ├── http/ # HTTP server and routers
│ │ │ ├── log-streaming/ # Log streaming service
│ │ │ ├── logger/ # Winston logger setup
│ │ │ ├── redis/ # Redis service, queues, and workers
│ │ │ ├── utils/ # Utility functions
│ │ │ ├── applicationContext.ts # DI container
│ │ │ └── run.ts # Application entry point
│ │ ├── drizzle/ # Database migrations
│ │ ├── drizzle.config.ts # Drizzle Kit configuration
│ │ └── config.yaml # Server configuration
│ │
│ └── web/ # Frontend React application
│ ├── src/
│ │ ├── components/ # React components
│ │ │ └── ui/ # shadcn/ui components
│ │ ├── pages/ # Page components
│ │ ├── lib/ # Utilities
│ │ └── app.tsx # App entry point with routing
│ └── public/ # Static assets
│
├── libs/ # Shared libraries (empty by default)
│
├── .github/
│ └── workflows/ # GitHub Actions CI/CD
│
├── biome.json # Biome configuration
├── turbo.json # Turborepo configuration
├── pnpm-workspace.yaml # pnpm workspace configuration
└── Dockerfile # Multi-stage Docker build
pnpm build- Build all apps in the monorepopnpm build:watch- Build all apps in watch modepnpm test- Run tests across all appspnpm test:watch- Run tests in watch modepnpm check- Run Biome linting and formatting checkspnpm check:fix- Auto-fix linting issues (includes unsafe fixes)pnpm clean- Remove all build artifactspnpm update-dependencies- Update all dependencies across the monorepo
pnpm dev- Run server in development mode with hot reloadpnpm build- Compile TypeScript to JavaScriptpnpm build:watch- Compile in watch modepnpm db:generate- Generate database migration from schema changespnpm db:migrate- Apply pending migrations to database
pnpm dev- Run Vite dev server with HMRpnpm build- Build for productionpnpm preview- Preview production build locallypnpm shadcn- Add shadcn/ui components
- Express.js - Web framework
- TypeScript - Type safety
- GraphQL Yoga - GraphQL server
- graphql-modules - Modular GraphQL architecture
- GraphQL Code Generator - Type generation
- graphql-ws - GraphQL subscriptions over WebSocket
- Drizzle ORM - Type-safe ORM
- Drizzle Kit - Database migrations
- postgres - PostgreSQL client
- BullMQ - Redis-based job queue
- Bull Board - Queue monitoring UI
- Zod - Schema validation
- Winston - Logging
- date-fns - Date manipulation
- React 19 - UI framework
- React Router v7 - Client-side routing
- Vite - Build tool
- Apollo Client - GraphQL client
- gql.tada - Type-safe GraphQL operations
- Tailwind CSS v4 - Utility-first CSS
- shadcn/ui - Component library
- TanStack Form - Form management
- Sonner - Toast notifications
- next-themes - Dark mode
- Turborepo - Monorepo build system
- pnpm - Package manager
- Biome - Linter and formatter
- Lefthook - Git hooks
- tsx - TypeScript execution
The server supports configuration through YAML files and environment variables. Environment variables take precedence over YAML configuration.
Configuration files (searched in order):
config.local.yamlconfig.yamlconfig.local.ymlconfig.yml
Environment variables:
The most important variables to configure:
| Variable | Description | Default |
|---|---|---|
DATABASE_CONNECTION_URL |
PostgreSQL connection string | postgres://localhost:5432/server |
REDIS_HOST |
Redis host | 127.0.0.1 |
REDIS_PORT |
Redis port | 6379 |
REDIS_PASSWORD |
Redis password | — |
REDIS_WORKERS |
Worker configuration (JSON array) | — |
REDIS_QUEUES |
Queue configuration (JSON array) | — |
LOG_LEVEL |
Logging level (debug, info, warn, error, fatal, notice) |
info |
For the full environment variable reference see docs/configuration.md.
The web app includes PWA support. To enable PWA features in development:
PWA_DEV=true pnpm devThe backend uses a modular GraphQL architecture:
File Structure:
apps/server/src/http/routers/
├── graphql/
│ ├── graphQLRouter.ts # GraphQL server setup
│ ├── graphQLContext.ts # Context and PubSub types
│ ├── scalars.ts # Custom scalar definitions
│ ├── common.graphql # Common scalars
│ └── generated/ # Auto-generated types
└── [feature]/
├── graphql/
│ ├── [feature].graphql # Schema definition
│ ├── [feature].query.ts # Query resolvers
│ ├── [feature].mutation.ts # Mutation resolvers (optional)
│ └── [feature].subscription.ts # Subscription resolvers (optional)
└── [feature]Module.ts # Module registration
Adding a New GraphQL Feature:
- Create feature directory with schema:
cd apps/server/src/http/routers
mkdir -p myfeature/graphql- Define schema in
myfeature/graphql/myfeature.graphql:
type Query {
myData: MyData!
}
type MyData {
id: ID!
name: String!
}- Create resolvers in
myfeature/graphql/myfeature.query.ts:
import type { ResolversGQL } from '../../graphql/index.js';
export const myFeatureQuery: Partial<ResolversGQL> = {
Query: {
myData: () => ({
id: '1',
name: 'Example',
}),
},
};- Create module in
myfeature/myFeatureModule.ts:
import { loadFiles } from '@graphql-tools/load-files';
import { createModule } from 'graphql-modules';
import { scalars } from '../graphql/index.js';
import { myFeatureQuery } from './graphql/myfeature.query.js';
export const MyFeatureModule = async () => {
return createModule({
id: 'myfeature',
dirname: import.meta.dirname,
typeDefs: await loadFiles('**/*.graphql', {
globOptions: { cwd: import.meta.dirname },
}),
resolvers: [scalars, myFeatureQuery],
});
};- Register in
HTTPServer.setupRouters()(apps/server/src/http/httpServer.ts):
new GraphQLRouter(
/* ... */,
[HealthModule, MyFeatureModule] // Add your module
)- Generate types:
cd apps/server
pnpm codegenThe frontend uses Apollo Client with gql.tada for type safety:
Usage Example:
import { useQuery, useSubscription } from '@apollo/client/react';
import { graphql } from '@/lib/graphql';
// Define query with gql.tada
const MyQuery = graphql(`
query MyData {
myData {
id
name
}
}
`);
// Use in component
function MyComponent() {
const { data } = useQuery(MyQuery);
return <div>{data?.myData.name}</div>;
}Subscriptions Example:
const MySubscription = graphql(`
subscription OnDataChange {
dataChanged {
id
name
}
}
`);
function MyComponent() {
useSubscription(MySubscription, {
onData: ({ data }) => {
console.log('Data updated:', data);
},
});
}Environment Variables:
VITE_API_URL- GraphQL HTTP endpoint (default:http://localhost:8080/graphql)VITE_WS_URL- GraphQL WebSocket endpoint (default:ws://localhost:8080/graphql)VITE_GRAPHQL_EXPLORER_ENABLED- Enable the in-app GraphiQL Explorer (default:false)
TypeScript Integration:
- Backend types generated to
apps/server/src/http/routers/graphql/generated/ - Frontend types generated to
apps/web/src/lib/graphql/graphql-env.d.ts - Schema exported to
apps/server/schema.graphqlfor client consumption
To create shared code between apps:
- Create a new package in
libs/:
mkdir -p libs/my-shared-lib
cd libs/my-shared-lib
pnpm init- Add it to your workspace and import it in apps:
{
"dependencies": {
"@repo/my-shared-lib": "workspace:*"
}
}