Skip to content

Commit d1bf745

Browse files
committed
Initial commit: Production-ready MVP template with FastAPI and Next.js
0 parents  commit d1bf745

36 files changed

+1219
-0
lines changed

.github/workflows/ci.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
lint-and-test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: '3.12'
21+
22+
- name: Install backend dependencies
23+
run: |
24+
pip install uv
25+
uv pip install --system --from-requirements backend/pyproject.toml
26+
27+
- name: Run backend linters and tests
28+
run: |
29+
cd backend
30+
pytest
31+
32+
- name: Set up Node.js and pnpm
33+
uses: actions/setup-node@v4
34+
with:
35+
node-version: 20
36+
cache: 'pnpm'
37+
cache-dependency-path: frontend/pnpm-lock.yaml
38+
39+
- name: Install frontend dependencies
40+
run: |
41+
cd frontend
42+
pnpm install --frozen-lockfile
43+
44+
- name: Run frontend linters and tests
45+
run: |
46+
cd frontend
47+
pnpm lint

.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Environment Variables
2+
# Never commit .env files with secrets
3+
.env
4+
5+
# Python
6+
__pycache__/
7+
*.py[oc]
8+
*.py[cod]
9+
.venv/
10+
venv/
11+
env/
12+
13+
# Python Build & Test Artifacts
14+
build/
15+
dist/
16+
*.egg-info/
17+
.pytest_cache/
18+
.mypy_cache/
19+
.ruff_cache/
20+
htmlcov/
21+
22+
# Node.js
23+
# Ignore all node_modules directories
24+
**/node_modules
25+
26+
# Frontend Build Artifacts
27+
# Next.js build output
28+
**/.next/
29+
30+
# IDE & Editor Files
31+
.idea/
32+
.vscode/
33+
*.swp
34+
35+
# OS Files
36+
.DS_Store
37+
Thumbs.db

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# MVP Template: FastAPI, React (Next.js), and Postgres
2+
3+
This repository is a production-ready template for building a Minimum Viable Product (MVP). It provides a solid architectural foundation for a modern web application, allowing you to focus on building your product's unique features from day one.
4+
5+
## Getting Started
6+
7+
### Prerequisites
8+
9+
1. **Docker & Docker Compose:** [Install Docker](https://docs.docker.com/get-docker/).
10+
2. **Node.js & pnpm:** [Install pnpm](https://pnpm.io/installation).
11+
3. **Python:** The backend uses Python 3.12+.
12+
13+
### 1. Generate Frontend Lockfile (Critical First Step)
14+
15+
Before running the Docker build, you must generate the `pnpm-lock.yaml` file. This file is essential for ensuring reproducible builds inside and outside of Docker.
16+
17+
Navigate to the frontend directory and run the install command:
18+
19+
```sh
20+
cd frontend
21+
pnpm install
22+
cd ..
23+
```
24+
25+
This will create the `pnpm-lock.yaml` file in the `frontend` directory. Commit this file to your repository.
26+
27+
### 2. Set Up Environment Variables
28+
29+
Copy the example environment file to create your local configuration:
30+
31+
```sh
32+
cp example.env .env
33+
```
34+
35+
### 3. Run the Application
36+
37+
With the lockfile generated and the `.env` file in place, you can now build and run the entire stack:
38+
39+
```sh
40+
docker-compose up --build
41+
```
42+
43+
- **Backend API** will be available at `http://localhost:8000`
44+
- **Frontend App** will be available at `http://localhost:3000`

backend/Dockerfile

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# --- Builder Stage: Compile dependencies --- #
2+
FROM python:3.12-slim AS builder
3+
4+
# Install uv, our package manager
5+
RUN pip install --no-cache-dir uv
6+
7+
WORKDIR /app
8+
9+
# Copy the dependency definition file
10+
COPY pyproject.toml ./
11+
12+
# Compile pyproject.toml into a requirements.txt file
13+
# This creates a locked set of dependencies for production
14+
RUN uv pip compile pyproject.toml -o requirements.txt
15+
16+
17+
# --- Final Stage: Build the application image --- #
18+
FROM python:3.12-slim
19+
20+
# Set environment variables for Python
21+
ENV PYTHONDONTWRITEBYTECODE=1
22+
ENV PYTHONUNBUFFERED=1
23+
24+
WORKDIR /app
25+
26+
# Install uv again in the final stage
27+
RUN pip install --no-cache-dir uv
28+
29+
# Copy the compiled requirements.txt from the builder stage
30+
COPY --from=builder /app/requirements.txt .
31+
32+
# Install the production dependencies using the locked requirements file
33+
# Using sync ensures the environment is exactly as specified
34+
RUN uv pip sync --system requirements.txt
35+
36+
# Copy the application code into the container
37+
COPY ./app ./app
38+
39+
# Create a non-root user for security
40+
RUN groupadd -r appgroup && useradd -r -g appgroup -d /home/appuser -m -s /bin/bash appuser
41+
RUN chown -R appuser:appgroup /app
42+
USER appuser
43+
44+
# Expose the application port
45+
EXPOSE 8000
46+
47+
# Command to run the FastAPI application
48+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

backend/alembic.ini

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# path to migration scripts
5+
script_location = %(here)s/alembic
6+
7+
# template used to generate migration file names
8+
# file_template = %%(rev)s_%%(slug)s
9+
10+
# sys.path path, will be prepended to sys.path if present.
11+
prepend_sys_path = .
12+
13+
# database URL
14+
s sqlalchemy.url = driver://user:pass@localhost/dbname
15+
16+
[loggers]
17+
keys = root,sqlalchemy,alembic
18+
19+
[handlers]
20+
keys = console
21+
22+
[formatters]
23+
keys = generic
24+
25+
[logger_root]
26+
level = WARNING
27+
handlers = console
28+
qualname =
29+
30+
[logger_sqlalchemy]
31+
level = WARNING
32+
handlers =
33+
qualname = sqlalchemy.engine
34+
35+
[logger_alembic]
36+
level = INFO
37+
handlers =
38+
qualname = alembic
39+
40+
[handler_console]
41+
class = StreamHandler
42+
args = (sys.stderr,)
43+
level = NOTSET
44+
formatter = generic
45+
46+
[formatter_generic]
47+
format = %(levelname)-5.5s [%(name)s] %(message)s
48+
datefmt = %H:%M:%S

backend/alembic/env.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import os
2+
import sys
3+
from logging.config import fileConfig
4+
5+
from sqlalchemy import engine_from_config
6+
from sqlalchemy import pool
7+
8+
from alembic import context
9+
10+
# Add the project root to the Python path
11+
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
12+
sys.path.insert(0, PROJECT_ROOT)
13+
14+
# Import your models here so Alembic can see them
15+
# from app.models import YourModel # Example
16+
from sqlmodel import SQLModel
17+
18+
# This is the Alembic Config object, which provides
19+
# access to the values within the .ini file in use.
20+
config = context.config
21+
22+
# Interpret the config file for Python logging.
23+
if config.config_file_name is not None:
24+
fileConfig(config.config_file_name)
25+
26+
# Set the database URL from an environment variable
27+
# This assumes you have a DATABASE_URL environment variable set.
28+
# You can also set it directly in alembic.ini
29+
config.set_main_option('sqlalchemy.url', os.environ.get('DATABASE_URL'))
30+
31+
# Your model's MetaData object for 'autogenerate' support
32+
target_metadata = SQLModel.metadata
33+
34+
def run_migrations_offline() -> None:
35+
"""Run migrations in 'offline' mode."""
36+
url = config.get_main_option("sqlalchemy.url")
37+
context.configure(
38+
url=url,
39+
target_metadata=target_metadata,
40+
literal_binds=True,
41+
dialect_opts={"paramstyle": "named"},
42+
)
43+
44+
with context.begin_transaction():
45+
context.run_migrations()
46+
47+
48+
def run_migrations_online() -> None:
49+
"""Run migrations in 'online' mode."""
50+
connectable = engine_from_config(
51+
config.get_section(config.config_ini_section, {}),
52+
prefix="sqlalchemy.",
53+
poolclass=pool.NullPool,
54+
)
55+
56+
with connectable.connect() as connection:
57+
context.configure(
58+
connection=connection, target_metadata=target_metadata
59+
)
60+
61+
with context.begin_transaction():
62+
context.run_migrations()
63+
64+
65+
if context.is_offline_mode():
66+
run_migrations_offline()
67+
else:
68+
run_migrations_online()

backend/alembic/versions/.gitkeep

Whitespace-only changes.

backend/app/__init__.py

Whitespace-only changes.

backend/app/main.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from fastapi import FastAPI
2+
3+
app = FastAPI(title="MVP Template Backend")
4+
5+
@app.get("/api/v1/health", tags=["Health"])
6+
def health_check():
7+
"""Check if the API is running."""
8+
return {"status": "ok"}

backend/pyproject.toml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
[project]
2+
name = "backend"
3+
version = "0.1.0"
4+
description = "A FastAPI backend for the MVP template."
5+
readme = "README.md"
6+
requires-python = ">=3.12"
7+
dependencies = [
8+
"fastapi>=0.115.12",
9+
"authlib>=1.6.0",
10+
"cryptography>=45.0.4",
11+
"email-validator>=2.2.0",
12+
"httpx>=0.28.1",
13+
"itsdangerous>=2.2.0",
14+
"passlib[bcrypt]>=1.7.4",
15+
"psycopg2-binary>=2.9.10",
16+
"pydantic-settings>=2.9.1",
17+
"python-dotenv>=1.1.0",
18+
"python-jose[cryptography]>=3.5.0",
19+
"python-multipart>=0.0.20",
20+
"sqlalchemy>=2.0.41",
21+
"sqlmodel>=0.0.24",
22+
"uvicorn[standard]>=0.34.3",
23+
"alembic>=1.16.2",
24+
"jinja2>=3.1.6"
25+
]
26+
27+
[project.optional-dependencies]
28+
dev = [
29+
"pytest>=7.0.0",
30+
"ruff>=0.1.0",
31+
"black>=23.0.0",
32+
"mypy>=1.0.0",
33+
"httpx>=0.28.1",
34+
"debugpy>=1.8.1",
35+
"pytest-cov>=6.2.1",
36+
"pytest-env>=1.0.0",
37+
"pytest-asyncio>=0.23.0"
38+
]
39+
40+
[tool.setuptools.packages.find]
41+
include = ["app*"]
42+
exclude = ["alembic*"]
43+
44+
[tool.pyright]
45+
include = ["app"]
46+
exclude = ["**/__pycache__", ".venv", "**/.*_cache", "alembic/versions"]
47+
48+
[tool.coverage.run]
49+
source = ["app"]
50+
51+
[tool.coverage.report]
52+
fail_under = 80
53+
show_missing = true

0 commit comments

Comments
 (0)