UNPKG

create-bun-stack

Version:

Rails-inspired fullstack application generator for Bun

694 lines (505 loc) 15.8 kB
# CLAUDE.md - Complete Project Documentation This document contains comprehensive documentation about the create-bun-stack project, including architecture decisions, implementation details, and guidelines for future development. ## Table of Contents 1. [Project Overview](#project-overview) 2. [Architecture & Design Decisions](#architecture--design-decisions) 3. [CLI Generator Implementation](#cli-generator-implementation) 4. [Testing Strategy](#testing-strategy) 5. [Security Implementation](#security-implementation) 6. [Database Architecture](#database-architecture) 7. [Build System](#build-system) 8. [Common Development Tasks](#common-development-tasks) 9. [Troubleshooting Guide](#troubleshooting-guide) 10. [Performance Optimizations](#performance-optimizations) 11. [Deployment Considerations](#deployment-considerations) ## Project Overview create-bun-stack is a Rails-inspired fullstack application generator for Bun. It generates a complete, production-ready application with: - **Frontend**: React 18 with TypeScript, React Router, and Tailwind CSS - **Backend**: Bun.serve() with file-based routing and middleware - **Database**: Drizzle ORM with PostgreSQL primary and SQLite fallback - **Authentication**: JWT-based auth with secure password hashing - **Testing**: Integration tests using Bun's built-in test runner - **Security**: CSRF protection, secure headers, input validation ### Key Philosophy Following Rails' "Convention over Configuration" principle, we make opinionated choices that allow developers to be productive immediately while maintaining flexibility for customization. ## Architecture & Design Decisions ### 1. **Bun-First Approach** We leverage Bun's built-in capabilities wherever possible: ```typescript // Use Bun.serve() instead of Express Bun.serve({ port: 3000, fetch: router.fetch, }); // Use Bun.password instead of bcrypt const hash = await Bun.password.hash(password); // Use Bun's built-in test runner import { test, expect } from "bun:test"; ``` ### 2. **File-Based API Routing** Similar to Next.js but simpler: ```typescript // src/server/routes/users.ts export const users = { "/": { GET: getAllUsers, POST: createUser, }, "/:id": { GET: getUser, PUT: updateUser, DELETE: deleteUser, }, }; ``` ### 3. **Middleware Architecture** Middleware runs in a specific order for security and functionality: 1. **CORS** - Handle cross-origin requests 2. **Security Headers** - Apply security headers 3. **CSRF Protection** - Validate CSRF tokens 4. **Authentication** - Verify JWT tokens 5. **Route Handler** - Process the request ```typescript // Middleware composition const handler = compose( corsMiddleware, securityHeadersMiddleware, csrfMiddleware, authMiddleware, routeHandler ); ``` ### 4. **Dual Database Support** PostgreSQL for production, SQLite for development: ```typescript // Automatic detection and fallback const DATABASE_URL = process.env.DATABASE_URL; const isPostgres = DATABASE_URL?.startsWith("postgresql://"); export const db = isPostgres ? drizzle(postgres(DATABASE_URL)) : drizzle(new Database("./app.db")); ``` ### 5. **Repository Pattern** Database operations are abstracted into repositories: ```typescript interface UserRepository { findAll(): Promise<User[]>; findById(id: string): Promise<User | null>; create(data: CreateUserData): Promise<User>; update(id: string, data: UpdateUserData): Promise<User | null>; delete(id: string): Promise<boolean>; } ``` ## CLI Generator Implementation ### Template System The CLI uses a single-file approach with template literals: ```typescript const files: Record<string, string> = { "src/server/index.ts": ` import { serve } from "bun"; // ... template content `, // ... more files }; ``` ### Template Literal Escaping When generating files that contain template literals, we escape them: ```typescript // In the template const message = \`Hello \${name}\`; // This prevents interpolation during generation ``` ### Project Generation Flow 1. **Check Bun installation** - Prompt to install if missing 2. **Create project structure** - All directories and files 3. **Install dependencies** - Using `bun install` 4. **Setup database** - Run migrations and optional seed 5. **Build CSS** - Initial Tailwind compilation 6. **Start dev server** - Launch the application ## Testing Strategy ### Integration Testing Approach We use real HTTP requests against a running server: ```typescript test("creates a user", async () => { const response = await fetch("http://localhost:3000/api/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Test User" }), }); expect(response.status).toBe(201); }); ``` ### Test Isolation Each test uses unique data to avoid conflicts: ```typescript const timestamp = Date.now(); const testEmail = `test-${timestamp}@example.com`; ``` ### Key Testing Decisions 1. **No mocking fetch** - We removed global fetch mocks that were causing test pollution 2. **Real server required** - Tests expect `bun run dev` to be running 3. **Database cleanup** - Tests clean up after themselves 4. **Parallel execution** - Tests can run concurrently ### Test Organization ``` tests/ ├── app/ # Frontend component tests ├── server/ # API endpoint tests ├── db/ # Database layer tests ├── lib/ # Utility function tests └── helpers.ts # Shared test utilities ``` ## Security Implementation ### CSRF Protection Double-submit cookie pattern: 1. Server generates CSRF token on login/register 2. Token sent in response body AND as HttpOnly cookie 3. Client includes token in X-CSRF-Token header 4. Server validates token matches cookie ```typescript // Generate token const csrfToken = crypto.randomUUID(); const csrfCookie = await Bun.password.hash(csrfToken); // Validate token const isValid = await Bun.password.verify(headerToken, cookieToken); ``` ### Security Headers Applied based on content type: ```typescript // HTML responses get full CSP "Content-Security-Policy": isDev ? "default-src 'self' 'unsafe-inline' 'unsafe-eval';" : "default-src 'self'; script-src 'self' 'unsafe-inline';"; // API responses get basic security "X-Content-Type-Options": "nosniff" "X-Frame-Options": "DENY" ``` ### Authentication Flow 1. **Registration** - Hash password, create user, generate JWT 2. **Login** - Verify password, generate JWT and CSRF token 3. **Protected Routes** - Validate JWT, check CSRF for mutations 4. **Logout** - Clear CSRF token (JWT remains valid until expiry) ## Database Architecture ### Schema Design Using Drizzle ORM for type-safe queries: ```typescript export const users = sqliteTable("users", { id: text("id") .primaryKey() .$defaultFn(() => createId()), email: text("email").notNull().unique(), name: text("name").notNull(), password: text("password"), createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`), }); ``` ### Migration Strategy - **Development**: Use `drizzle-kit push` for rapid iteration - **Production**: Use `drizzle-kit generate` for versioned migrations ### Dual Database Queries Queries work identically for both databases: ```typescript // Works for both PostgreSQL and SQLite const users = await db.select().from(usersTable); ``` ## Build System ### Development Build Hot reload with Bun's built-in watcher: ```bash bun --hot src/server/index.ts ``` ### Production Build 1. **CSS Build**: Tailwind processes all styles 2. **Frontend Build**: Bun bundles React app 3. **Server**: Run directly with Bun (no build needed) ```dockerfile FROM oven/bun:1 WORKDIR /app COPY . . RUN bun install --frozen-lockfile RUN bun run build:css EXPOSE 3000 CMD ["bun", "src/server/index.ts"] ``` ## Common Development Tasks ### Adding a New Model 1. **Define schema** in `src/db/schema.ts`: ```typescript export const posts = sqliteTable("posts", { id: text("id") .primaryKey() .$defaultFn(() => createId()), title: text("title").notNull(), content: text("content"), authorId: text("author_id").references(() => users.id), }); ``` 2. **Create repository** in `src/db/repositories/`: ```typescript export class PostRepository { async findAll() { return db.select().from(posts); } // ... other methods } ``` 3. **Add routes** in `src/server/routes/posts.ts`: ```typescript export const posts = { "/": { GET: async () => { const posts = await postRepo.findAll(); return Response.json(posts); }, }, }; ``` 4. **Update router** in `src/server/router.ts` ### Adding Authentication to a Route Use the `requireAuth` middleware: ```typescript import { requireAuth } from "../middleware/auth"; export const protectedRoute = { "/": { GET: [ requireAuth, async (req) => { const user = req.user; // Set by middleware return Response.json({ message: `Hello ${user.name}` }); }, ], }, }; ``` ### Adding a New Test 1. Create test file following the naming convention 2. Import test utilities 3. Write integration tests ```typescript import { test, expect, describe } from "bun:test"; describe("Feature", () => { test("does something", async () => { const response = await fetch("http://localhost:3000/api/feature"); expect(response.status).toBe(200); }); }); ``` ## Troubleshooting Guide ### Common Issues #### 1. **Test Failures: "Cannot read properties of undefined"** **Cause**: Global fetch was mocked and not restored **Solution**: We removed fetch mocking from tests #### 2. **Database Connection Errors** **PostgreSQL**: ```bash # Check if PostgreSQL is running pg_isready # Create database createdb myapp_dev ``` **SQLite fallback**: ```bash # Remove DATABASE_URL to use SQLite unset DATABASE_URL ``` #### 3. **CSRF Token Mismatch** **Causes**: - Cookie not being sent (check SameSite settings) - Token regenerated (multiple logins) - Cross-origin request without proper CORS #### 4. **Port Already in Use** ```bash # Find process lsof -i :3000 # Kill process kill -9 <PID> ``` ### Debug Mode Enable debug logging: ```typescript // In server/index.ts const DEBUG = process.env.DEBUG === "true"; if (DEBUG) { console.log(`[${req.method}] ${req.url}`); } ``` ## Performance Optimizations ### Bun-Specific Optimizations 1. **Use Bun.write() for file operations**: ```typescript await Bun.write("file.txt", "content"); ``` 2. **Leverage Bun's speed**: - Start time: ~10ms vs 100ms+ for Node.js - Test execution: 2-3x faster - Installation: 10x faster than npm 3. **Optimize imports**: ```typescript // Bun resolves these at compile time import config from "./config.json"; ``` ### Database Optimizations 1. **Connection pooling** (PostgreSQL): ```typescript const sql = postgres(DATABASE_URL, { max: 10, idle_timeout: 20, }); ``` 2. **Prepared statements**: ```typescript const stmt = db .select() .from(users) .where(eq(users.id, sql.placeholder("id"))) .prepare(); const user = await stmt.execute({ id: "123" }); ``` 3. **Indexes**: ```sql CREATE INDEX idx_users_email ON users(email); ``` ### Frontend Optimizations 1. **Code splitting**: ```typescript const AdminPanel = lazy(() => import("./AdminPanel")); ``` 2. **React Query caching**: ```typescript const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5 minutes }, }, }); ``` ## Deployment Considerations ### Environment Variables Required for production: ```env NODE_ENV=production DATABASE_URL=postgresql://... JWT_SECRET=<random-string> PORT=3000 ``` ### Database Migrations Run before starting the app: ```bash bun run db:push ``` ### Health Checks The `/api/health` endpoint returns: - Server status - Database connectivity - Version information ### Monitoring Recommended tools: - **Logs**: stdout/stderr (works with any log aggregator) - **Metrics**: Prometheus-compatible `/metrics` endpoint - **Errors**: Sentry or similar error tracking ### Scaling Considerations 1. **Horizontal scaling**: App is stateless (except CSRF tokens) 2. **Database**: Consider read replicas for PostgreSQL 3. **Static assets**: Serve via CDN 4. **Sessions**: Move CSRF to Redis for multi-instance ## Bun-Specific Features and Patterns ### Using Bun's Built-in APIs Always prefer Bun's native APIs over npm packages: ```typescript // ❌ Don't use external packages import bcrypt from "bcryptjs"; import { config } from "dotenv"; // ✅ Use Bun's built-ins const hash = await Bun.password.hash(password); // .env is loaded automatically ``` ### Shell Commands Use Bun's shell API: ```typescript import { $ } from "bun"; // Run shell commands const files = await $`ls -la`.text(); // With error handling try { await $`git commit -m "Update"`; } catch (error) { console.error("Commit failed:", error); } ``` ### WebSockets Built-in WebSocket support: ```typescript Bun.serve({ fetch(req, server) { if (server.upgrade(req)) { return; // WebSocket upgrade successful } return new Response("Regular HTTP response"); }, websocket: { open(ws) { ws.send("Connected!"); }, message(ws, message) { ws.send(`Echo: ${message}`); }, }, }); ``` ### File Handling Bun's file API is much faster: ```typescript // Read file const file = Bun.file("./data.json"); const data = await file.json(); // Write file await Bun.write("./output.txt", "Hello World"); // Stream large files const stream = Bun.file("./large.csv").stream(); ``` ## Project Maintenance ### Updating Dependencies ```bash # Check outdated bunx npm-check-updates # Update all bun update # Update specific bun update react ``` ### Code Quality The project uses: - **TypeScript** - Strict mode enabled - **Biome** - Fast linter and formatter - **Prettier** - Code formatting - **Bun test** - Built-in test runner Run all checks: ```bash bun run check ``` ### Contributing Guidelines 1. **Follow existing patterns** - Consistency is key 2. **Write tests** - All features need tests 3. **Update types** - Keep TypeScript happy 4. **Document changes** - Update relevant docs 5. **Performance matters** - Leverage Bun's speed ## Future Enhancements Planned improvements: 1. **WebSocket support** - Real-time features 2. **GraphQL option** - Alternative to REST 3. **Service worker** - Offline support 4. **Admin panel** - Auto-generated CRUD UI 5. **More databases** - MySQL, MongoDB support 6. **Email service** - Transactional emails 7. **Job queue** - Background task processing 8. **File uploads** - S3/local storage 9. **API documentation** - OpenAPI/Swagger 10. **Monitoring** - Built-in observability ## Conclusion create-bun-stack demonstrates how Bun's modern runtime can be combined with proven patterns from Rails to create a productive fullstack development experience. By leveraging Bun's built-in features and maintaining a focus on developer experience, we've created a tool that makes it easy to build production-ready applications quickly. The key is balancing convention with flexibility - providing sensible defaults while allowing developers to customize when needed. This approach, combined with Bun's performance advantages, creates a compelling alternative to existing JavaScript frameworks. # important-instruction-reminders Do what has been asked; nothing more, nothing less. NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. NEVER proactively create documentation files (\*.md) or README files. Only create documentation files if explicitly requested by the User.