# Vibetuner > Vibetuner is a production-ready FastAPI project scaffolding tool that generates full-stack web applications with authentication, flexible database support (MongoDB or SQL), frontend, Docker deployment, and CLI tools pre-configured in seconds. Built by All Tuner Labs for rapid iteration and modern development. Important notes: - Vibetuner consists of three packages: a Python framework (`vibetuner`), a JavaScript package (`@alltuner/vibetuner`), and a Copier scaffolding template - The framework separates immutable framework code (`vibetuner` package) from your application code (`src/app/`) for clean updates - In all examples, `app` refers to your project's Python package (the directory under `src/`). The actual name depends on your project slug (e.g., `src/myproject/` for a project named "myproject") - HTMX is used instead of React/Vue for simplicity - server-rendered HTML with sprinkles of interactivity - All tools are chosen for speed: uv (Python), bun (JavaScript), Granian (ASGI server), Ruff (linting) - The project is designed to work excellently with AI coding assistants like Claude, Cursor, and ChatGPT ## Quick Start ### Prerequisites - **Python 3.11+**: [python.org/downloads](https://www.python.org/downloads/) - **Docker**: [docker.com/get-started](https://www.docker.com/get-started/) (for containerized development) That's it! Vibetuner handles the rest. ### Create Your First Project **Using uvx (Recommended)** No installation needed: ```bash uvx vibetuner scaffold new my-app ``` **Install Globally** ```bash uv tool install vibetuner vibetuner scaffold new my-app ``` **Interactive Setup** The scaffold command will ask you: - **Project name**: `my-app` - **Company name**: Your company/organization - **Author details**: Name and email - **Features**: OAuth providers, background jobs, etc. **Skip Prompts** Use defaults for everything: ```bash uvx vibetuner scaffold new my-app --defaults ``` ### Start Development ```bash cd my-app just dev ``` For projects using SQL models (SQLModel/SQLite/PostgreSQL), create tables first: ```bash uv run vibetuner db create-schema # Required once before first run just dev ``` This starts: - Database (MongoDB or SQL, if configured) - Redis (if background jobs enabled) - FastAPI application with hot reload - Frontend asset compilation Visit `http://localhost:8000` - your app is running! ### Project Structure ```text my-app/ ├── src/app/ # Your code (edit freely) │ ├── frontend/routes/ # Your HTTP routes │ ├── models/ # Your database models │ └── services/ # Your business logic ├── templates/ # Jinja2 templates ├── assets/ # Static files └── Dockerfile # Production deployment ``` The `vibetuner` framework (auth, database, core services) is installed as a package dependency. ### Justfile Commands All project management tasks use `just` (command runner). Run `just` to see all available commands. **Development:** ```bash just dev # Docker development with hot reload (recommended) just local-dev PORT=8000 # Local development without Docker just worker-dev # Background worker (if background jobs enabled) ``` **Dependencies:** ```bash just install-deps # Install from lockfiles just update-repo-deps # Update root scaffolding dependencies uv add package-name # Add Python package bun add package-name # Add JavaScript package ``` **Code Formatting:** ```bash just format # Format ALL code (Python, Jinja, TOML, YAML) just format-py # Format Python with ruff just format-jinja # Format Jinja templates with djlint just format-toml # Format TOML files with taplo just format-yaml # Format YAML files with dprint ``` **IMPORTANT**: Always run `ruff format .` or `just format-py` after Python changes. **Code Linting:** ```bash just lint # Lint ALL code just lint-py # Lint Python with ruff just lint-jinja # Lint Jinja templates with djlint just lint-md # Lint markdown files just lint-toml # Lint TOML files with taplo just lint-yaml # Lint YAML files with dprint just type-check # Type check Python with ty ``` **Localization:** ```bash just i18n # Full workflow: extract, update, compile just extract-translations # Extract translatable strings just update-locale-files # Update existing .po files just compile-locales # Compile .po to .mo files just new-locale LANG # Create new language (e.g., just new-locale es) ``` **CI/CD & Deployment:** ```bash just build-dev # Build development Docker image just test-build-prod # Test production build locally just build-prod # Build production image just release # Build and release production image just deploy-latest HOST # Deploy to remote host ``` **Scaffolding:** ```bash just update-scaffolding # Update project to latest vibetuner template ``` ## Core Documentation ### Development Guide #### Development Environment Vibetuner supports two development modes: **Docker Development (Recommended)** Run everything in containers with hot reload: ```bash just dev ``` This starts: - Database (MongoDB or SQL, if configured) - Redis (if background jobs enabled) - FastAPI application with auto-reload - Frontend asset compilation with watch mode Changes to Python code, templates, and assets automatically reload. **Local Development** Run services locally without Docker: ```bash # Terminal 1: Frontend assets bun dev # Terminal 2: Backend server just local-dev ``` A database (MongoDB or SQL) is required if using database features. Redis is only required if background jobs are enabled. **HTTPS via Tailscale Expose** Use `just dev-exposed` to run the dev server with HTTPS via Tailscale Serve. This wraps `just local-all` with automatic tailscale serve setup/teardown. #### Adding New Routes Create a new file in `src/app/frontend/routes/`: ```python # src/app/frontend/routes/blog.py from fastapi import APIRouter router = APIRouter(prefix="/blog", tags=["blog"]) @router.get("/") async def list_posts(): return {"posts": []} ``` Register in `src/app/frontend/__init__.py`: ```python from app.frontend.routes import blog app.include_router(blog.router) ``` **`@render` decorator** — for simple routes, eliminate `render_template()` boilerplate: ```python from vibetuner import render @router.get("/dashboard") @render("dashboard.html.jinja") async def dashboard(request: Request, user=Depends(get_current_user)) -> dict: return {"user": user} ``` Returns `Response` objects unchanged (escape hatch for redirects, conditional logic). #### Adding Database Models Create models in `src/app/models/`. The approach depends on your database choice: **MongoDB (Beanie ODM)** ```python # src/app/models/post.py from beanie import Document from pydantic import Field class Post(Document): title: str content: str published: bool = Field(default=False) class Settings: name = "posts" ``` **SQL (SQLModel)** ```python # src/app/models/post.py from sqlmodel import SQLModel, Field class Post(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) title: str content: str published: bool = Field(default=False) ``` For SQL databases, create tables with: `vibetuner db create-schema` Register models in `src/app/models/__init__.py`: ```python from app.models.post import Post __all__ = ["Post"] ``` The model will be automatically registered with the database on startup. #### Creating Templates Add templates in `templates/`: ```html {% extends "base/skeleton.html.jinja" %} {% block content %}
Price: {{ product.price | money }}
``` **Important:** If a filter returns HTML, wrap the result with `markupsafe.Markup` to prevent Jinja2 auto-escaping. Always escape user input inside the markup: ```python from markupsafe import Markup, escape def tag_badge(value: str) -> Markup: """Render a badge — escape user input, wrap result in Markup.""" return Markup('{}').format(escape(value)) ``` #### Adding Background Jobs If you enabled background jobs, create tasks in `src/app/tasks/`: ```python # src/app/tasks/emails.py from vibetuner.tasks.worker import get_worker from vibetuner.models import UserModel from vibetuner.services.email import EmailService worker = get_worker() @worker.task() async def send_welcome_email(user_id: str): user = await UserModel.get(user_id) if user: email_service = EmailService() await email_service.send_email( to_address=user.email, subject="Welcome!", html_body="