feat: add fullstack TypeScript template with Docker support

- Created package.json for managing workspaces (frontend and backend)
- Added scripts for development, build, testing, and Docker operations
- Implemented kill-dev-processes.sh script to terminate development processes gracefully
This commit is contained in:
2025-05-29 08:03:49 +00:00
commit c40b069ab9
41 changed files with 36870 additions and 0 deletions

25
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,25 @@
FROM node:18
# Install git and other useful tools
RUN apt-get update && apt-get install -y \
git \
curl \
vim \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /workspace
# Install global packages
RUN npm install -g typescript ts-node nodemon
# Create user with same UID as host (for file permissions)
ARG USERNAME=node
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN groupmod --gid $USER_GID $USERNAME \
&& usermod --uid $USER_UID --gid $USER_GID $USERNAME \
&& chown -R $USER_UID:$USER_GID /home/$USERNAME
USER $USERNAME

View File

@@ -0,0 +1,42 @@
{
"name": "Fotodrucker Full Stack",
"build": {
"dockerfile": "Dockerfile"
},
"workspaceFolder": "/workspace",
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.vscode-typescript-next",
"esbenp.prettier-vscode",
"ms-vscode.vscode-json",
"bradlc.vscode-tailwindcss",
"ms-vscode.vscode-eslint",
"ms-vscode-remote.remote-containers"
],
"settings": {
"typescript.preferences.includePackageJsonAutoImports": "auto",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
},
"postCreateCommand": "cd backend && npm install && cd ../frontend && npm install",
"forwardPorts": [3000, 3001, 9229, 9230],
"portsAttributes": {
"3000": {
"label": "Frontend (React)",
"onAutoForward": "openBrowser"
},
"3001": {
"label": "Backend (Express)"
},
"9229": {
"label": "Backend Debug Port"
},
"9230": {
"label": "Frontend Debug Port"
}
}
}

View File

@@ -0,0 +1,16 @@
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ..:/workspace:cached
ports:
- "3000:3000"
- "3001:3001"
- "9229:9229"
- "9230:9230"
command: sleep infinity
working_dir: /workspace

61
.dockerignore Normal file
View File

@@ -0,0 +1,61 @@
# Dockerignore für optimierte Builds
# Verhindert, dass unnötige Dateien in den Build-Context kopiert werden
# Git
.git
.gitignore
# Documentation (README.md sollte im Image sein für Referenz)
docs/
# IDE & Editor
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Dependencies (werden separat installiert)
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
# Build outputs (werden im Container erstellt)
/backend/dist/
/frontend/build/
# Environment (production wird separat gesetzt)
.env*
# Testing & Coverage
coverage/
.nyc_output
**/*.test.*
**/*.spec.*
# Logs
logs/
*.log
# Development Scripts
scripts/
# CI/CD
.woodpecker.yml
# Temporary & Cache
tmp/
temp/
.cache/
.parcel-cache
# Database Files & Volumes
*.db
*.sqlite*
postgres_data/
redis_data/

83
.gitignore vendored Normal file
View File

@@ -0,0 +1,83 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build outputs
/backend/dist/
/frontend/build/
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# nyc test coverage
.nyc_output
# IDE / Editor
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Logs
logs
*.log
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# Docker volumes (not the config files!)
postgres_data/
redis_data/
pgadmin_data/
# Docker runtime
.docker/

15
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"recommendations": [
"ms-vscode.vscode-typescript-next",
"esbenp.prettier-vscode",
"ms-vscode.vscode-json",
"ms-vscode.vscode-eslint",
"bradlc.vscode-tailwindcss",
"ms-vscode-remote.remote-containers",
"ms-vscode.node-debug2",
"ms-vscode.js-debug",
"formulahendry.auto-rename-tag",
"christian-kohler.path-intellisense",
"ms-vscode.vscode-typescript-tslint-plugin"
]
}

7
.vscode/keybindings.json vendored Normal file
View File

@@ -0,0 +1,7 @@
[
{
"key": "ctrl+shift+q",
"command": "workbench.action.tasks.runTask",
"args": "🛑 Cleanup Development Processes"
}
]

72
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,72 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "🚀 Debug Backend",
"type": "node",
"request": "launch",
"runtimeExecutable": "npx",
"runtimeArgs": [
"ts-node-dev",
"--respawn",
"--transpile-only",
"--no-notify"
],
"args": ["src/index.ts"],
"cwd": "${workspaceFolder}/backend",
"env": {
"NODE_ENV": "development"
},
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"],
"sourceMaps": true,
"restart": true,
"outputCapture": "std",
"presentation": {
"group": "fullstack",
"panel": "new"
},
"postDebugTask": "🛑 Terminate All Development Processes"
},
{
"name": "🌐 Debug Frontend (Chrome)",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/frontend/src",
"skipFiles": ["<node_internals>/**"]
},
{
"name": "🔧 Debug Frontend (Edge)",
"type": "msedge",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/frontend/src",
"skipFiles": ["<node_internals>/**"]
}
],
"compounds": [
{
"name": "🚀🌐 Debug Full Stack (Chrome)",
"preLaunchTask": "🌐 Frontend",
"configurations": ["🚀 Debug Backend", "🌐 Debug Frontend (Chrome)"],
"stopAll": true,
"presentation": {
"hidden": false,
"group": "fullstack",
"order": 1
}
},
{
"name": "🚀🔧 Debug Full Stack (Edge)",
"preLaunchTask": "🌐 Frontend",
"configurations": ["🚀 Debug Backend", "🔧 Debug Frontend (Edge)"],
"stopAll": true,
"presentation": {
"hidden": false,
"group": "fullstack",
"order": 2
}
}
]
}

