Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 97 additions & 11 deletions .vitepress/theme/components/HeroSection.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
<script setup>
import { Copy, FileText, Terminal } from "lucide-vue-next"
import { computed, onMounted, onUnmounted, ref } from "vue"
import { Terminal, FileText, Copy } from "lucide-vue-next"

// ── Raw Python snippet imports ──────────────────────────────────────────────
import applicationRaw from "../snippets/application.py?raw"
import artisanRaw from "../snippets/artisan?raw"
import databaseAppRaw from "../snippets/database_app.py?raw"
import dbMigrationsRaw from "../snippets/database_migrations.py?raw"
import dbModelsRaw from "../snippets/database_models.py?raw"
import fastapiAppRaw from "../snippets/fastapi-app.py?raw"
import fastapiRaw from "../snippets/fastapi.py?raw"
import fastapiControllerRaw from "../snippets/fastapi_users_controller.py?raw"
import fastapiRoutesRaw from "../snippets/fastapi_routes.py?raw"
import fastapiControllerRaw from "../snippets/fastapi_users_controller.py?raw"
import logLoggerRaw from "../snippets/logging_logger.py?raw"

// ── Word rotator ────────────────────────────────────────────────────────────
Expand All @@ -32,16 +34,15 @@
onUnmounted(() => { if (interval) clearInterval(interval) })

// ── Tab / file structure ────────────────────────────────────────────────────
const categories = ["Application", "FastAPI", "Database", "Migrations", "Logging"]
const categories = ["Application", "FastAPI", "Database", "Logging"]
const activeCategory = ref("Application")
const activeFileIndex = ref(0)
const isTransitioning = ref(false)

const tabData = {
Application: { files: ["application.py", "artisan"], raw: [applicationRaw, artisanRaw] },
FastAPI: { files: ["fastapi.py", "users_controllers.py", "api.py"], raw: [fastapiRaw, fastapiControllerRaw, fastapiRoutesRaw] },
Database: { files: ["models.py"], raw: [dbModelsRaw] },
Migrations: { files: ["2026_04_26_110113_create_users.py"], raw: [dbMigrationsRaw] },
FastAPI: { files: ["api.py", "application", "api.py", "users_controllers.py"], raw: [fastapiRaw, fastapiAppRaw, fastapiRoutesRaw, fastapiControllerRaw] },
Database: { files: ["application.py", "2026_04_26_110113_create_users.py", "models.py"], raw: [databaseAppRaw, dbMigrationsRaw, dbModelsRaw] },
Logging: { files: ["logging.py"], raw: [logLoggerRaw] },
}

Expand All @@ -50,6 +51,25 @@
const highlighted = ref({})
const highlighterReady = ref(false)

