Schichtpläne werden in vielen Unternehmen noch per Excel oder WhatsApp koordiniert – mit allen damit verbundenen Problemen: Überschneidungen werden übersehen, Änderungen kommen nicht bei allen an, und eine Übersicht über offene Schichten gibt es nicht. Als ein Bekannter im Eventcatering-Bereich genau diese Probleme beschrieb, war das der Startschuss für den Schichtplaner.
Das Besondere an diesem Projekt: Es sind technisch gesehen drei Anwendungen in einer – eine Flask-REST-API im Backend und zwei separate React-Frontends, die sich dieselbe API teilen.
Warum Flask statt Django?
Für reine API-Backends ohne Template-Rendering ist Flask oft die bessere Wahl: weniger Overhead, klare Trennung zwischen API und Frontend, einfachere Konfiguration für JSON-only-Endpoints. Flask-Migrate (basierend auf Alembic) übernimmt die Datenbank-Migrations – bei jedem Deployment wird flask db upgrade ausgeführt, um Schema-Änderungen sicher auf die Produktionsdatenbank anzuwenden.
JWT-Authentifizierung mit Rollenmodell
Die API nutzt JWT-Token zur Authentifizierung. Ein Token enthält neben der User-ID auch die Rolle (admin oder staff). Beide Frontends nutzen dieselbe API, bekommen aber je nach Rolle unterschiedliche Daten und Berechtigungen:
from functools import wraps
from flask_jwt_extended import verify_jwt_in_request, get_jwt
def require_role(role):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
verify_jwt_in_request()
claims = get_jwt()
if claims.get("role") != role:
return {"msg": "Forbidden"}, 403
return fn(*args, **kwargs)
return wrapper
return decorator
@app.route("/api/shifts/", methods=["POST"])
@require_role("admin")
def create_shift():
...Zwei Frontends – ein API-Endpunkt
Der Admin benutzt das Admin-Dashboard: Schichten anlegen, Mitarbeiter verwalten, Besetzung kontrollieren. Die Servicekräfte öffnen den Marktplatz: offene Schichten sehen, sich eintragen, Absagen melden.
Beide Frontends sind mit React und Vite gebaut. Der Build-Output sind statische HTML/JS/CSS-Dateien, die Nginx direkt serviert. Für API-Calls zeigen beide auf dieselbe Flask-API, aber mit verschiedenen JWT-Rollen.
cd frontend && npm ci && npm run build # → frontend/dist/
cd marketplace && npm ci && npm run build # → marketplace/dist/Nginx-Konfiguration auf NixOS
Ein Gunicorn-Socket bedient beide Nginx-Vhosts. Nginx entscheidet anhand des Host-Headers, welches statische Frontend serviert wird, und leitet API-Anfragen an denselben Socket weiter:
services.nginx.virtualHosts."admin.example.com" = {
root = "/var/www/meinprojekt/frontend/dist";
locations."/" = { tryFiles = "$uri $uri/ /index.html"; };
locations."/api/" = {
proxyPass = "http://unix:/run/meinprojekt/gunicorn.sock";
};
};
services.nginx.virtualHosts."jobs.example.com" = {
root = "/var/www/meinprojekt/marketplace/dist";
locations."/" = { tryFiles = "$uri $uri/ /index.html"; };
locations."/api/" = {
proxyPass = "http://unix:/run/meinprojekt/gunicorn.sock";
};
};Das tryFiles "$uri $uri/ /index.html" ist entscheidend für das SPA-Routing: Wenn React-Router eine Route wie /shifts/42 rendert und der Nutzer die Seite neu lädt, würde Nginx ohne diese Direktive eine 404 zurückgeben.
Deploy mit flask db upgrade
Das Deploy-Script muss drei Teile aktualisieren:
cd /var/www/meinprojekt
git pull origin main
# Backend + Migrations
pip install -r backend/requirements.txt
FLASK_APP=wsgi.py flask db upgrade
# Frontends neu bauen
cd frontend && npm ci && npm run build
cd marketplace && npm ci && npm run build
systemctl restart meinprojektEin GitHub Webhook triggert dieses Script automatisch bei jedem Push – der gleiche Mechanismus wie in meinem Webhook-Deploy-Artikel beschrieben.
Fazit
Das Hybrid-Setup aus Flask-API und zwei React-Frontends hat sich bewährt. Die klare Trennung zwischen API und UI macht es einfach, Admin-Interface und Mitarbeiter-App unabhängig voneinander weiterzuentwickeln. Fragen zur Architektur gerne über das Kontaktformular.