28
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,28 @@
{
"typescript.preferences.includePackageJsonAutoImports": "auto",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"files.exclude": {
"**/node_modules": true,
"**/dist": true,
"**/.git": true,
"**/.DS_Store": true
},
"search.exclude": {
"**/node_modules": true,
"**/dist": true
},
"typescript.updateImportsOnFileMove.enabled": "always",
"emmet.includeLanguages": {
"typescript": "typescriptreact"
},
"debug.allowBreakpointsEverywhere": true,
"debug.node.autoAttach": "on",
"terminal.integrated.enablePersistentSessions": false,
"terminal.integrated.confirmOnKill": "editor",
"task.autoDetect": "off",
"task.showDecorations": true
}

322
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,322 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Install Backend Dependencies",
"type": "shell",
"command": "npm",
"args": ["install"],
"options": {
"cwd": "${workspaceFolder}/backend"
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
}
},
{
"label": "Install Frontend Dependencies",
"type": "shell",
"command": "npm",
"args": ["install"],
"options": {
"cwd": "${workspaceFolder}/frontend"
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
}
},
{
"label": "🖥️ Backend",
"type": "shell",
"command": "npm",
"args": ["run", "dev"],
"options": {
"cwd": "${workspaceFolder}/backend"
},
"group": "build",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"group": "dev",
"showReuseMessage": false
},
"problemMatcher": {
"pattern": {
"regexp": "^.*$",
"file": 1,
"location": 2,
"message": 3
},
"background": {
"activeOnStart": true,
"beginsPattern": "^.*ts-node-dev.*$",
"endsPattern": "^🚀 Backend Server läuft auf Port \\d+$"
}
}
},
{
"label": "🌐 Frontend",
"type": "shell",
"command": "npm",
"args": ["start"],
"options": {
"cwd": "${workspaceFolder}/frontend",
"env": {
"BROWSER": "none"
}
},
"group": "build",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"group": "dev",
"showReuseMessage": false
},
"problemMatcher": {
"owner": "custom",
"pattern": {
"regexp": "^.*$",
"file": 1,
"location": 2,
"message": 3
},
"background": {
"activeOnStart": true,
"beginsPattern": "^.*Starting the development server.*$",
"endsPattern": "^.*webpack compiled.*$"
}
}
},
{
"label": "🛑 Kill Frontend Process",
"type": "shell",
"command": "pkill",
"args": ["-f", "npm start"],
"options": {
"cwd": "${workspaceFolder}/frontend"
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "🛑 Kill Backend Process",
"type": "shell",
"command": "pkill",
"args": ["-f", "ts-node-dev"],
"options": {
"cwd": "${workspaceFolder}/backend"
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "📊 Show Development Processes Status",
"type": "shell",
"command": "bash",
"args": [
"-c",
"echo '📊 Aktuelle Development-Prozesse:'; echo ''; ps aux | grep -E 'npm.*start|react-scripts|ts-node-dev' | grep -v grep | awk '{print \"PID: \" $2 \" - \" $11 \" \" $12 \" \" $13}' || echo 'Keine Development-Prozesse gefunden'"
],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "🛑 Cleanup Development Processes",
"type": "shell",
"command": "bash",
"args": [
"-c",
"pkill -f 'ts-node-dev' > /dev/null 2>&1 || true; pkill -f 'npm.*start' > /dev/null 2>&1 || true; pkill -f 'react-scripts' > /dev/null 2>&1 || true"
],
"group": "build",
"presentation": {
"echo": false,
"reveal": "never",
"focus": false,
"panel": "shared",
"clear": false,
"showReuseMessage": false,
"close": true
},
"problemMatcher": [],
"isBackground": false,
"runOptions": {
"reevaluateOnRerun": true
}
},
{
"label": "🧹 Post Debug Cleanup",
"type": "shell",
"command": "bash",
"args": ["-c", "/workspace/scripts/post-debug-cleanup.sh"],
"group": "build",
"presentation": {
"echo": false,
"reveal": "never",
"focus": false,
"panel": "new",
"clear": false,
"showReuseMessage": false,
"close": true
},
"problemMatcher": [],
"isBackground": false,
"runOptions": {
"reevaluateOnRerun": true,
"runOn": "default"
}
},
{
"label": "🔄 Force Kill All Dev Processes",
"type": "shell",
"command": "bash",
"args": [
"-c",
"RANDOM_ID=$RANDOM; echo \"Cleanup ID: $RANDOM_ID\" > /dev/null; pkill -9 -f 'ts-node-dev' > /dev/null 2>&1 || true; pkill -9 -f 'npm.*start' > /dev/null 2>&1 || true; pkill -9 -f 'react-scripts' > /dev/null 2>&1 || true"
],
"group": "build",
"presentation": {
"echo": false,
"reveal": "never",
"focus": false,
"panel": "shared",
"clear": false,
"showReuseMessage": false,
"close": true
},
"problemMatcher": []
},
{
"label": "🛑 Terminate All Development Processes",
"type": "shell",
"command": "/workspace/scripts/kill-dev-processes.sh",
"args": [],
"group": "build",
"presentation": {
"echo": false,
"reveal": "never",
"focus": false,
"panel": "shared",
"clear": false,
"showReuseMessage": false,
"close": true
},
"problemMatcher": [],
"runOptions": {
"runOn": "default"
}
},
{
"label": "🛑 Terminate All Development Processes (Verbose)",
"type": "shell",
"command": "/workspace/scripts/kill-dev-processes.sh",
"args": [],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"clear": true,
"showReuseMessage": false
},
"problemMatcher": []
},
{
"label": "🚀 Start Full Stack (Split Terminal)",
"dependsOrder": "parallel",
"dependsOn": ["🖥️ Backend", "🌐 Frontend"],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
}
},
{
"label": "🔄 Restart Full Stack",
"type": "shell",
"command": "bash",
"args": [
"-c",
"/workspace/scripts/kill-dev-processes.sh && sleep 2 && echo '🚀 Starte Full Stack...' && code --command 'workbench.action.tasks.runTask' 'shell: 🚀 Start Full Stack (Split Terminal)'"
],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"clear": true
},
"problemMatcher": []
},
{
"label": "Build Backend",
"type": "shell",
"command": "npm",
"args": ["run", "build"],
"options": {
"cwd": "${workspaceFolder}/backend"
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": ["$tsc"]
},
{
"label": "Build Frontend",
"type": "shell",
"command": "npm",
"args": ["run", "build"],
"options": {
"cwd": "${workspaceFolder}/frontend"
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
}
}
]
}

