Project Introduction & Development Overview
After 228006 lines of code, 1300+ commits and 1 year of development, the Marienmühle project is finally complete. The following chapters will give you an overview of the project's architecture, design decisions, and implementation details.
System Specifications & Feature Overview
The Marienmühle restaurant management system is a comprehensive platform designed to handle all aspects of restaurant operations, from customer reservations to administrative management. Below are the core system specifications organized by functional areas.
Technical Architecture
UI/UX Design & Mobile-First Approach
I designed the Pages and components using Figma. I created 65 pages in total with global color and font settings wich are used thourghout the project.
Over 60 % of our guests are using the old website from their mobile phone. Therefore the new designs are mobile first with a width of 393px which represents the iPhone 15 Pro. Only the staff/admin panel is designed for desktop because it will only be used on desktop devices.



Database Architecture & Entity-Relationship Model
The database is implemented using PostgreSQL with Prisma as the ORM. In the following figure, you can see the ERM of the Marienmühle project, which includes all entities and their relationships
I changed the database structure probable 20 times during the development of the project. But thats the end result for now. As you can see, the Voucher entity has a TODO flag and is not yet implemented, because this is not planned yet.
Project Structure & Turborepo Monorepo Setup
When it came to structuring the project, i quickly realized that i need some kind of monorepo structure to keep the code organized. The packages need to be separated into different applications and libraries. To achievet this, i came across turborepo. Turborepo is a high-performance build system for JavaScript and TypeScript codebases, written in Rust.
I structured the project as follows:
- api
- email-worker
- web
- common
- database
- eslint-config
- logger
- typescript-config
- ui
REST API Endpoints & Express.js Backend
The API is implemented using Express and TypeScript. The logger, common and database packages are referenced. Additional packages like multer, zod, express-rate-limit, minio, etc. are used to enhance functionality and security. From 80 endpoints, only 24 are public, the rest is protected by the authentication system and only available to staff members.
In the following endpoints, you can see how the general structure of the API is organized.
The GET endpoints can be extended with additional query parameters to filter, sort, paginate and further include additional objects in the result.
The POST, PUT, and DELETE endpoints require in most cases authentication and are used to create, update, or delete resources. Specific calls like creating a guest or a reservation can also be done with no authentication.
Get all reservations
Query Parameters:
Update menu item
Path Parameters:
Request Body (form):
Upload media file
Request Body (form):
Browser usage statistics for the last 90 days
Query Parameters:
Authentication & Security Implementation
The api endpoints related to security are crucial for protecting user data and ensuring secure access to the Marienmühle system. They handle authentication, session management, and user registration. Here are the key endpoints:
User authentication with email and password
Request Body (json):
User logout and deletion of session cookies
Refresh tokens
User registration, only available in development mode
Request Body (json):
Before any authentication logic runs, incoming credentials are strictly validated using Zod. I call e.g. to extract and type-check and . If the data doesn't meet the schema (e.g., invalid email format or missing password), Zod throws an error and the request is rejected with a 400 Bad Request—preventing malformed or malicious input from ever hitting our authentication layer.
The Marienmühle project uses a custom authentication system based on CSRF tokens. This approach allows for secure user authentication without relying on third-party services. The authentication flow is as follows:
This flowchart abstracts the core decision points for any incoming admin request. First, it checks whether a valid access token is present - if not, the request is rejected with a 401 error. If the token is valid, the flow branches on “read” versus “write” operations: simple data fetches proceed directly to a 200 OK response, whereas any state-changing action (POST/PUT/DELETE) requires a second check for a matching CSRF token.
This sequence diagram lays out every message exchange in the login handshake. The client submits email and password; the server retrieves the staff record by email and uses bcrypt (with the server's salt) to compare the submitted password against the stored hash. On success, three tokens are created in turn:
- A short-lived access JWT (expires in 15 minutes), signed with the HS512 algorithm and the access-token secret.
- A longer-lived refresh JWT (expires in 7 days), also signed with HS512 and persisted server-side.
- A random 48-character hexadecimal CSRF token, generated via Node's crypto library.
All three are sent back as secure cookies (HttpOnly for both JWTs, JavaScript-accessible for the CSRF token) with appropriate and flags. If any check fails (invalid credentials, expired or missing tokens, or CSRF mismatch), an error is returned immediately. This view makes clear exactly where each cryptographic operation and cookie assignment occurs.
MinIO S3 Storage & File Management
When thinking about the media and menu handling, i first implemented a api saving which i discarded pretty fast, because it was a dirty solution. I realized i need some kind of storage server. The best free and open source for me was MinIO.
In the following image you can see how i handle the uploading of the media items which are basically only images for the gallery or the events and the updating of the menu pdfs.
I set both buckets to be publicly available, therefore, there is no need to create a predefined link. I am getting the file link like this:
Next.js Web Application & Frontend Development
The web application is built using Next.js v15 with the App Router. For the styling, I used Tailwind CSS v4 and custom shadcn/ui components. To code the frontend was probably the most straightforward part, thanks to AI tools like v0 and the VS Code Agent with Claude Sonnet v4. It contains a lot of React code, and I tried to keep everything as simple and reusable as possible.
Admin Portal Video Overview
Email Worker Service & Automated Communication
The email worker is a dedicated service responsible for handling all email communications in the Marienmühle system. It runs as a separate application within the monorepo structure, ensuring that email processing doesn't block the main API or web application performance.
The system handles six different types of automated emails, each with its own HTML template and specific triggers. These emails cover the complete customer journey from reservation to post-visit follow-up:
- Reservation Confirmation - Sent immediately after booking with all reservation details
- Guest Cancellation - Acknowledges when a guest cancels their own reservation
- Owner Cancellation - Notifies guests when the restaurant cancels a reservation
- Reservation Reminder - Dispatched 24 hours before the reservation date
- Follow-up Email - Sent after the dining experience for feedback collection
- Contact Form Confirmation - Acknowledges customer inquiries submitted through the website
Each email template uses dynamic content insertion with placeholders for guest names, dates, reservation details, and restaurant-specific information, ensuring personalized communication throughout the customer experience.


The email worker is configured to poll for new email jobs every 5 minutes, preventing excessive database queries while ensuring timely email delivery. The service can process up to 5 emails simultaneously to balance throughput with resource usage. Failed email deliveries are automatically retried up to 3 times with exponential backoff.
Database Migration Service & Schema Management
The database migrator is a specialized service that handles all database schema changes and data migrations for the Marienmühle project. As a separate application in the monorepo, it ensures consistent and safe database updates across all environments, from development to production.
- ...
- 20250618131630_add_uuids
The migrator uses Prisma's migration system to manage database schema changes. Each migration is versioned and tracked, allowing for safe rollbacks if needed. If for e.g. the migration is not yet applied, the migrator will run it automatically when the service starts. This ensures that the database schema is always up-to-date with the latest application code.
Docker Deployment & CI/CD Pipeline
The Marienmühle project is deployed using Docker containers orchestrated through Docker Compose. Each service runs in its own container with specific health checks, dependency management, and environment configuration. The deployment is managed through Coolify, which handles the CI/CD pipeline and container orchestration.
The deployment consists of four main services: database migrator, web application, API server, and email worker. Each service is built into a separate Docker image and deployed independently, allowing for granular scaling and maintenance. All services communicate through a shared Docker network called .
The complete deployment configuration showcases the orchestration of all services with their dependencies, environment variables, and health monitoring:
The deployment is automated through GitHub Actions workflows that build Docker images and trigger deployments to Coolify. Each service has its own build workflow that pushes images to GitHub Container Registry when changes are detected.
The deployment follows a strict dependency chain: database migration runs first and exits, then the API starts and waits for database readiness, followed by the web application which depends on the API, and finally the email worker. Each long-running service includes comprehensive health checks with configurable intervals, timeouts, and retry attempts to ensure system reliability.