Add Dockerized FastNext stack template with backend and frontend

Implemented a full-stack template combining Next.js (frontend), FastAPI (backend), and PostgreSQL. Included Docker configurations for development and production, environment file templates, Makefile commands, and initial setup for database migrations and builds. The stack is production-ready and supports hot-reloading for local development.
This commit is contained in:
2025-02-27 13:45:03 +01:00
parent 4470ca81cb
commit b76a45d0ce
43 changed files with 1169 additions and 172 deletions

2
backend/.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
.venv
*.iml

6
backend/=1.14.1 Normal file
View File

@@ -0,0 +1,6 @@
Requirement already satisfied: alembic in ./.venv/lib/python3.12/site-packages (1.14.1)
Requirement already satisfied: SQLAlchemy>=1.3.0 in ./.venv/lib/python3.12/site-packages (from alembic) (2.0.38)
Requirement already satisfied: Mako in ./.venv/lib/python3.12/site-packages (from alembic) (1.3.9)
Requirement already satisfied: typing-extensions>=4 in ./.venv/lib/python3.12/site-packages (from alembic) (4.12.2)
Requirement already satisfied: greenlet!=0.4.17 in ./.venv/lib/python3.12/site-packages (from SQLAlchemy>=1.3.0->alembic) (3.1.1)
Requirement already satisfied: MarkupSafe>=0.9.2 in ./.venv/lib/python3.12/site-packages (from Mako->alembic) (3.0.2)

34
backend/Dockerfile Normal file
View File

@@ -0,0 +1,34 @@
# Development stage
FROM python:3.12-slim AS development
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONPATH=/app
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc postgresql-client && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
# Production stage
FROM python:3.12-slim AS production
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONPATH=/app
RUN apt-get update && \
apt-get install -y --no-install-recommends postgresql-client && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

37
backend/alembic.ini Normal file
View File

@@ -0,0 +1,37 @@
[alembic]
script_location = alembic
sqlalchemy.url = postgresql://postgres:postgres@db:5432/eventspace
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

1
backend/alembic/README Normal file
View File

@@ -0,0 +1 @@
Generic single-database configuration.

78
backend/alembic/env.py Normal file
View File

@@ -0,0 +1,78 @@
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = None
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@@ -0,0 +1,26 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}

View File

@@ -0,0 +1,26 @@
"""Initial empty migration
Revision ID: 7396957cbe80
Revises:
Create Date: 2025-02-27 12:47:46.445313
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '7396957cbe80'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
pass
def downgrade() -> None:
pass

0
backend/app/__init__.py Normal file
View File

29
backend/app/config.py Normal file
View File

@@ -0,0 +1,29 @@
from pydantic_settings import BaseSettings
from typing import Optional, List
class Settings(BaseSettings):
PROJECT_NAME: Optional[str] = "EventSpace"
VERSION: Optional[str] = "1.0.0"
API_V1_STR: Optional[str] = "/api/v1"
# Database configuration
DATABASE_URL: Optional[str] = None
# JWT configuration
SECRET_KEY: Optional[str] = None
ALGORITHM: Optional[str] = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: Optional[int] = 30
# CORS configuration
BACKEND_CORS_ORIGINS: Optional[List[str]] = ["http://localhost:3000"] # Frontend URL
# Admin user
FIRST_SUPERUSER_EMAIL: Optional[str] = None
FIRST_SUPERUSER_PASSWORD: Optional[str] = None
class Config:
env_file = ".env"
settings = Settings()

View File

View File

@@ -0,0 +1,20 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@db:5432/eventspace")
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Dependency to get DB session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

36
backend/app/main.py Normal file
View File

@@ -0,0 +1,36 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
from app.config import settings
app = FastAPI(
title=settings.PROJECT_NAME,
version=settings.VERSION,
openapi_url=f"{settings.API_V1_STR}/openapi.json"
)
# Set up CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.BACKEND_CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/", response_class=HTMLResponse)
async def root():
return """
<html>
<head>
<title>EventSpace API</title>
</head>
<body>
<h1>Welcome to EventSpace API</h1>
<p>Explore the available endpoints and documentation:</p>
<a href="/docs">OpenAPI Documentation</a>
</body>
</html>
"""

8
backend/entrypoint.sh Normal file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
set -e
echo "Starting Backend"
# Apply database migrations
alembic upgrade head
# Execute the command passed to docker run
exec "$@"

44
backend/requirements.txt Normal file
View File

@@ -0,0 +1,44 @@
# Core FastAPI framework and dependencies
fastapi>=0.115.8
uvicorn>=0.34.0
pydantic>=2.10.6
pydantic-settings>=2.2.1
python-multipart>=0.0.19
# Database
sqlalchemy>=2.0.29
alembic>=1.14.1
psycopg2-binary>=2.9.9
asyncpg>=0.29.0
# Security and authentication
python-jose>=3.4.0
passlib>=1.7.4
bcrypt>=4.1.2
python-dotenv>=1.0.1
# API documentation
email-validator>=2.1.0.post1
ujson>=5.9.0
# CORS support
starlette>=0.40.0
starlette-csrf>=1.4.5
# Utilities
httpx>=0.27.0
tenacity>=8.2.3
pytz>=2024.1
pillow>=10.3.0
# Testing
pytest>=8.0.0
pytest-asyncio>=0.23.5
pytest-cov>=4.1.0
requests>=2.32.0
# Development tools
black>=24.3.0
isort>=5.13.2
flake8>=7.0.0
mypy>=1.8.0