164
.woodpecker.yml Normal file
View File

@@ -0,0 +1,164 @@
# Woodpecker CI Pipeline für Full Stack TypeScript Template
# Automatisiert Docker Builds für main, beta und alpha branches
when:
event: [push, pull_request]
steps:
# =============================================================================
# Dependencies installieren und Tests ausführen
# =============================================================================
dependencies:
image: node:18-alpine
commands:
- echo "📦 Installing dependencies..."
- npm ci
- cd backend && npm ci && cd ..
- cd frontend && npm ci && cd ..
when:
event: [push, pull_request]
lint_and_test:
image: node:18-alpine
commands:
- echo "🔍 Running linting and tests..."
- cd backend && npm run test && cd ..
- cd frontend && npm run test -- --coverage --watchAll=false && cd ..
when:
event: [push, pull_request]
depends_on: [dependencies]
build_check:
image: node:18-alpine
commands:
- echo "🏗️ Testing production builds..."
- cd backend && npm run build && cd ..
- cd frontend && npm run build && cd ..
when:
event: [push, pull_request]
depends_on: [dependencies]
# =============================================================================
# Docker Build & Push für MAIN Branch (Production)
# =============================================================================
docker_build_push_main:
image: woodpeckerci/plugin-docker-buildx
settings:
repo: YOUR_REGISTRY/YOUR_ORG/fullstack-typescript-template
registry: YOUR_REGISTRY
platforms: linux/amd64,linux/arm64
tag: latest
dockerfile: Dockerfile
context: .
username:
from_secret: docker_username
password:
from_secret: docker_password
build_args:
- NODE_ENV=production
- BUILD_VERSION=${CI_COMMIT_SHA:0:8}
when:
branch: main
event: push
depends_on: [lint_and_test, build_check]
# =============================================================================
# Docker Build & Push für BETA Branch (Staging)
# =============================================================================
docker_build_push_beta:
image: woodpeckerci/plugin-docker-buildx
settings:
repo: YOUR_REGISTRY/YOUR_ORG/fullstack-typescript-template
registry: YOUR_REGISTRY
platforms: linux/amd64,linux/arm64
tag: beta
dockerfile: Dockerfile
context: .
username:
from_secret: docker_username
password:
from_secret: docker_password
build_args:
- NODE_ENV=staging
- BUILD_VERSION=${CI_COMMIT_SHA:0:8}
when:
branch: beta
event: push
depends_on: [lint_and_test, build_check]
# =============================================================================
# Docker Build & Push für ALPHA Branch (Development)
# =============================================================================
docker_build_push_alpha:
image: woodpeckerci/plugin-docker-buildx
settings:
repo: YOUR_REGISTRY/YOUR_ORG/fullstack-typescript-template
registry: YOUR_REGISTRY
platforms: linux/amd64,linux/arm64
tag: alpha
dockerfile: Dockerfile
context: .
username:
from_secret: docker_username
password:
from_secret: docker_password
build_args:
- NODE_ENV=development
- BUILD_VERSION=${CI_COMMIT_SHA:0:8}
when:
branch: alpha
event: push
depends_on: [lint_and_test, build_check]
# =============================================================================
# Notification bei erfolgreichem Build
# =============================================================================
notify_success:
image: alpine:latest
commands:
- echo "✅ Pipeline erfolgreich abgeschlossen!"
- echo "🐳 Docker Image gebaut für Branch: ${CI_COMMIT_BRANCH}"
- echo "📦 Tag: ${CI_COMMIT_BRANCH == 'main' ? 'latest' : CI_COMMIT_BRANCH}"
- echo "🔗 Commit: ${CI_COMMIT_SHA:0:8}"
when:
status: success
event: push
branch: [main, beta, alpha]
depends_on: [docker_build_push_main, docker_build_push_beta, docker_build_push_alpha]
# =============================================================================
# Security Scan (Optional - kann aktiviert werden)
# =============================================================================
# security_scan:
# image: aquasec/trivy:latest
# commands:
# - trivy image --exit-code 0 --severity HIGH,CRITICAL YOUR_REGISTRY/YOUR_ORG/fullstack-typescript-template:${CI_COMMIT_BRANCH == 'main' ? 'latest' : CI_COMMIT_BRANCH}
# when:
# branch: [main, beta, alpha]
# event: push
# depends_on: [docker_build_push_main, docker_build_push_beta, docker_build_push_alpha]
# =============================================================================
# Pipeline Services (falls externe Services für Tests benötigt werden)
# =============================================================================
# services:
# postgres:
# image: postgres:15-alpine
# environment:
# POSTGRES_DB: testdb
# POSTGRES_USER: test
# POSTGRES_PASSWORD: test
# ports:
# - 5432:5432
#
# redis:
# image: redis:7-alpine
# ports:
# - 6379:6379

