---
title: "Multi-Tenant-SaaS-Entwickler: Terminplanung, Buchungen, Zahlungen und Rollen"
description: "Die Hälfte Ihres Produkts, die niemand sieht, bis sie bricht. NestJS und PostgreSQL, wo die Datenbank die Regeln durchsetzt, statt Ihrem Code zu vertrauen, dass er sie sich merkt."
url: https://riteshkc.com.np/de/services/multi-tenant-systems
source: https://riteshkc.com.np/de/services/multi-tenant-systems.md
updated: 2026-08-27
site: "Ritesh KC"
---
# Das System darunter

Multi-Tenant-SaaS-Entwickler: Terminplanung, Buchungen, Zahlungen und Rollen

Die Hälfte Ihres Produkts, die niemand sieht, bis sie bricht. NestJS und PostgreSQL, wo die Datenbank die Regeln durchsetzt, statt Ihrem Code zu vertrauen, dass er sie sich merkt.

- Layer: 02 / system
- Builds: SaaS-Plattformen (ein Produkt, viele zahlende Kunden, getrennt gehalten und getrennt abgerechnet); Multi-Tenant-Anwendungen (ein Deployment, viele Organisationen, und keine Daten, die zwischen ihnen wandern); Buchungssysteme (zwei Personen können nicht denselben Slot nehmen, und die Datenbank ist es, die das verhindert); APIs (Endpoints, die Ihr eigenes Frontend und der Client eines anderen gleichermaßen aufrufen); Auth und Rollen (wer was darf, an einer Stelle am Rand des Systems geprüft); Zahlungen und Abrechnung (Abonnements, Rechnungen, und Webhooks, die zweimal ankommen); Background-Jobs (die Arbeit, die um 3 Uhr nachts läuft, so geschrieben, dass ein zweiter Lauf nichts tut); Drittanbieter-Integrationen (die API eines anderen, wiederholt durch die Stunden, in denen sie weg ist)
- Tools: NestJS, Node.js, PostgreSQL, Prisma, Redis + BullMQ, AWS
- Drei Multi-Tenant-Produkte in Produktion
- Fahrschulen, Yachtcharter, Auslandsstudium
- Alleiniger Entwickler bei Gradsy: Schema, API, Deploy

## Einen dieser Sätze haben Sie wahrscheinlich schon laut gesagt.

- Wir haben an ein zweites Unternehmen verkauft, und jetzt entscheidet ein if-Statement, wessen Daten zurückkommen. Das funktioniert — bis jemand das if vergisst. Ich verlege die Trennung ins Schema, wo Vergessen keine Option ist, die der Code hat.
- Zwei Kunden haben denselben Slot gebucht. Wir haben einem erstattet, und niemand weiß, wie es passiert ist. Ihre API hat den Slot gelesen und dann geschrieben. Die zweite Buchung kam zwischen diese beiden Schritte. Ein Unique Constraint schließt die Lücke; keine noch so gründliche Prüfung im Code tut das.
- Der Zahlungsanbieter hat denselben Webhook zweimal geschickt, und wir haben zweimal abgebucht. At-least-once ist die Funktionsweise jeder Queue und jedes Webhooks. Jeder Job, den ich schreibe, tut beim zweiten Lauf nichts.
- Niemand im Team will die langweilige Hälfte übernehmen. Die Berechtigungen, die Sonderfälle der Abrechnung, der Job um 3 Uhr nachts, der stimmen muss. Das ist die Hälfte, die ich nehme.
- Eine Person versteht dieses System, und nichts davon ist aufgeschrieben. Ich dokumentiere beim Bauen — das Schema, die Rollen, die Jobs und die Gründe hinter den unbequemen Entscheidungen. Ich war diese eine Person, und das ist ein schlechter Ort, ein Team zurückzulassen.

## Eine Regel gehört in die Datenbank, wenn die Datenbank sie halten kann.

Anwendungscode prüft eine Regel. Eine Datenbank setzt sie durch. Der Unterschied zeigt sich an dem Tag, an dem zwei Leute in derselben Sekunde denselben Button klicken.

- ein Unique Constraint: zwei Personen buchen denselben Slot. Der zweite Schreibvorgang schlägt fehl, und dieser Person wird gesagt, dass er weg ist.
- Zustände in einer Tabelle: die Übergänge, die eine Bewerbung machen darf, sind Zeilen, die man lesen kann — keine if-Kette über drei Services verteilt.
- Jobs, die sich gefahrlos wiederholen: eine Queue liefert mindestens einmal, was manchmal zweimal heißt. Einmal belasten, einmal mailen, einmal zählen.
- Mandantentrennung im Schema: die Subdomain begrenzt die Query, damit nie ein if-Statement das ist, was zwei Kunden auseinanderhält.
- ein Ort für Berechtigungen: wer das darf, ist eine Frage, die Sie durch Öffnen einer Datei beantworten.
- Migrationen, die rückwärts laufen: ein schlechtes Release rollt so zurück, wie es hinausging, statt zu einem Incident zu werden.

