Development Guide¶
Daily development workflow for Vibetuner projects.
Development Environment¶
Vibetuner supports two development modes:
Docker Development (Recommended)¶
Run everything in containers with hot reload:
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:
A database (MongoDB or SQL) is required if using database features. Redis is only required if background jobs are enabled.
Justfile Commands Reference¶
All project management tasks use just (command runner). Run just without arguments to see all
available commands.
Development¶
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¶
just install-deps # Install from lockfiles
just update-repo-deps # Update root scaffolding dependencies
just update-and-commit-repo-deps # Update deps and commit changes
uv add package-name # Add Python package
bun add package-name # Add JavaScript package
Code Formatting¶
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¶
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 (i18n)¶
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)
just dump-untranslated DIR # Export untranslated strings
CI/CD & Deployment¶
just build-dev # Build development Docker image
just test-build-prod # Test production build locally
just build-prod # Build production image (requires clean tagged commit)
just release # Build and release production image
just deploy-latest HOST # Deploy to remote host
Scaffolding Updates¶
Common Tasks¶
Adding New Routes¶
Create a new file in src/app/frontend/routes/:
# 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:
Adding Database Models¶
Create models in src/app/models/. The approach depends on your database choice:
MongoDB (Beanie ODM)¶
# 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)¶
# 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:
Creating Templates¶
Add templates in templates/:
<!-- templates/blog/list.html.jinja -->
{% extends "base/skeleton.html.jinja" %}
{% block content %}
<div class="container mx-auto">
<h1 class="text-3xl font-bold">Blog Posts</h1>
<div class="grid gap-4">
{% for post in posts %}
<article class="card">
<h2>{{ post.title }}</h2>
<div>{{ post.content }}</div>
</article>
{% endfor %}
</div>
</div>
{% endblock %}
Adding Custom Template Filters¶
Create custom Jinja2 filters in src/app/frontend/templates.py:
# src/app/frontend/templates.py
from vibetuner.frontend.templates import register_filter
@register_filter()
def uppercase(value):
"""Convert value to uppercase"""
return str(value).upper()
@register_filter("money")
def format_money(value):
"""Format value as USD currency"""
try:
return f"${float(value):,.2f}"
except (ValueError, TypeError):
return str(value)
Use in templates:
The @register_filter() decorator automatically registers filters with the Jinja
environment. If no name is provided, the function name becomes the filter name.
Adding Background Jobs¶
If you enabled background jobs, create tasks in src/app/tasks/:
# src/app/tasks/emails.py
from vibetuner.tasks.worker import worker
from vibetuner.models import UserModel
from vibetuner.services.email import send_email
@worker.task()
async def send_welcome_email(user_id: str):
user = await UserModel.get(user_id)
if user:
await send_email(
to_email=user.email,
subject="Welcome!",
html_content="<h1>Welcome!</h1>"
)
return {"status": "sent"}
Register tasks in src/app/tasks/__init__.py:
Queue jobs from your routes:
from app.tasks.emails import send_welcome_email
@router.post("/signup")
async def signup(email: str):
# Create user
user = await create_user(email)
# Queue background task
task = await send_welcome_email.enqueue(str(user.id))
return {"message": "Welcome email queued", "task_id": task.id}
Styling with Tailwind¶
Vibetuner uses Tailwind CSS + DaisyUI. Edit assets/config.css for custom styles:
/* assets/config.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
.btn-custom {
@apply btn btn-primary rounded-full;
}
The build process automatically compiles to assets/statics/css/bundle.css.
Working with HTMX¶
Vibetuner uses HTMX for interactive features without JavaScript:
<!-- Load more posts -->
<button hx-get="/blog?page=2"
hx-target="#posts"
hx-swap="beforeend"
class="btn btn-primary">Load More</button>
<div id="posts">
<!-- Posts will be appended here -->
</div>
Server endpoint:
@router.get("/blog")
async def list_posts(page: int = 1):
posts = await Post.find().skip((page - 1) * 10).limit(10).to_list()
return templates.TemplateResponse("blog/posts.html.jinja", {
"posts": posts
})
Internationalization¶
Extracting Translations¶
After adding translatable strings:
This scans your code and templates for {% trans %} blocks and gettext() calls.
Adding New Languages¶
Updating Translations¶
Edit .po files in translations/:
Compile translations:
Using in Templates¶
Using in Python¶
Debugging¶
View Logs¶
Access Database¶
# MongoDB
docker compose exec mongodb mongosh
# PostgreSQL
docker compose exec postgres psql -U postgres
Interactive Shell¶
Testing¶
Run Tests¶
Test Coverage¶
Integration Tests¶
Integration tests should use a real database (not mocks):
import pytest
from app.models import Post
@pytest.mark.asyncio
async def test_create_post():
post = Post(title="Test", content="Content")
await post.insert() # MongoDB with Beanie
found = await Post.get(post.id)
assert found.title == "Test"
Code Quality¶
Format Code¶
Runs:
ruff formatfor Python
Check Code¶
Runs:
ruff checkfor Python- Type checking
- Template validation
Environment Configuration¶
Development Settings¶
Copy .env.local to .env:
Edit as needed:
# .env
# MongoDB
MONGODB_URL=mongodb://localhost:27017/myapp
# Or SQL database (PostgreSQL, MySQL, MariaDB, SQLite)
DATABASE_URL=postgresql+asyncpg://user:pass@localhost/myapp
# DATABASE_URL=sqlite+aiosqlite:///./data.db
SECRET_KEY=your-secret-key-here
DEBUG=true
# OAuth (optional)
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
Production Settings¶
Use environment variables or .env file in production:
# MongoDB
MONGODB_URL=mongodb://prod-server:27017/myapp
# Or SQL database
DATABASE_URL=postgresql+asyncpg://user:pass@prod-server/myapp
SECRET_KEY=very-secret-key
DEBUG=false
Keeping the Scaffold Up to Date¶
When new versions of the template ship, update your project using either:
vibetuner scaffold update– works from anywhere; replays Copier with your saved answers.just update-scaffolding– runs inside the generated project and wrapscopier updateplus dependency sync.
Both commands modify tracked files, so commit or stash your work beforehand and review the changes afterward. See the Scaffolding Reference for a deeper walkthrough.
Dependency Management¶
Add Python Package¶
Add JavaScript Package¶
Sync All Dependencies¶
Syncs both Python and JavaScript dependencies.
Next Steps¶
- Authentication - Set up OAuth providers
- Deployment - Deploy to production
- Architecture - Understand the system design