230
Dockerfile Normal file
View File

@@ -0,0 +1,230 @@
# Multi-Stage Dockerfile für Full Stack TypeScript Template
# Optimiert für Produktion mit Nginx + Node.js Backend für Traefik Deployment
# =============================================================================
# Build Stage für Frontend
# =============================================================================
FROM node:18-alpine AS frontend-builder
WORKDIR /app/frontend
# Package files kopieren für besseres Caching
COPY frontend/package*.json ./
RUN npm ci && npm cache clean --force
# Source Code kopieren und builden
COPY frontend/ ./
RUN npm run build
# =============================================================================
# Build Stage für Backend
# =============================================================================
FROM node:18-alpine AS backend-builder
WORKDIR /app/backend
# Package files kopieren
COPY backend/package*.json ./
RUN npm install
# Source Code kopieren und builden
COPY backend/ ./
RUN npm run build
# =============================================================================
# Production Stage mit Nginx + Node.js + Supervisor
# =============================================================================
FROM node:18-alpine AS production
# Build Arguments
ARG NODE_ENV=production
ARG BUILD_VERSION=unknown
# Environment Variables setzen
ENV NODE_ENV=${NODE_ENV}
ENV BUILD_VERSION=${BUILD_VERSION}
ENV TZ=Europe/Berlin
# System-Dependencies installieren
RUN apk add --no-cache \
nginx \
supervisor \
curl \
tzdata \
tini \
&& rm -rf /var/cache/apk/*
# Arbeitsverzeichnisse erstellen
RUN mkdir -p /var/www/html \
&& mkdir -p /app/backend/dist \
&& mkdir -p /etc/supervisor.d \
&& mkdir -p /data
# Frontend Build von Builder Stage kopieren
COPY --from=frontend-builder /app/frontend/build /var/www/html
# Backend Build und Dependencies kopieren
COPY --from=backend-builder /app/backend/dist /app/backend/dist
COPY --from=backend-builder /app/backend/node_modules /app/backend/node_modules
# Nginx Konfiguration erstellen
RUN printf 'worker_processes auto;\n\
error_log /var/log/nginx/error.log warn;\n\
pid /var/run/nginx.pid;\n\
\n\
events {\n\
worker_connections 1024;\n\
use epoll;\n\
multi_accept on;\n\
}\n\
\n\
http {\n\
include /etc/nginx/mime.types;\n\
default_type application/octet-stream;\n\
\n\
# Logging\n\
log_format main '\''$remote_addr - $remote_user [$time_local] "$request" '\''\n\
'\''$status $body_bytes_sent "$http_referer" '\''\n\
'\''"$http_user_agent" "$http_x_forwarded_for"'\'';\n\
access_log /var/log/nginx/access.log main;\n\
\n\
# Performance\n\
sendfile on;\n\
tcp_nopush on;\n\
tcp_nodelay on;\n\
keepalive_timeout 65;\n\
gzip on;\n\
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;\n\
\n\
# Security Headers\n\
add_header X-Frame-Options "SAMEORIGIN" always;\n\
add_header X-Content-Type-Options "nosniff" always;\n\
add_header X-XSS-Protection "1; mode=block" always;\n\
add_header Referrer-Policy "strict-origin-when-cross-origin" always;\n\
\n\
server {\n\
listen 80;\n\
server_name _;\n\
\n\
client_max_body_size 50M;\n\
client_body_timeout 60s;\n\
client_header_timeout 60s;\n\
\n\
# Health Check Endpoint\n\
location /health {\n\
access_log off;\n\
return 200 "healthy";\n\
add_header Content-Type text/plain;\n\
}\n\
\n\
# Serve Frontend (SPA)\n\
location / {\n\
root /var/www/html;\n\
index index.html;\n\
try_files $uri $uri/ /index.html;\n\
\n\
# Cache static assets\n\
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {\n\
expires 1y;\n\
add_header Cache-Control "public, immutable";\n\
}\n\
}\n\
\n\
# Proxy API requests to backend\n\
location /api/ {\n\
proxy_pass http://127.0.0.1:3001/api/;\n\
proxy_http_version 1.1;\n\
proxy_set_header Upgrade $http_upgrade;\n\
proxy_set_header Connection "upgrade";\n\
proxy_set_header Host $host;\n\
proxy_set_header X-Real-IP $remote_addr;\n\
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\
proxy_set_header X-Forwarded-Proto $scheme;\n\
proxy_set_header X-Forwarded-Host $host;\n\
proxy_cache_bypass $http_upgrade;\n\
\n\
# Timeouts\n\
proxy_connect_timeout 60s;\n\
proxy_send_timeout 60s;\n\
proxy_read_timeout 60s;\n\
}\n\
}\n\
}' > /etc/nginx/nginx.conf
# Supervisor Konfiguration erstellen
RUN printf '[supervisord]\n\
nodaemon=true\n\
loglevel=info\n\
logfile=/dev/stdout\n\
logfile_maxbytes=0\n\
\n\
[program:nginx]\n\
command=/usr/sbin/nginx -g "daemon off;"\n\
autorestart=true\n\
startretries=3\n\
stdout_logfile=/dev/stdout\n\
stdout_logfile_maxbytes=0\n\
stderr_logfile=/dev/stderr\n\
stderr_logfile_maxbytes=0\n\
priority=10\n\
\n\
[program:backend]\n\
command=node /app/backend/dist/index.js\n\
directory=/app/backend\n\
environment=NODE_ENV="production",DATA_DIR="/data",PORT="3001"\n\
autorestart=true\n\
startretries=3\n\
stdout_logfile=/dev/stdout\n\
stdout_logfile_maxbytes=0\n\
stderr_logfile=/dev/stderr\n\
stderr_logfile_maxbytes=0\n\
priority=20\n\
user=node\n' > /etc/supervisor.d/supervisord.ini
# Health Check Script erstellen
RUN printf '#!/bin/sh\n\
# Health check for both nginx and backend\n\
nginx_status=$(curl -f -s http://localhost/health > /dev/null && echo "ok" || echo "fail")\n\
backend_status=$(curl -f -s http://localhost:3001/api/health > /dev/null && echo "ok" || echo "fail")\n\
\n\
if [ "$nginx_status" = "ok" ] && [ "$backend_status" = "ok" ]; then\n\
exit 0\n\
else\n\
echo "Health check failed: nginx=$nginx_status, backend=$backend_status"\n\
exit 1\n\
fi' > /usr/local/bin/healthcheck.sh && chmod +x /usr/local/bin/healthcheck.sh
# Berechtigungen setzen
RUN chown -R node:node /data /app/backend
RUN chown nginx:nginx /var/www/html
# Health Check konfigurieren
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD /usr/local/bin/healthcheck.sh
# Arbeitsverzeichnis setzen
WORKDIR /app
# Startup mit Tini für proper signal handling
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor.d/supervisord.ini"]
# =============================================================================
# Traefik Labels für automatisches Service Discovery
# =============================================================================
LABEL traefik.enable=true
LABEL traefik.http.routers.app.rule="Host(\`your-domain.com\`)"
LABEL traefik.http.routers.app.entrypoints=websecure
LABEL traefik.http.routers.app.tls.certresolver=letsencrypt
LABEL traefik.http.services.app.loadbalancer.server.port=80
# =============================================================================
# Metadata Labels
# =============================================================================
LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="${BUILD_VERSION}"
LABEL description="Full Stack TypeScript Template - Traefik Ready"
LABEL org.opencontainers.image.source="https://github.com/your-org/fullstack-typescript-template"
LABEL org.opencontainers.image.documentation="https://github.com/your-org/fullstack-typescript-template#readme"
LABEL org.opencontainers.image.vendor="Your Organization"
LABEL org.opencontainers.image.licenses="MIT"

23
LICENSE Normal file
View File

@@ -0,0 +1,23 @@
# License
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

254
README.md Normal file
View File

@@ -0,0 +1,254 @@
# 🚀 Full Stack TypeScript Template
Ein modernes Full Stack Template mit React Frontend und Express Backend, komplett containerisiert mit Docker.
## 📋 Übersicht
- **Frontend**: React 18 + TypeScript + Create React App
- **Backend**: Express.js + TypeScript + Node.js
- **Database**: PostgreSQL 15
- **Container**: Docker + Docker Compose
- **Development**: Hot Reload für Frontend & Backend
- **CI/CD**: Woodpecker CI Ready
## 🛠️ Voraussetzungen
- Node.js >= 18.0.0
- npm >= 9.0.0
- Docker & Docker Compose
## 📦 Installation
### 1. Repository klonen
```bash
git clone <repository-url>
cd fullstack-typescript-template
```
### 2. Dependencies installieren
```bash
npm run install:all
```
## 🏃‍♂️ Development
### Lokale Entwicklung (ohne Docker)
```bash
# Alle Services starten
npm run dev
# Einzeln starten
npm run dev:backend # Backend auf Port 3001
npm run dev:frontend # Frontend auf Port 3000
# Development-Prozesse beenden
npm run kill:dev
```
### Mit Docker (empfohlen)
```bash
# Development mit Hot Reload
npm run docker:dev
# Production-ähnliche Umgebung
npm run docker:prod
# Services stoppen
npm run docker:down
```
## 🐳 Docker Commands
### Build & Start
```bash
npm run docker:build # App Image erstellen
npm run docker:up # Services im Hintergrund starten
npm run docker:dev # Development-Modus mit Rebuild
npm run docker:prod # Production-Modus
```
### Management
```bash
npm run docker:stop # Services stoppen (Container bleiben)
npm run docker:down # Services stoppen + Container entfernen
npm run docker:restart # Services neustarten
```
### Logs & Debugging
```bash
npm run docker:logs # Alle Logs anzeigen
npm run docker:logs:app # Nur App-Logs
npm run docker:logs:db # Nur Datenbank-Logs
```
### Cleanup
```bash
npm run docker:clean # Alles aufräumen (Volumes, Container, Images)
```
### Manuelle Docker Commands
```bash
# Image bauen
docker build -t fullstack-app .
# Services starten
docker-compose up -d
# Logs verfolgen
docker-compose logs -f
# Services stoppen
docker-compose down
```
## 🏗️ Build
### Development Build
```bash
npm run build # Frontend + Backend
npm run build:frontend # Nur Frontend
npm run build:backend # Nur Backend
```
### Production Build
```bash
# Mit Docker (empfohlen)
npm run docker:prod
# Manuell
npm run build
NODE_ENV=production npm start
```
## 🧪 Testing
```bash
npm run test # Alle Tests
npm run test:frontend # Frontend Tests
npm run test:backend # Backend Tests
```
## 🧹 Cleanup
```bash
npm run clean # Alle node_modules & Build-Ordner
npm run clean:build # Nur Build-Ordner
npm run docker:clean # Docker Cleanup
```
## 🌍 Environment Variables
Erstelle eine `.env` Datei im Root-Verzeichnis:
```bash
# Database
POSTGRES_DB=appdb
POSTGRES_USER=postgres
POSTGRES_PASSWORD=secure-password-change-this
DATABASE_URL=postgresql://postgres:secure-password-change-this@postgres:5432/appdb
# App
NODE_ENV=development
JWT_SECRET=your-jwt-secret-change-this
# Ports
APP_PORT=3000
POSTGRES_PORT=5432
```
## 📡 Ports
- **Frontend**: http://localhost:3000
- **Backend**: http://localhost:3001
- **Database**: localhost:5432
## 🏗️ Projekt-Struktur
```
├── 📁 frontend/ # React App
│ ├── 📁 src/
│ ├── 📁 public/
│ └── 📄 package.json
├── 📁 backend/ # Express API
│ ├── 📁 src/
│ └── 📄 package.json
├── 📁 scripts/ # Helper Scripts
│ └── 🔧 kill-dev-processes.sh
├── 🐳 docker-compose.yml
├── 🐳 Dockerfile
├── ⚙️ .woodpecker.yml # CI/CD
└── 📦 package.json # Root Package
```
## 🔧 VS Code Tasks
Das Template enthält vorkonfigurierte VS Code Tasks:
- `🖥️ Backend` - Backend im Watch-Modus starten
- `🌐 Frontend` - Frontend starten
- `🛑 Terminate All Development Processes` - Alle Dev-Prozesse beenden
- `📊 Show Development Processes Status` - Status anzeigen
Öffne die Command Palette (`Ctrl+Shift+P`) und tippe "Tasks: Run Task".
## 🚢 Deployment
### Mit Docker (empfohlen)
```bash
# Production Image erstellen
docker build -t fullstack-app .
# Mit Environment-Variablen starten
NODE_ENV=production npm run docker:prod
```
### CI/CD mit Woodpecker
Das Template ist ready für Woodpecker CI. Konfiguration in `.woodpecker.yml`.
## 🤝 Development Workflow
1. **Development starten**: `npm run docker:dev`
2. **Code ändern**: Hot Reload ist aktiv
3. **Tests ausführen**: `npm run test`
4. **Build testen**: `npm run build`
5. **Production testen**: `npm run docker:prod`
6. **Cleanup**: `npm run docker:clean`
## 📝 Troubleshooting
### Port bereits belegt
```bash
npm run kill:dev
npm run docker:down
```
### Docker Problems
```bash
npm run docker:clean
docker system prune -a
```
### Development-Prozesse hängen
```bash
./scripts/kill-dev-processes.sh
```
## 📄 License
[MIT](LICENSE)

29
backend/package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "fullstack-typescript-template-backend",
"version": "1.0.0",
"description": "TypeScript Backend für Full Stack Template",
"main": "dist/index.js",
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only --inspect=0.0.0.0:9229 src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.24",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
"ts-node-dev": "^2.0.0",
"typescript": "^5.3.3"
},
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.3"
}
}

50
backend/src/index.ts Normal file
View File

@@ -0,0 +1,50 @@
import express from "express";
import cors from "cors";
const app = express();
const PORT = process.env.PORT || 3001;
// Middleware
app.use(cors());
app.use(express.json());
// Routes
app.get("/api/health", (req, res) => {
// 🐛 Breakpoint-Test: Setze hier einen Breakpoint zum Testen
console.log("Health check aufgerufen!");
res.json({
status: "OK",
message: "Backend is running",
timestamp: new Date().toISOString(),
});
});
app.get("/api/photos", (req, res) => {
// Beispiel-Endpunkt für Fotos
res.json({
photos: [
{ id: 1, name: "photo1.jpg", url: "/images/photo1.jpg" },
{ id: 2, name: "photo2.jpg", url: "/images/photo2.jpg" },
],
});
});
app.post("/api/print", (req, res) => {
// Beispiel-Endpunkt für Druckaufträge
const { photoId, copies } = req.body;
console.log(`Druckauftrag erhalten: Photo ID ${photoId}, Kopien: ${copies}`);
res.json({
success: true,
message: `Druckauftrag für Photo ${photoId} mit ${copies} Kopien wurde erstellt`,
jobId: Math.random().toString(36).substr(2, 9),
});
});
app.listen(PORT, () => {
console.log(`🚀 Backend Server läuft auf Port ${PORT}`);
console.log(`📍 Health Check: http://localhost:${PORT}/api/health`);
console.log(`🐛 Debugger bereit auf Port 9229`);
});

36
backend/tsconfig.json Normal file
View File

@@ -0,0 +1,36 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"removeComments": false,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist",
"**/*.test.ts"
]
}

