the best lightweight web dev stack built on bun
1# Tacy Stack
2
3This is (in my very biased opinion) the absolute best and most enjoyable way to build detailed web apps on top of Bun.
4
5It uses Lit, Bun, and Drizzle as the main stack and they all work together to make a wonderful combo.
6
7## Quick Start
8
9```bash
10bunx tacy-stack@latest
11```
12
13The CLI will guide you through creating a new project with an interactive prompt.
14
15**Manual setup:**
16
17```bash
18git clone <this-repo> my-app
19cd my-app
20bun run setup
21bun dev
22```
23
24Your server will be running at `http://localhost:3000` with hot module reloading. Just edit any `.ts`, `.html`, or `.css` file and watch it update in the browser.
25
26## How does it work?
27
28The development flow is really nice in my opinion. The server imports HTML files as route handlers. Those HTML files import TypeScript components using `<script type="module">`. The components are just Lit web components that self-register as custom elements. Bun sees all this and bundles everything automatically including linked styles.
29
30```typescript
31// src/index.ts - Server imports HTML as routes
32import indexHTML from "./pages/index.html";
33
34Bun.serve({
35 port: 3000,
36 routes: {
37 "/": indexHTML,
38 },
39 development: {
40 hmr: true,
41 console: true,
42 },
43});
44```
45
46```html
47<!-- src/pages/index.html -->
48<!DOCTYPE html>
49<html lang="en">
50 <head>
51 <link rel="stylesheet" href="../styles/main.css" />
52 </head>
53 <body>
54 <counter-component count="0"></counter-component>
55 <script type="module" src="../components/counter.ts"></script>
56 </body>
57</html>
58```
59
60```typescript
61// src/components/counter.ts
62import { LitElement, html, css } from "lit";
63import { customElement, property } from "lit/decorators.js";
64
65@customElement("counter-component")
66export class CounterComponent extends LitElement {
67 @property({ type: Number }) count = 0;
68
69 static styles = css`
70 :host {
71 display: block;
72 padding: 1rem;
73 }
74 `;
75
76 render() {
77 return html`
78 <div>${this.count}</div>
79 <button @click=${() => this.count++}>+</button>
80 `;
81 }
82}
83```
84
85The database uses Drizzle ORM for type-safe SQLite access. Schema changes are handled through migrations:
86
87```bash
88# Make changes to src/db/schema.ts, then:
89bun run db:push # Push schema to database
90bun run db:studio # Visual database browser
91```
92
93## Commands
94
95```bash
96bun dev # Development server with hot reload
97bun test # Run tests
98bun run db:generate # Generate migrations from schema
99bun run db:push # Push schema to database
100bun run db:studio # Open Drizzle Studio
101```
102
103The canonical repo for this is hosted on tangled over at [`dunkirk.sh/tacy-stack`](https://tangled.org/@dunkirk.sh/tacy-stack)
104
105<p align="center">
106 <img src="https://raw.githubusercontent.com/taciturnaxolotl/carriage/main/.github/images/line-break.svg" />
107</p>
108
109<p align="center">
110 <i><code>© 2025-present <a href="https://dunkirk.sh">Kieran Klukas</a></code></i>
111</p>
112
113<p align="center">
114 <a href="https://tangled.org/dunkirk.sh/tacy-stack/raw/main/README.md"><img src="https://img.shields.io/static/v1.svg?style=for-the-badge&label=License&message=MIT&logoColor=d9e0ee&colorA=363a4f&colorB=b7bdf8"/></a>
115</p>