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:
25
.devcontainer/Dockerfile
Normal file
25
.devcontainer/Dockerfile
Normal 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
|
||||
42
.devcontainer/devcontainer.json
Normal file
42
.devcontainer/devcontainer.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
.devcontainer/docker-compose.yml
Normal file
16
.devcontainer/docker-compose.yml
Normal 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
61
.dockerignore
Normal 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
83
.gitignore
vendored
Normal 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
15
.vscode/extensions.json
vendored
Normal 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
7
.vscode/keybindings.json
vendored
Normal 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
72
.vscode/launch.json
vendored
Normal 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
28
.vscode/settings.json
vendored
Normal 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
322
.vscode/tasks.json
vendored
Normal 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
164
.woodpecker.yml
Normal 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
230
Dockerfile
Normal 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
23
LICENSE
Normal 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
254
README.md
Normal 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
29
backend/package.json
Normal 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
50
backend/src/index.ts
Normal 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
36
backend/tsconfig.json
Normal 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
47
docker-compose.yml
Normal 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
17
frontend/Dockerfile.dev
Normal 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
46
frontend/README.md
Normal 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 can’t go back!**
|
||||
|
||||
If you aren’t 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 you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t 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
17554
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
44
frontend/package.json
Normal file
44
frontend/package.json
Normal 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
BIN
frontend/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
43
frontend/public/index.html
Normal file
43
frontend/public/index.html
Normal 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
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
BIN
frontend/public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
frontend/public/manifest.json
Normal file
25
frontend/public/manifest.json
Normal 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"
|
||||
}
|
||||
3
frontend/public/robots.txt
Normal file
3
frontend/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
52
frontend/src/App.css
Normal file
52
frontend/src/App.css
Normal 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;
|
||||
}
|
||||
9
frontend/src/App.test.tsx
Normal file
9
frontend/src/App.test.tsx
Normal 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
98
frontend/src/App.tsx
Normal 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
13
frontend/src/index.css
Normal 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
19
frontend/src/index.tsx
Normal 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
1
frontend/src/logo.svg
Normal 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
1
frontend/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
15
frontend/src/reportWebVitals.ts
Normal file
15
frontend/src/reportWebVitals.ts
Normal 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;
|
||||
5
frontend/src/setupTests.ts
Normal file
5
frontend/src/setupTests.ts
Normal 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
26
frontend/tsconfig.json
Normal 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
17295
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
package.json
Normal file
43
package.json
Normal 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
57
scripts/kill-dev-processes.sh
Executable 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"
|
||||
Reference in New Issue
Block a user