47
docker-compose.yml Normal file
View File

@@ -0,0 +1,47 @@
# Docker Compose für Full Stack Application
version: "3.8"
services:
app:
image: ${APP_IMAGE:-fullstack-app:latest}
container_name: ${APP_CONTAINER_NAME:-fullstack-app}
restart: unless-stopped
environment:
- NODE_ENV=${NODE_ENV:-production}
- DATABASE_URL=${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/appdb}
- JWT_SECRET=${JWT_SECRET:-your-jwt-secret-change-this}
networks:
- app-network
depends_on:
postgres:
condition: service_healthy
ports:
- "${APP_PORT:-3000}:3000"
postgres:
image: postgres:15-alpine
container_name: ${POSTGRES_CONTAINER_NAME:-postgres-db}
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-appdb}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app-network
ports:
- "${POSTGRES_PORT:-5432}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
interval: 10s
timeout: 5s
retries: 5
networks:
app-network:
driver: bridge
volumes:
postgres_data:
driver: local

17
frontend/Dockerfile.dev Normal file
View File

@@ -0,0 +1,17 @@
# Frontend Development Dockerfile
FROM node:18-alpine
WORKDIR /app
# Package files kopieren
COPY package*.json ./
RUN npm ci
# Source code kopieren
COPY . .
# Port exposieren
EXPOSE 3000
# Development server starten
CMD ["npm", "start"]

