02_Prisma
Code-Dateien
| Dateiname | Aktion |
|---|---|
| CODECode_bank.zip | Download |
| CODECode_pizza.zip | Download |
| CODECode_student.zip | Download |
Videos
| Dateiname | Aktion |
|---|---|
| VIDEOVideo_Bank_D | Abspielen |
| VIDEOVideo_Pizza_E | Abspielen |
Lernmaterialien
ORM
Ein ORM (Object–Relational Mapping) ist eine Technik bzw. Bibliothek, die Objekte aus deinem Code (Klassen/Objekte) auf Tabellen in einer relationalen Datenbank abbildet.
Statt SQL von Hand zu schreiben, arbeitest du mit Objekten – das ORM erzeugt (meist) die passenden SQL-Statements für dich.
Beispiel-Idee
Du hast eine Tabelle users in der DB und im Code ein
User-Objekt:
Ohne ORM:
SELECT * FROM users WHERE id = 1;(SQL selbst schreiben)Mit ORM (vereinfacht):
User.findById(1)(ORM macht daraus SQL)
Was macht ein ORM typischerweise?
CRUD: Create, Read, Update, Delete als Methoden
Mapping: Spalten ↔︎️ Objektfelder, Tabellen ↔︎️ Klassen
Beziehungen: 1:n, n:m (z. B. User–Posts) als Objekt-Relationen
Migrationen: Tabellen/Schema-Versionierung per Code
Validierung & Typen (je nach ORM)
Vorteile
Weniger „Boilerplate“-SQL (Standard-Code) für Standardfälle
Einheitlicher Zugriff auf DB, oft bessere Wartbarkeit
Praktische Features (Migrations, Relations, Lazy/Eager Loading)
Nachteile
Kann komplexe SQL-Abfragen verstecken oder erschweren
Performance-Fallen (z. B. N+1-Problem)
Abhängigkeit vom ORM-Stil/Framework
Prisma
Prisma ist ein modernes ORM (Object-Relational Mapping) für TypeScript & JavaScript, das den Zugriff auf relationale Datenbanken stark vereinfacht und typsicher macht.
Kurzdefinition
Prisma ist:
ein Datenbank-Toolkit (nicht nur ein ORM)
entwickelt von der Prisma
TypeScript-first
stark typisiert
kompatibel mit PostgreSQL, MySQL, SQL Server, SQLite
➡️ Prisma übersetzt Typen & Objekte in SQL – sicher und kontrolliert.
Wozu braucht man Prisma?
Klassischer DB-Zugriff (Node.js):
const result = await db.query("SELECT * FROM users WHERE id = " + id);❌ SQL-Injection ❌ Keine Typprüfung ❌ Fehler erst zur Laufzeit
Mit Prisma:
const user = await prisma.user.findUnique({
where: { id: 1 }
});✅ Typsicher ✅ Autocomplete ✅ Sicher ✅ Lesbar
Die 3 Kernbestandteile von Prisma
1. Prisma Schema (Datenmodell)
model Student {
id Int @id @default(autoincrement())
name String
course String
}
➡️ Single Source of Truth
➡️ Vergleichbar mit:
ER-Modell
DDL in SQL
2. Prisma Client (Typsichere API)
Aus dem Schema wird automatisch Code generiert:
const users = await prisma.user.findMany();Methoden sind typisiert
Fehler schon im Editor
3. Migrationen
Eine Prisma Migration ist eine versionierte
Änderung am Datenbankschema, die Prisma aus deinem
schema.prisma ableitet und als
SQL-Änderungsschritt speichert und auf die Datenbank
anwendet.
npx prisma migrate devVersionskontrolle für das DB-Schema
Reproduzierbare DB-Zustände
Vergleichbar mit Flyway / Liquibase
Unterstützte Datenbanken
| Datenbank | Support |
|---|---|
| PostgreSQL | ✅ |
| MySQL | ✅ |
| SQL Server | ✅ |
| SQLite | ✅ |
| Oracle | ❌ (Stand heute) |
➡️ Für Oracle wäre weiterhin:
JDBC / JPA
oder klassische SQL-Zugriffe nötig
Prisma vs. klassische ORMs
| Aspekt | Prisma | Hibernate / JPA |
|---|---|---|
| Sprache | TypeScript | Java |
| Typisierung | Sehr stark | Stark |
| Konfiguration | Einfach | Komplex |
| Performance | Sehr gut | Gut |
| SQL-Nähe | Mittel | Abstrakt |
➡️ Prisma ist weniger Magie, mehr Kontrolle.
Prisma & TypeScript (großer Vorteil)
Prisma nutzt TypeScript maximal aus:
Autocomplete
Compile-Fehler bei falschen Queries
Typisierte Relationen
user.posts[0].title // garantiert vorhanden➡️ Keine NullPointer-Überraschungen
Prisma & moderne Runtimes
Prisma funktioniert mit:
Node.js
Deno
Bun
Serverless & Cloud
Besonders beliebt in:
REST-APIs
GraphQL
Microservices
Typische Architektur mit Prisma
Controller / API
↓
Prisma Client
↓
SQL-Datenbank
➡️ Saubere Schichten
Implementierung
deno add npm:prisma@6
generator client {
provider = "prisma-client"
output = "../generated"
runtime = "deno"
}
datasource db {
provider = "sqlite"
url = "file:../student.db"
}
model Student {
id Int @id @default(autoincrement())
name String
course String
}
.env
DATABASE_URL=file:../student.db
.gitignore
.env
**/*.db
deno.json
{
"tasks": {
"dev": "deno --watch -A --env-file server.ts",
"pv": "deno -A prisma validate",
"pg": "deno -A prisma generate",
"pmd": "deno -A prisma migrate dev",
"pms": "deno -A prisma migrate status",
"seed": "deno -A --env-file ./prisma/seed.ts"
},
...
"dev": "deno --watch -A --env-file server.ts"
denoStartet die Deno Runtime.
--watchDeno überwacht alle verwendeten Dateien. Bei jeder Änderung wird das Programm wird automatisch neu gestartet.
-AAllow All Permissions. –allow-read –allow-write –allow-net –allow-env –allow-run –allow-ffi. Nur für Development empfohlen!
--env-fileLädt Umgebungsvariablen aus der Datei.env.
server.tsEinstiegspunkt der Anwendung.
"pv": "deno -A prisma validate"
pvName des Tasks ist frei wählbar. Prisma Validate.
validateprüft die Dateiprisma/schema.prisma. Syntax des Schemas, Models & Relationen und Datasource-Konfiguration.
"pg": "deno -A prisma generate"
pgPrisma Generate. startet die Deno Runtime. führt die Prisma-CLI ausliest
prisma/schema.prismaerzeugt den Prisma Client und schreibt Code in./generated/.Wann muss man
prisma generateausführen?
Schema geändert
Neue Relation
Neue DB
"pmd": "deno -A prisma migrate dev"
Dieser Befehl:
liest
prisma/schema.prismavergleicht es mit dem aktuellen DB-Schema
erstellt neue Migrationen (SQL)
wendet sie direkt auf die Datenbank an
führt automatisch
prisma generateaus
"pms": "deno -A prisma migrate status"
Zeigt:
welche Migrationen lokal existieren
welche in der Datenbank angewendet wurden
ob Migrationen fehlen, ausstehen oder driften
"seed": "deno -A --env-file seed.ts"
Initialdaten anlegen
-ANotwendig, weil
seed.tsmeist:
auf die Datenbank zugreift
Umgebungsvariablen liest
evtl. Dateien lädt
.gitignore
.env
**/*.db
generated/
Restart Visual Studio Code!!!
deno add npm:prisma@6
deno add npm:@prisma/client@6
prisma/seed.ts
import { PrismaClient } from "../generated/client.ts";
const prisma = new PrismaClient();
const students = [
{ id: -1, name: "Anna", course: "Computer Science" },
{ id: -2, name: "Susi", course: "Mathematics" },
{ id: -3, name: "Fritz", course: "English" },
{ id: -4, name: "Andrea", course: "Mathematics" },
{ id: -5, name: "Thomas", course: "German" },
{ id: -6, name: "Verena", course: "Mathematics" },
{ id: -7, name: "Marion", course: "Mathematics" },
{ id: -8, name: "Karl", course: "Computer Science" },
{ id: -9, name: "Hans", course: "Mathematics" },
{ id: -10, name: "Barbara", course: "Computer Science" },
];
async function main() {
console.log("Remove old testdata...");
const result = await prisma.student.deleteMany({
where: {
id: { lt: 0 },
},
});
console.log(`Deleted ${result.count} students with negative id`);
console.log("Start seeding...");
for (const student of students) {
await prisma.student.create({
data: student,
});
console.log(`Created student with id: ${student.id}`);
}
console.log("Seeding finished.");
}
try {
await main();
}
catch (e) {
console.error(e);
Deno.exit(1);
}
finally {
await prisma.$disconnect();
}
Das ARRAY im server.ts gehört gelöscht, da es durch eine Tabelle ersetzt wird!
statt Array
import { PrismaClient } from "./generated/client.ts";
const prisma = new PrismaClient();const students = await prisma.student.findMany();get route
app.get("/students", async (_req: Request, res: Response) => {
const students = await prisma.student.findMany();
res.json(students);
});Das array students muss bei jedem get neu
geladen werden, da sich die Daten in der Tabelle ändern können!
async bedeutet in JavaScript/TypeScript: Diese
Funktion arbeitet “asynchron” und kann auf Dinge warten, die
Zeit brauchen (z. B. Datenbankzugriffe), ohne den Server zu
blockieren.
await bedeutet: „Warte, bis das Promise fertig
ist, und gib mir das Ergebnis.“
Es wird benutzt, wenn eine Funktion asynchron arbeitet (z. B. Datenbank, Netzwerk, Datei).
prisma.student.findMany()startet die DB-Abfrage und liefert sofort ein Promise.awaitsorgt dafür, dass dein Code an dieser Stelle pausiert, bis die Abfrage fertig ist.Danach steckt im
studentswirklich das Array mit Daten.
get id
app.get("/students/:id", async (req: Request, res: Response) => {
const id = parseInt(req.params.id);
const student = await prisma.student.findUnique({
where: { id }
});
if (!student) {
return res.status(404).json({ error: "Student not found" });
}
res.json(student);
});Das Suchen eines Studenten muss auch wieder über die Tabelle erfolgen
Änderungen:
asyncwurde hinzugefügt (damitawaitmöglich ist)students.find(...)wurde ersetzt durchawait prisma.student.findUnique(...)Der Rest (id parsen, 404 wenn nicht gefunden, sonst
res.json) bleibt gleich.
post
app.post("/students", async (req: Request, res: Response) => {
const { name, course } = req.body;
if (!name || !course) {
return res.status(400).json({ error: "Name and course are required!" });
}
const newStudent = await prisma.student.create({
data: { name, course }
});
res.status(201).json(newStudent);
});Änderungen:
asynchinzugefügtnewId+students.push(...)fällt weg (die DB/Prisma erzeugt und speichert den Datensatz)Stattdessen:
await prisma.student.create({ data: ... })
Wichtig: Das funktioniert, da das Prisma-Model
Student ein
id Int @id @default(autoincrement()) hat. Wenn
id nicht automatisch erzeugt wird, muss die id
im data mitgeben.
put
app.put("/students/:id", async (req: Request, res: Response) => {
const id = parseInt(req.params.id);
const { name, course } = req.body;
if (!name || !course) {
return res.status(400).json({ error: "Name and course are required!" });
}
try {
const updatedStudent = await prisma.student.update({
where: { id },
data: { name, course }
});
res.json(updatedStudent);
} catch {
return res.status(404).json({ error: "Student not found" });
}
});Änderungen
async+awaitfindIndex/Array-Update wegPrisma
updateübernimmt Speichern in DBtry/catch, weil Prisma bei “id existiert nicht” einen Fehler wirft
patch
app.patch("/students/:id", async (req: Request, res: Response) => {
const id = parseInt(req.params.id);
const { name, course } = req.body;
const data: { name?: string; course?: string } = {};
if (name !== undefined) data.name = name;
if (course !== undefined) data.course = course;
try {
const updatedStudent = await prisma.student.update({
where: { id },
data
});
res.json(updatedStudent);
} catch {
return res.status(404).json({ error: "Student not found" });
}
});!!! Bei einem numerischen Wert die Typkonvertierung nicht vergessen !!!
if (balance !== undefined) data.balance = Number(balance);
Änderungen:
async+awaitkein
students.find(...)und kein Mutieren im ArrayPrisma
updatespeichert direkt in der DBdata: { ... }sorgt dafür, dass nur die Felder aktualisiert werden, die wirklich im Body vorhanden sindtry/catch, weil Prisma bei nicht vorhandenerideinen Fehler wirft
const data: { name?: string; course?: string } = {};
bedeutet:
dataist eine Variable (ein Objekt), die am Anfang leer ist:{}Der Teil nach dem Doppelpunkt
: { name?: string; course?: string }ist ein TypeScript-Typ für dieses Objekt.
Was sagt dieser Typ aus?
name?: stringheißt:
datakann ein Feldnamehaben, und wenn es existiert, ist es einstring.
Das?bedeutet optional (kann fehlen).course?: stringheißt dasselbe fürcourse.
Warum macht man das bei PATCH?
Bei PATCH willst du nur die Felder setzen, die im Request
vorkommen.
Deshalb baust du ein Objekt schrittweise auf:
const data: { name?: string; course?: string } = {};
if (name !== undefined) data.name = name;
if (course !== undefined) data.course = course;Am Ende kann data sein:
{}(nichts zu ändern){ name: "Anna" }{ course: "DB" }{ name: "Anna", course: "DB" }
Und genau so ein Objekt erwartet Prisma in
update({ data: ... }).
delete
app.delete("/students/:id", async (req: Request, res: Response) => {
const id = parseInt(req.params.id);
try {
await prisma.student.delete({
where: { id }
});
return res.status(204).send(); // No Content
} catch {
return res.status(404).json({ error: "Student not found" });
}
});Änderugnen:
async+awaitfindIndex/splicewegPrisma
delete({ where: { id } })löscht in der DBtry/catch, weil Prisma bei nicht vorhandener ID einen Fehler wirft
endpoints.rest
GET http://localhost:3000/students
###
GET http://localhost:3000/students/-3
###
POST http://localhost:3000/students
Content-Type: application/json
{
"name": "Max",
"course": "Italien"
}
###
PATCH http://localhost:3000/students/-1
Content-Type: application/json
{
"name": "Annalisa",
"course": "Informatik"
}
###
DELETE http://localhost:3000/students/-2