Introduction
On this page 18
Stacks is a batteries-included framework for building TypeScript applications. Think of it as the Laravel of TypeScript - a complete ecosystem for building web apps, APIs, desktop apps, CLIs, and libraries.
What is Stacks
Stacks provides everything you need to build modern applications:
- Web Framework - Build SPAs, SSR apps, or static sites
- API Builder - RESTful APIs with automatic route generation
- ORM - Type-safe database interactions
- Authentication - Complete auth system out of the box
- Queue System - Background job processing
- Real-time - WebSocket support
- CLI Tools - Build command-line applications
- Desktop Apps - Native apps with Craft
- Mobile Apps - iOS/Android with Capacitor
- Cloud Deployment - One-command AWS deployment
Why Stacks
Zero External Dependencies
Stacks is built from the ground up in TypeScript with zero external runtime dependencies. This means:
- Smaller bundle sizes
- Faster performance
- Complete control over the codebase
- No supply chain vulnerabilities
TypeScript First
Everything is written in TypeScript with full type safety:
// Models are fully typed
const user = await User.find(1)
user.name // TypeScript knows this is string
// Routes are type-safe
router.get('/users/:id', async (request) => {
const id = request.params.id // Typed as string
})
// Validation is type-safe
const data = await request.validate({
email: 'required|email',
age: 'required|integer|min:18',
})
// data is typed as { email: string, age: number }
Developer Experience
Stacks prioritizes developer experience:
- Hot Module Replacement - Instant feedback during development
- Automatic Imports - Components and utilities auto-imported
- IDE Integration - Full IntelliSense support
- Error Messages - Clear, actionable error messages
- Documentation - Comprehensive guides and API docs
Laravel-Inspired
If you love Laravel, you'll feel at home with Stacks:
// Eloquent-style ORM
const posts = await Post.query()
.with('author', 'comments')
.where('published', true)
.orderBy('created_at', 'desc')
.paginate(15)
// Artisan-style CLI
buddy make:model Post
// Laravel-style routing
router.get('/posts', PostController.index)
router.resource('/users', UserController)
// Middleware
router.group({ middleware: ['auth', 'verified'] }, () => {
router.get('/dashboard', DashboardController.index)
})
Getting Started
Prerequisites
- Pantry, installed with
curl -fsSL https://pantry.dev | bash - macOS, Linux, or Windows through WSL2
Run pantry bootstrap once. Pantry then provisions Bun 1.3 or newer and the complete Stacks toolchain from the project dependency manifest.
Detailed Pantry behavior is versioned outside Stacks. The whitepaper pins its
package-manager and
registry contracts.
Quick Start
# Create a new project
panx @stacksjs/buddy new my-app
# Navigate to project
cd my-app
# Start development server
bun run dev
Your app is now running at <http://localhost:3000>.
Project Structure
my-app/
├── app/ # Your code
│ ├── Actions/ # Business logic, one file per action
│ ├── Models/ # Data models (defineModel)
│ ├── Jobs/ # Queue jobs
│ ├── Listeners/ # Event listeners
│ ├── Mail/ # Mailable classes
│ ├── Middleware/ # HTTP middleware
│ ├── Commands/ # Custom buddy commands
│ ├── Skills/ # Project-specific AI agent skills
│ ├── Routes.ts # Registers the files in routes/
│ ├── Events.ts # Event → listener map
│ ├── Gates.ts # Authorization gates
│ └── Scheduler.ts # Scheduled tasks
├── config/ # Typed configuration, one file per subsystem
├── database/
│ └── migrations/ # Generated from your models
├── public/ # Static assets
├── resources/
│ ├── views/ # stx pages
│ ├── components/ # stx components
│ ├── layouts/ # stx layouts
│ └── functions/ # Auto-imported browser functions
├── routes/
│ ├── api.ts # API routes
│ └── web.ts # Web routes
├── storage/ # Framework internals, defaults, and runtime state
├── tests/ # Bun test suites
├── AGENTS.md # Guidance every AI coding agent reads
└── tsconfig.json # The only tsconfig you own
Anything under app/ overrides the framework's equivalent in
storage/framework/defaults/app/. Create app/Models/User.ts and it wins over
the built-in one; leave it out and you get the default. That is how 60+ models
and 80+ actions ship usable out of the box while staying fully replaceable.
Core Concepts
Models
A model describes its schema, validation, factory, relationships and behavior in one place. Migrations are generated from it - you never hand-write the SQL.
// app/Models/Post.ts
import { defineModel } from '@stacksjs/orm'
import { schema } from '@stacksjs/validation'
export default defineModel({
name: 'Post',
table: 'posts',
traits: {
useUuid: true,
useTimestamps: true,
useSeeder: { count: 20 },
useApi: { uri: 'posts', routes: ['index', 'store', 'show', 'update', 'destroy'] },
},
belongsTo: ['Author'],
attributes: {
title: {
required: true,
fillable: true,
validation: { rule: schema.string().min(3).max(255) },
factory: faker => faker.lorem.sentence(),
},
status: {
fillable: true,
default: 'draft',
validation: { rule: schema.enum(['draft', 'published', 'archived']) },
},
},
} as const)
useApi alone generates the five REST actions and their routes. Then:
buddy generate:migrations # diff models against the schema
buddy migrate # apply
Actions
Actions hold your business logic, one per file, and are what routes point at.
// app/Actions/SendWelcomeEmail.ts
import { Action } from '@stacksjs/actions'
import { mail, template } from '@stacksjs/email'
export default new Action({
name: 'SendWelcomeEmail',
description: 'Sends a welcome email to newly registered users',
async handle(request) {
const to = request.get('to')
const { html, text } = await template('welcome', {
subject: 'Welcome!',
variables: { name: request.get('name'), email: to },
})
await mail.send({ to, subject: 'Welcome to Stacks!', html, text })
return { success: true }
},
})
Routes
Routes live in routes/ and reference actions by path:
// routes/api.ts
import { route } from '@stacksjs/router'
route.get('/posts', 'Actions/PostIndexAction')
route.post('/welcome', 'Actions/SendWelcomeEmail')
route.group({ prefix: '/admin', middleware: ['auth'] }, () => {
route.get('/stats', 'Actions/Dashboard/StatsAction')
})
Models, response, and the rest of the framework are auto-imported on the
server, so an action rarely needs an import for them.
Components
Build your UI with stx components:
<!-- resources/components/InputGroup.stx -->
<scriptserver
import { defineProps, withDefaults } from 'stx'
interface InputGroupProps {
id: string
label: string
type?: 'text' | 'email' | 'password'
placeholder?: string
}
const { id, label, type, placeholder } = withDefaults(
defineProps<InputGroupProps(),
{ type: 'text', placeholder: '' },
)
</script
<template
<divclass=""
<labelfor=""{{ label }}</label
<inputtype=""id=""placeholder=""
</div
</template
Components in resources/components/ are resolved by name - write
<InputGroup /> in a view with no import.
What's Next
- Installation - Set up your development environment
- Configuration - Configure your application
- Routing - Define your routes
- Models - Work with your database
- Views - Build your UI
Community
- GitHub - github.com/stacksjs/stacks
- Discord - Join our community
- Twitter - Follow @stacksjs
License
Stacks is open-source software licensed under the MIT license.