46
frontend/README.md Normal file
View File

@@ -0,0 +1,46 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

17554
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

44
frontend/package.json Normal file
View File

@@ -0,0 +1,44 @@
{
"name": "fullstack-typescript-template-frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.126",
"@types/react": "^19.1.6",
"@types/react-dom": "^19.1.5",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
frontend/public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
frontend/public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

52
frontend/src/App.css Normal file
View File

@@ -0,0 +1,52 @@
.App {
text-align: center;
}
.App-header {
background-color: #282c34;
padding: 20px;
color: white;
min-height: 100vh;
}
.health-status {
background-color: #4CAF50;
color: white;
padding: 10px;
border-radius: 5px;
margin: 20px 0;
display: inline-block;
}
.status-ok {
font-weight: bold;
}
.photos-grid {
margin-top: 30px;
}
.photo-card {
background-color: #f5f5f5;
color: #333;
margin: 10px;
padding: 15px;
border-radius: 8px;
display: inline-block;
min-width: 200px;
}
.print-button {
background-color: #008CBA;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin-top: 10px;
}
.print-button:hover {
background-color: #007B9A;
}

View File

@@ -0,0 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

98
frontend/src/App.tsx Normal file
View File

@@ -0,0 +1,98 @@
import React, { useState, useEffect } from 'react';
import './App.css';
interface Photo {
id: number;
name: string;
url: string;
}
interface HealthStatus {
status: string;
message: string;
timestamp: string;
}
function App() {
const [photos, setPhotos] = useState<Photo[]>([]);
const [health, setHealth] = useState<HealthStatus | null>(null);
const [loading, setLoading] = useState(true);
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:3001';
useEffect(() => {
const fetchData = async () => {
try {
// Health Check
const healthResponse = await fetch(`${API_BASE_URL}/api/health`);
const healthData = await healthResponse.json();
setHealth(healthData);
// Photos
const photosResponse = await fetch(`${API_BASE_URL}/api/photos`);
const photosData = await photosResponse.json();
setPhotos(photosData.photos);
} catch (error) {
console.error('Fehler beim Laden der Daten:', error);
} finally {
setLoading(false);
}
};
fetchData();
}, [API_BASE_URL]);
const handlePrint = async (photoId: number) => {
try {
const response = await fetch(`${API_BASE_URL}/api/print`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ photoId, copies: 1 }),
});
const data = await response.json();
alert(`Druckauftrag erstellt: ${data.message}`);
} catch (error) {
console.error('Fehler beim Drucken:', error);
alert('Fehler beim Erstellen des Druckauftrags');
}
};
return (
<div className="App">
<header className="App-header">
<h1>📸 Fotodrucker</h1>
{health && (
<div className="health-status">
<p>Backend Status: <span className="status-ok">{health.status}</span></p>
<small>Letzte Aktualisierung: {new Date(health.timestamp).toLocaleString()}</small>
</div>
)}
{loading ? (
<p>Lade Fotos...</p>
) : (
<div className="photos-grid">
<h2>Verfügbare Fotos</h2>
{photos.map((photo) => (
<div key={photo.id} className="photo-card">
<h3>{photo.name}</h3>
<p>URL: {photo.url}</p>
<button
className="print-button"
onClick={() => handlePrint(photo.id)}
>
🖨 Drucken
</button>
</div>
))}
</div>
)}
</header>
</div>
);
}
export default App;