// Strip diff markers from source and return { code, lineTypes }
// Supported markers: # [+] # [-] # [!hl]
function extractHighlights(raw) {
const lines = raw.split("\n")
const lineTypes = {} // index -> 'add' | 'remove' | 'highlight'
const cleaned = lines.map((line, i) => {
if (line.includes("# [+]")) {
lineTypes[i] = "add"
return line.replace(/\s*#\s*\[\+\]/, "")
}
if (line.includes("# [-]")) {
lineTypes[i] = "remove"
return line.replace(/\s*#\s*\[-\]/, "")
}
return line
})
return { code: cleaned.join("\n"), lineTypes }
}

onMounted(async () => {
try {
const { createHighlighter } = await import("shiki")
Expand All @@ -60,12 +80,20 @@

const result = {}
for (const [cat, data] of Object.entries(tabData)) {
result[cat] = data.raw.map(code => {
result[cat] = data.raw.map(raw => {
const { code, lineTypes } = extractHighlights(raw)
const html = hl.codeToHtml(code, { lang: "python", theme: "github-dark" })
// Strip outer <pre ...><code> wrapper — we render inside our own pre/code
return html
const inner = html
.replace(/^<pre[^>]*><code[^>]*>/, "")
.replace(/<\/code><\/pre>$/, "")
// Wrap each line in a span and apply diff/highlight classes
return inner.split("\n").map((line, i) => {
const type = lineTypes[i]
const cls = type ? ` ${type}` : ""
const prefix = type === "add" ? "+" : type === "remove" ? "-" : " "
return `<span class="line${cls}"><span class="diff-sign">${prefix}</span>${line}</span>`
}).join("\n")
})
}
highlighted.value = result
Expand Down Expand Up @@ -111,6 +139,18 @@
}, 150)
}

// ── Copy to clipboard ───────────────────────────────────────────────────────
const copied = ref(false)

function copyCode() {
const raw = currentTabData.value.raw[activeFileIndex.value]
const { code } = extractHighlights(raw)
navigator.clipboard.writeText(code).then(() => {
copied.value = true
setTimeout(() => { copied.value = false }, 2000)
})
}

// ── Synchronized horizontal scroll (file tabs ↔ code body) ─────────────────
const fileTabsRef = ref(null)
const codeBodyRef = ref(null)
Expand Down Expand Up @@ -164,7 +204,7 @@
<div class="flex flex-col sm:flex-row gap-4">
<a href="/docs/getting-started" class="bg-brand-teal text-white dark:text-black px-8 py-4 rounded font-label-md font-bold flex items-center justify-center gap-2 transition-all hover:brightness-110 shadow-lg shadow-brand-teal/20 active:scale-[0.98]">
Initialize Project
<Terminal :size="18" />
<Terminal :size="18"/>
</a>
</div>

Expand Down Expand Up @@ -223,12 +263,15 @@
:class="activeFileIndex === idx ? 'bg-brand-teal/10 border-b-2 border-brand-teal' : 'opacity-50 hover:opacity-75'"
@click="switchFile(idx)"
>
<FileText :size="12" :class="activeFileIndex === idx ? 'text-brand-teal' : 'text-outline-variant'" />
<FileText :size="12" :class="activeFileIndex === idx ? 'text-brand-teal' : 'text-outline-variant'"/>
<span class="text-xs font-mono tracking-tight" :class="activeFileIndex === idx ? 'text-white' : 'text-outline-variant'">{{ file }}</span>
</button>
</div>
</div>
<Copy :size="14" class="text-outline-variant hover:text-brand-teal cursor-pointer transition-colors" />
<button @click="copyCode" class="shrink-0 transition-colors" :title="copied ? 'Copied!' : 'Copy code'">
<Copy v-if="!copied" :size="14" class="text-outline-variant hover:text-brand-teal"/>
<span v-else class="text-[11px] text-brand-teal font-mono">copied!</span>
</button>
</div>

<!-- Code body -->
Expand All @@ -251,4 +294,47 @@
pre, code {
background: transparent !important;
}

/* Diff / highlight line decorations */
:deep(.line) {
display: inline-block;
width: 100%;
}

:deep(.diff-sign) {
user-select: none;
margin-right: 6px;
opacity: 0.6;
}

:deep(.line.highlight) {
background-color: rgba(5, 150, 105, 0.15);
border-left: 2px solid #059669;
padding-left: 6px;
margin-left: -8px;
}

:deep(.line.add) {
background-color: rgba(5, 150, 105, 0.15);
border-left: 2px solid #059669;
padding-left: 6px;
margin-left: -8px;
}

:deep(.line.add .diff-sign) {
color: #059669;
opacity: 1;
}

:deep(.line.remove) {
background-color: rgba(239, 68, 68, 0.1);
border-left: 2px solid #ef4444;
padding-left: 6px;
margin-left: -8px;
}

:deep(.line.remove .diff-sign) {
color: #ef4444;
opacity: 1;
}
</style>
7 changes: 4 additions & 3 deletions .vitepress/theme/snippets/application.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# bootstrap/application.py
from pathlib import Path
from fastapi_startkit import Application
from fastapi_startkit.fastapi import FastAPIProvider
from fastapi_startkit.logging import LoggerProvider

# Initialize Application with logging and fastapi
# A lightweight background worker with zero web overhead,
# No FastAPI, no database ORMs, no frontend assets—just pure, lean Python.
# Automatically loads .env files and exposes a structured CLI engine.
app: Application = Application(
base_path=Path(__file__).parent.parent,
providers=[
# Configures uniform logs across terminal, files, syslog, or Slack
LoggerProvider,
FastAPIProvider
]
)
5 changes: 5 additions & 0 deletions .vitepress/theme/snippets/artisan
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@ import sys
from bootstrap.application import app

if __name__ == "__main__":
# Your console scripts now can access a centralized, single source of truth
# for environment variables and structured logging.
status = app.handle_command()
sys.exit(status if isinstance(status, int) else 0)

# Save as `artisan` -> Run via `uv run python artisan`
# Build background workers, cron jobs, or database utilities uniformly.
17 changes: 17 additions & 0 deletions .vitepress/theme/snippets/database_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# bootstrap/application.py
from fastapi_startkit import Application
from fastapi_startkit.fastapi import FastAPIProvider
from fastapi_startkit.logging import LoggerProvider
from fastapi_startkit.masoniteorm import DatabaseProvider # [+]
from pathlib import Path
# FastAPI Startkit ships with a fully async MasoniteORM provider.
# Use it for an elegant, Active Record workflow—or completely swap it
# for SQLAlchemy, SQLModel, or any async database stack you prefer.
app: Application = Application(
base_path=Path(__file__).parent.parent,
providers=[
LoggerProvider,
FastAPIProvider,
DatabaseProvider # [+]
]
)
6 changes: 5 additions & 1 deletion .vitepress/theme/snippets/database_migrations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from fastapi_startkit.masoniteorm import Migration


# Explicit schema design: MasoniteORM requires you to write your
# migrations manually rather than auto-generating them from
# code models (like Django or Alembic do).
class CreateUsersTable(Migration):
async def up(self):
async with await self.schema.create("users") as table:
Expand All @@ -12,3 +14,5 @@ async def up(self):

async def down(self):
await self.schema.drop("users")

# Run `uv run python artisan db:migrate` to apply this migration.
7 changes: 7 additions & 0 deletions .vitepress/theme/snippets/database_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ class User(Model):
id: int
name: str
email: str
password: str

# Note: Showing a HasMany relationship example (Post schema not pictured).
posts: list["Post"] = HasMany("Post")

class Post(Model):
id: int
user_id: int
title: str
content: str

# Then you can use the models in your application:
# from app.models import User, Post
# user = await User.find(1)
# user_with_posts = await User.with_('posts').find(1)
19 changes: 19 additions & 0 deletions .vitepress/theme/snippets/fastapi-app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# bootstrap/application.py
from fastapi_startkit import Application
from fastapi_startkit.fastapi import FastAPIProvider # [+]
from fastapi_startkit.logging import LoggerProvider
from pathlib import Path

app: Application = Application(
base_path=Path(__file__).parent.parent,
providers=[
LoggerProvider,
# Register FastAPIProvider to bind the fastapi to the application,
FastAPIProvider # [+]
]
)

# Bind your routes to fastapi.
from routes.api import router
app.include_router(router)
# `uv run python artisan serve` to serve the fastapi application
6 changes: 2 additions & 4 deletions .vitepress/theme/snippets/fastapi.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
# routes/api.py

# Define your FastAPI routes as it is using fastapi's APIRouter
from fastapi import APIRouter

router = APIRouter()

@router.get("/")
async def index():
return {"message": "Hello, FastAPI!"}

@router.post("/items")
async def create_item(item: ItemSchema):
return item
1 change: 1 addition & 0 deletions .vitepress/theme/snippets/fastapi_routes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# routes/web.py
from fastapi_startkit.fastapi import Router

# Or, switch to an opinionated routing architecture for better organization
router = Router()

from app.http.controllers import users_controller
Expand Down
1 change: 1 addition & 0 deletions .vitepress/theme/snippets/fastapi_users_controller.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# app/http/controllers/users_controller.py
# NOTE: Database provider needs to register to run this code
async def index(request: Request):
return await User.all()

Expand Down
5 changes: 4 additions & 1 deletion .vitepress/theme/snippets/logging_logger.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from fastapi_startkit.logging import Logger

# Log messages with different levels
# Once LoggerProvider is registered in bootstrap/application.py,
# the global Logger is accessible anywhere in your codebase.
# No local instantiation or repetitive configuration is required.

Logger.debug("This is a debug message")
Logger.info("Application is starting...")
Logger.notice("A formal notice")
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jsonLd:
"name": "Fastapi Startkit Team"
---

Fastapi Startkit is a modular, Laravel-inspired framework for building robust FastAPI applications with minimal boilerplate. That said, **it doesn't enforce you to use FastAPI at all** — you can build completely CLI-only applications or background task workers and still get access to the full suite of infrastructure components such as logging, database, configuration, and dependency injection.
Fastapi Startkit is a modular, provider-driven, Laravel-inspired framework for building robust FastAPI applications with minimal boilerplate. That said, **it doesn't enforce you to use FastAPI at all** — You can build entirely headless CLI utilities, cron scripts, or background task workers and still get access to the full suite of infrastructure components such as logging, database, configuration, and dependency injection.

## Prerequisites

Expand Down