## Rahmenbedingung, Schema, API, Betrieb.

01. begrenzen — was niemals passieren darf — eine Doppelbuchung, ein vermischter Mandant, eine zweimal belastete Karte
02. modellieren — das Schema, das genau das unmöglich macht, geschrieben bevor ein einziger Endpoint existiert
03. bauen — NestJS und Node auf PostgreSQL, Berechtigungen an einer Kante geprüft, Jobs sicher bei zweitem Lauf
04. betreiben — auf AWS deployed, samt der Pipeline, die es dorthin bringt — Ausliefern ist ein Dienstag, kein Ereignis

## Sie bringen es. Ich übernehme. Sie bekommen es besser zurück.

- You bring: ein Produkt mit einem zweiten Kunden, oder ein System, das einmal gebrochen ist; die Regel, die niemals verletzt werden darf; wer auch immer das Support-Ticket beantwortet, wenn sie es doch wird
- I handle: Schema-Design, und Migrationen, die in beide Richtungen laufen; Multi-Tenant-Isolation und rollenbasierter Zugriff; APIs, die Ihr Frontend und der Client eines anderen gleichermaßen aufrufen; Queues, Background-Jobs und Wiederholungssicherheit; Zahlungen, Rechnungen und doppelte Webhooks; Deployment und die Pipeline, die es erledigt
- You get: Ihr Code, in Ihrem Repository, ab dem ersten Commit; Dokumentation, die beim Bauen entsteht statt für das Ende versprochen zu werden; ein System, in dem die Regeln von der Datenbank durchgesetzt und nicht von Menschen erinnert werden

Zuerst das Schema, dann die API, dann die Oberfläche. Die Screens darüber baue ich mit, damit es keine Naht gibt, an der meine Arbeit aufhört und die von jemand anderem anfangen soll.

## how I build it

A rule belongs in the database if the database can hold it.

Two counselors open the same appointment slot and both click book. Postgres lets both of them read that slot before either one saves. Both see it free. Both take it. No amount of checking in your API code closes that gap, because the gap sits between the read and the write, and it is as wide as your traffic is heavy. A unique constraint on the table does close it. The second write fails, and the second person is told the slot has gone.

State works the same way. An application that moves through stages gets a table of states and the moves allowed between them, not a text column and a pile of if statements spread across three services. Then "can this go from submitted to accepted" is a row you can look at instead of a code review you have to run.

Queues promise to deliver a message at least once. In practice that sometimes means twice. So every background job is written to do nothing on the second run. Charge once. Email once. Count once.

Schema first, then the API, then the interface. Migrations run backwards as well as forwards. Permissions are checked in one place at the edge of the system, so "who is allowed to do this" is a question you answer by opening one file.

## when a project needs more than me

I deploy and run the infrastructure most products need. Some projects need more than that: dedicated platform work, heavy data pipelines, an environment with real operational demands. For those I bring in senior infrastructure engineers I already work with.

The database and the application stay with me. The platform goes to somebody who does it full time. You are still dealing with one arrangement rather than three, and one person answerable for how it turns out.

## Proof

- [Gradsy](https://riteshkc.com.np/de/work/gradsy): Der gesamte Betrieb einer Auslandsstudien-Beratung über zwei Repos. Studierende bewerben sich, Berater bewegen Bewerbungen durch Zustände, Admins machen den Rest. Viel davon war Nebenläufigkeit. Stack: Next.js, React, NestJS, TypeScript, Prisma, PostgreSQL, Redis, BullMQ, AWS.

## Related writing

- [Überbuchung ist ein Datenbankproblem](https://riteshkc.com.np/de/blog/overbooking-is-a-database-problem): Zeilen zu zählen, bevor man eine einfügt, ist keine Kapazitätsprüfung. Wie stattdessen ein Unique Index, eine Constraint-Verletzung und ein NULL die Arbeit übernommen haben.
- [Eine Statusmaschine gehört in eine Tabelle](https://riteshkc.com.np/de/blog/a-status-machine-belongs-in-a-table): Neun Status, zwei Sätze erlaubter Übergänge und zwei Namen für jeden Zustand. Das alles in Zeilen statt in eine if-Kette zu verlegen, hat die Entwicklung aus dem Workflow-Geschäft geholt.
- [Einen Anbieter drosseln, ohne Rate Limiter](https://riteshkc.com.np/de/blog/rate-limiting-by-queue-shape): SES begrenzt Sendungen pro Sekunde. Die Lösung war Worker-Nebenläufigkeit von eins plus ein Sleep — und eine ehrliche Notiz im Code über den Fall, in dem das nicht mehr trägt.