13
frontend/src/index.css Normal file
View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

19
frontend/src/index.tsx Normal file
View File

@@ -0,0 +1,19 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

1
frontend/src/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

1
frontend/src/react-app-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

26
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

17295
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
package.json Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "fullstack-typescript-template",
"version": "1.0.0",
"description": "Full Stack TypeScript Template with React Frontend and Express Backend",
"private": true,
"workspaces": [
"backend",
"frontend"
],
"scripts": {
"install:all": "npm install && npm install --workspace backend && npm install --workspace frontend",
"dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"",
"dev:backend": "npm run dev --workspace backend",
"dev:frontend": "npm run start --workspace frontend",
"build": "npm run build --workspace backend && npm run build --workspace frontend",
"build:backend": "npm run build --workspace backend",
"build:frontend": "npm run build --workspace frontend",
"test": "npm run test --workspace backend && npm run test --workspace frontend",
"test:backend": "npm run test --workspace backend",
"test:frontend": "npm run test --workspace frontend",
"clean": "rm -rf node_modules backend/node_modules frontend/node_modules backend/dist frontend/build",
"clean:build": "rm -rf backend/dist frontend/build",
"docker:build": "docker build -t fullstack-app .",
"docker:up": "docker-compose up -d",
"docker:down": "docker-compose down",
"docker:stop": "docker-compose stop",
"docker:restart": "docker-compose restart",
"docker:logs": "docker-compose logs -f",
"docker:logs:app": "docker-compose logs -f app",
"docker:logs:db": "docker-compose logs -f postgres",
"docker:clean": "docker-compose down -v --remove-orphans && docker system prune -f",
"docker:dev": "docker-compose up --build",
"docker:prod": "NODE_ENV=production docker-compose up -d --build",
"kill:dev": "./scripts/kill-dev-processes.sh"
},
"devDependencies": {
"concurrently": "^8.2.2"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
}
}

