#!/bin/bash set -euo pipefail usage() { echo "Usage: $0 [project-name]" echo "If no project name is given, you will be prompted." } valid_name() { [[ "$1" =~ ^[A-Za-z0-9_-]+$ ]] } if [[ "${1-}" == "-h" || "${1-}" == "--help" ]]; then usage; exit 0 fi PROJ="${1-}" if [[ -n "$PROJ" ]] && ! valid_name "$PROJ"; then echo "Invalid project name: $PROJ (use letters, numbers, hyphen, underscore)" PROJ="" fi while [[ -z "$PROJ" ]]; do read -r -p "Enter project name (letters, numbers, -, _ only): " PROJ if [[ -z "$PROJ" ]]; then continue fi if ! valid_name "$PROJ"; then echo "Invalid project name. Use letters, numbers, hyphen, underscore." PROJ="" fi done echo ">> Creating Tauri+React+FastAPI boilerplate in '$PROJ' ..." # --- Directory Structure --- mkdir -p "$PROJ/backend" mkdir -p "$PROJ/frontend" # --- Backend: Python FastAPI+SQLite --- cat > "$PROJ/backend/requirements.txt" < "$PROJ/backend/db.py" < "$PROJ/backend/main.py" < src-tauri/Cargo.toml <<'EOF' [package] name = "tauri-react-fastapi-app" version = "0.1.0" description = "Tauri + React + FastAPI boilerplate" authors = ["You"] edition = "2021" build = "build.rs" [build-dependencies] tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = [] } serde = { version = "1", features = ["derive"] } serde_json = "1" [features] default = [] EOF cat > src-tauri/build.rs <<'EOF' fn main() { tauri_build::build() } EOF cat > src-tauri/src/main.rs <<'EOF' #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { tauri::Builder::default() .run(tauri::generate_context!()) .expect("error while running tauri application"); } EOF # Minimal 32x32 RGBA icon required by Tauri bundle tooling python3 - <<'PY' import pathlib, struct, zlib path = pathlib.Path("src-tauri/icons/icon.png") path.parent.mkdir(parents=True, exist_ok=True) w = h = 32 color = (48, 160, 255, 255) # rgba blue-ish row = bytes([0]) + bytes(color) * w # filter byte + pixels data = row * h def chunk(t, d): return (struct.pack("!I", len(d)) + t + d + struct.pack("!I", zlib.crc32(t + d) & 0xFFFFFFFF)) ihdr = chunk(b'IHDR', struct.pack("!IIBBBBB", w, h, 8, 6, 0, 0, 0)) idat = chunk(b'IDAT', zlib.compress(data, 9)) iend = chunk(b'IEND', b'') png = b'\x89PNG\r\n\x1a\n' + ihdr + idat + iend path.write_bytes(png) PY cd src cat > App.jsx <<'EOF' import React, { useEffect, useState } from "react"; import axios from "axios"; const API_URL = "http://localhost:8000"; function App() { const [allLikes, setAllLikes] = useState([]); const [wsMsg, setWsMsg] = useState(""); const [error, setError] = useState(""); const [wsAttempts, setWsAttempts] = useState(0); useEffect(() => { axios.get(API_URL + "/likes/all") .then(res => setAllLikes(res.data)) .catch(err => setError(err.message)); let socket; let retryTimer; const connectWs = (attempt = 0) => { setWsAttempts(attempt + 1); socket = new WebSocket("ws://localhost:8000/ws"); socket.onopen = () => { setError(""); }; socket.onmessage = e => { const msg = JSON.parse(e.data); setWsMsg(JSON.stringify(msg)); }; socket.onerror = () => { setError("WebSocket connection failed"); }; socket.onclose = () => { if (attempt < 4) { retryTimer = setTimeout(() => connectWs(attempt + 1), 1000 * (attempt + 1)); } }; }; connectWs(); return () => { if (retryTimer) clearTimeout(retryTimer); if (socket) socket.close(); }; }, []); return (

React · FastAPI · SQLite · WebSocket · ESLint

{error &&

Error: {error}

}

Likes from backend:

    {allLikes.map(t => (
  • {t.title} – {t.artist}
  • ))}

WebSocket (attempt {wsAttempts}): {wsMsg}

); } export default App; EOF cat > main.jsx <<'EOF' import React from 'react' import { createRoot } from 'react-dom/client' import App from './App.jsx' createRoot(document.getElementById('root')).render( , ) EOF cat > index.css <<'EOF' body { font-family: sans-serif; margin: 2em; background: #202126; color: #fafafa; } h1 { color: #6bf; } EOF cd ../ cd ../.. cat > "$PROJ/.gitignore" <<'EOF' backend/.venv backend/__pycache__/ backend/appdata.db frontend/node_modules frontend/dist frontend/src-tauri/target frontend/src-tauri/gen frontend/src-tauri/Cargo.lock .DS_Store *.pyc EOF cat > "$PROJ/run.sh" <<'EOF' #!/bin/bash set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BACKEND="$ROOT/backend" FRONTEND="$ROOT/frontend" VENV="$BACKEND/.venv" cleanup() { [[ -n "${BACK_PID:-}" ]] && kill "$BACK_PID" 2>/dev/null || true } trap cleanup EXIT INT TERM echo ">> Backend: ensure venv and deps" python3 -m venv "$VENV" source "$VENV/bin/activate" pip install --upgrade pip pip install -r "$BACKEND/requirements.txt" echo ">> Starting backend (uvicorn)..." cd "$BACKEND" python -m uvicorn main:app --port 8000 --reload & BACK_PID=$! echo ">> Installing frontend deps (npm install)..." cd "$FRONTEND" npm install echo ">> Launching Tauri dev (opens a window)..." npm run tauri-dev EOF chmod +x "$PROJ/run.sh" cat > "$PROJ/README.md" <> Creating Python venv and installing backend requirements..." cd "$PROJ/backend" python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install -r requirements.txt deactivate echo ">> Installing frontend npm dependencies..." cd ../frontend npm install echo ">> All dependencies installed." cd ../.. echo ">> Boilerplate created at '$PROJ'. See $PROJ/README.md for next steps." echo ">> Launching Tauri app..." cd "$PROJ/backend" source .venv/bin/activate python -m uvicorn main:app --port 8000 --reload & BACK_PID=$! trap 'kill $BACK_PID 2>/dev/null || true' EXIT cd ../frontend npm run tauri-dev