57
scripts/kill-dev-processes.sh Executable file
View File

@@ -0,0 +1,57 @@
#!/bin/bash
# Einfacher Lock-Mechanismus ohne störende Ausgaben
LOCK_FILE="/tmp/kill-dev-processes.lock"
# Prüfe auf bereits laufende Instanz
if [ -f "$LOCK_FILE" ]; then
# Prüfe ob Prozess tatsächlich noch läuft
if [ -f "$LOCK_FILE" ] && kill -0 $(cat "$LOCK_FILE") 2>/dev/null; then
# Stilles Beenden wenn bereits aktiv
exit 0
else
# Aufräumen alter Lock-Dateien
rm -f "$LOCK_FILE" 2>/dev/null
fi
fi
# Erstelle Lock-File mit aktueller PID
echo $$ > "$LOCK_FILE"
trap "rm -f $LOCK_FILE 2>/dev/null" EXIT
echo "🛑 Beende alle Development-Prozesse..."
echo ""
# Finde alle Development-Prozesse
FRONTEND_PIDS=$(ps aux | grep -E 'npm start|react-scripts' | grep -v grep | awk '{print $2}')
BACKEND_PIDS=$(ps aux | grep 'ts-node-dev' | grep -v grep | awk '{print $2}')
echo "Frontend-Prozesse (PIDs): $FRONTEND_PIDS"
echo "Backend-Prozesse (PIDs): $BACKEND_PIDS"
echo ""
# Beende Frontend-Prozesse
if [ ! -z "$FRONTEND_PIDS" ]; then
echo "Beende Frontend-Prozesse..."
echo $FRONTEND_PIDS | xargs kill -TERM 2>/dev/null
sleep 2
echo $FRONTEND_PIDS | xargs kill -KILL 2>/dev/null
echo "✅ Frontend-Prozesse beendet"
else
echo "❌ Keine Frontend-Prozesse gefunden"
fi
# Beende Backend-Prozesse
if [ ! -z "$BACKEND_PIDS" ]; then
echo "Beende Backend-Prozesse..."
echo $BACKEND_PIDS | xargs kill -TERM 2>/dev/null
sleep 1
echo $BACKEND_PIDS | xargs kill -KILL 2>/dev/null
echo "✅ Backend-Prozesse beendet"
else
echo "❌ Keine Backend-Prozesse gefunden"
fi
echo ""
echo "Verbleibende Development-Prozesse:"
ps aux | grep -E 'npm start|react-scripts|ts-node-dev' | grep -v grep || echo "✅ Keine Development-Prozesse mehr vorhanden"