Vzhled
21 • ORM
ORM, schéma, migrace databáze, dotazování přes ORM
Formát: 30 min praktická úloha, 15 min obhajoba + teorie. Praktika je s Prisma 7 (Next.js), teorie pokrývá ORM obecně, hlavní nástroje (Prisma, EF Core, Drizzle) a workflow.
🚨
POZOR: Prisma 7 (vydaná listopad 2025) zavedla zásadní změny oproti starší Prismě 6. Pokud někde najdeš starší tutorial, většina jeho konfigurace už nebude fungovat. Konkrétně v Prismě 7:
- Connection URL se přesunula ze
schema.prismado nového souboruprisma.config.ts new PrismaClient()vyžaduje driver adapter (pro SQLite:@prisma/adapter-better-sqlite3)- Generator se přejmenoval z
prisma-client-jsnaprisma-clienta generuje Client mimonode_modules(většinou doapp/generated/prisma/)
API operace (findMany, create, update, ...) zůstávají stejné. Tyhle zápisky pokrývají Prismu 7.
⚠️
Taky bych byl tady opatrnej s LLMkama. Většina o Prismě 7 vůbec neví – nejaktuálnější asi budou Prisma docs.
Část 1: Teorie
Co je ORM a proč ho používáme
ORM (Object-Relational Mapper) je vrstva mezi aplikačním kódem a relační databází. Místo psaní SQL dotazů pracuješ s objekty a metodami ve svém programovacím jazyce. ORM je přeloží na SQL za tebe.
Tvůj kód (TypeScript / C# / Python)
▲
│ prisma.post.findMany({ where: { published: true } })
▼
ORM
▲
│ SELECT * FROM Post WHERE published = true
▼
Relační databáze (SQLite / PostgreSQL / MySQL / SQL Server)Raw SQL vs ORM
| Raw SQL | ORM | |
|---|---|---|
| Syntaxe | SELECT * FROM Post WHERE id = 1 | prisma.post.findUnique({ where: { id: 1 } }) |
| Typová bezpečnost | String, chyba až za běhu | TypeScript typy, chyba při kompilaci |
| Migrace | Ruční SQL soubory | Automatické z definice schématu |
| Přenositelnost DB | Dialekty se liší (SQLite ≠ PostgreSQL) | ORM to abstrahuje |
| Složité dotazy | Plná kontrola, optimální | Někdy omezenější, escape hatch raw SQL |
| Učení | SQL syntax | API frameworku |
| Refactoring | Risk, IDE neumí najít všechny dotazy | TypeScript najde všechny použití |
Pravidlo z praxe: 90 % dotazů ORM zvládne výborně. Zbylých 10 % (komplexní raportní dotazy, agregace) raději napsat raw SQL pod ORM (Prisma má
$queryRaw).
Code-first vs DB-first
- Code-first (běžnější): definuješ strukturu DB v kódu (schéma), ORM vygeneruje a aplikuje SQL přes migrace. Pravda v kódu, DB je odvozená.
- DB-first: DB už existuje, ORM z ní vygeneruje kód (modely, klienta). Pravda v DB, kód je odvozený.
Prisma a moderní EF Core typicky používají code-first, je to čistější workflow.
Přehled ORM nástrojů
| ORM | Jazyk | Typické prostředí | Charakteristika |
|---|---|---|---|
| Prisma | TypeScript / JavaScript | Next.js, Node.js | Deklarativní schema, generuje typed client |
| Drizzle ORM | TypeScript | Modern stack (edge, serverless) | SQL-first, lightweight, výborný DX |
| Entity Framework Core | C# | ASP.NET Core | LINQ dotazy, code-first migrations |
| TypeORM | TypeScript | NestJS, Node.js | Decorator-based, starší přístup |
| Sequelize | JavaScript | Node.js (legacy) | Z doby před TypeScriptem |
| Hibernate | Java | Spring | Mature, mnoho features, složitější |
| SQLAlchemy | Python | FastAPI, Flask | Velmi expressive |
Tahle otázka je obvykle s Prisma (Next.js context).
Bonus: Prisma vs EF Core vs Drizzle
Praktická srovnávací tabulka, kterou si zkoušející asi cení.
| Prisma 7 | EF Core | Drizzle ORM | |
|---|---|---|---|
| Jazyk | TypeScript / JS | C# | TypeScript |
| Definice schématu | Vlastní DSL (schema.prisma) | C# třídy + DataAnnotations / Fluent API | TypeScript objekty |
| Generování typů | Generated do app/generated/prisma/ | Reflection / EF runtime | Inferred z definic |
| Migrace | prisma migrate dev | dotnet ef migrations add | drizzle-kit migrate |
| Query syntax | Method chaining (objektový) | LINQ (declarative) | SQL-like builder |
| Filozofie | "Zjednoduš ORM" | "Plný ORM se vším" | "Tenký wrapper nad SQL" |
| Architektura | JavaScript + WASM query compiler | LINQ provider | TS-only |
| Driver model | Vyžaduje explicit driver adapter | Built-in providers | Vyžaduje driver |
| Performance overhead | Medium (WASM compile, JSON parse) | Medium (LINQ provider) | Minimální (closer to raw SQL) |
| Lazy loading | Ne (explicit include) | Ano (default v EF) | Ne (explicit JOIN) |
| Raw SQL escape | $queryRaw | FromSqlRaw | Snadné (je SQL-blízké) |
| Komunita | Velká, Next.js standard | Velká, .NET enterprise | Rostoucí |
| Kdy si vybrat | TS/JS projekt s rychlým prototypingem | .NET enterprise, komplexní entity | Edge runtime, serverless, výkon |
Praktický příklad stejného dotazu (najdi posty s autory):
tsx
// Prisma
const posts = await prisma.post.findMany({
where: { published: true },
include: { author: true },
orderBy: { createdAt: 'desc' }
});csharp
// EF Core (LINQ)
var posts = await db.Posts
.Where(p => p.Published)
.Include(p => p.Author)
.OrderByDescending(p => p.CreatedAt)
.ToListAsync();tsx
// Drizzle (SQL-like)
const posts = await db
.select()
.from(posts)
.leftJoin(authors, eq(posts.authorId, authors.id))
.where(eq(posts.published, true))
.orderBy(desc(posts.createdAt));Pro maturitu: Prisma. Pro reálné Next.js projekty: oboje (Prisma pro rychlý start, Drizzle pro výkon/edge runtime). EF Core pro ASP.NET.
Co se v Prismě 7 zásadně změnilo (oproti 6)
Tuhle sekci si zapamatuj, pokud bys narazil na starší tutorial.
| Co | Prisma 6 (staré) | Prisma 7 (nové) |
|---|---|---|
| Generator | provider = "prisma-client-js" | provider = "prisma-client" |
| Generated location | node_modules/@prisma/client | většinou app/generated/prisma/ (explicit) |
| Import path | from '@prisma/client' | from '../generated/prisma/client' |
| Connection URL | url = env(...) v schema.prisma | V novém prisma.config.ts |
| PrismaClient init | new PrismaClient() | new PrismaClient({ adapter }) |
| Driver | Built-in Rust engine | Driver adapter (@prisma/adapter-*) |
| Modules | CommonJS i ESM | ESM only ("type": "module") |
| Engine | Rust (binary) | WebAssembly + JavaScript |
| Automatic seed on migrate | Ano | Odstraněno |
| Client middleware | $use() | Odstraněno (použij Client Extensions) |
Důsledek: instalace má víc kroků, ale runtime je výkonnější (žádný Rust process, menší bundle, lepší pro edge runtime).
Prisma 7: instalace a setup
bash
# 1. Prisma CLI (vývojová závislost)
npm install prisma --save-dev
# 2. Prisma Client (runtime)
npm install @prisma/client
# 3. Driver adapter pro SQLite + samotný driver
npm install @prisma/adapter-better-sqlite3 better-sqlite3
# 4. dotenv pro načítání env variables
npm install dotenv
# 5. Inicializace
npx prisma initPo prisma init vzniknou:
prisma/
└── schema.prisma ← definice modelů
prisma.config.ts ← **NOVÁ** konfigurace (URL, schema path)
.env ← DATABASE_URLpackage.json musí mít ESM
json
{
"type": "module",
"scripts": {
"dev": "tsx src/index.ts",
"generate": "prisma generate",
"migrate": "prisma migrate dev"
}
}tsconfig.json pro ESM
json
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"target": "ES2023",
"strict": true,
"esModuleInterop": true
}
}prisma.config.ts: nová konfigurace
V Prismě 7 se URL přesunulo ze schema.prisma do TypeScript konfigurace.
tsx
// prisma.config.ts (v ROOT projektu, vedle package.json)
import "dotenv/config";
import { defineConfig } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: process.env["DATABASE_URL"],
},
});CLI (prisma migrate dev, prisma generate, prisma studio) čte URL odtud. Runtime kód (tvůj Next.js) ho dostává přes driver adapter (viz dál).
Schéma (prisma/schema.prisma)
// Generátor: NOVÝ provider "prisma-client" + explicit output
generator client {
provider = "prisma-client"
output = "../app/generated/prisma"
}
// Zdroj dat: BEZ url (to je v prisma.config.ts)
datasource db {
provider = "sqlite" // nebo "postgresql", "mysql", "sqlserver"
}
// Model = tabulka v DB
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
createdAt DateTime @default(now())
author Author @relation(fields: [authorId], references: [id])
authorId Int
}
model Author {
id Int @id @default(autoincrement())
name String
email String @unique
posts Post[]
}Klíčové změny v schema.prisma vs Prisma 6:
provider = "prisma-client"(neprisma-client-js)outputje povinný (generuje mimonode_modules)- Datasource block nemá
url(přesunuto doprisma.config.ts)
Typy sloupců
| Prisma typ | SQL ekvivalent | Poznámka |
|---|---|---|
Int | INTEGER | Celé číslo |
String | TEXT / VARCHAR | Řetězec |
Boolean | BOOLEAN | true/false |
DateTime | DATETIME | Datum a čas |
Float | REAL | Desetinné číslo |
Decimal | DECIMAL | Přesné decimální (pro peníze) |
Json | JSON | JSON sloupec |
Bytes | BLOB | Binární data |
typ? | NULL povoleno | Otazník = volitelné |
typ[] | (relace) | Pro 1:N relace |
Atributy (column-level)
| Atribut | Význam |
|---|---|
@id | Primární klíč |
@default(autoincrement()) | Auto-increment |
@default(now()) | Aktuální čas při vytvoření |
@default("hodnota") | Statická výchozí hodnota |
@unique | Unikátní hodnota |
@relation(...) | Definice cizího klíče |
@updatedAt | Auto-update na změnu |
@map("nazev_v_db") | Mapování na jiný název v DB |
Atributy (block-level)
model Post {
id Int @id @default(autoincrement())
slug String
lang String
@@unique([slug, lang]) // složený unique constraint
@@index([createdAt]) // index pro rychlejší dotazy
@@map("blog_posts") // jiné jméno tabulky v DB
}Typy relací
// 1:N (one-to-many)
model Author {
posts Post[]
}
model Post {
author Author @relation(fields: [authorId], references: [id])
authorId Int
}
// 1:1 (one-to-one)
model User {
profile Profile?
}
model Profile {
user User @relation(fields: [userId], references: [id])
userId Int @unique
}
// N:M (many-to-many): implicit
model Post {
tags Tag[]
}
model Tag {
posts Post[]
}Migrace
Migrace = aplikování změn schématu do skutečné databáze. Verzování databáze jako git verzuje kód.
bash
# Vytvoří migraci a aplikuje ji (development)
npx prisma migrate dev --name init
# Popisné jméno změny: init, add-user-table, add-published-fieldCo příkaz udělá:
- Načte
prisma.config.tspro URL - Porovná aktuální schéma s předchozím stavem (snapshot)
- Vygeneruje SQL soubor do
prisma/migrations/ - Aplikuje SQL na databázi
- Regeneruje Prisma Client do
app/generated/prisma/(aby TypeScript typy odpovídaly schématu)
prisma/
├── schema.prisma
└── migrations/
├── 20260513120000_init/
│ └── migration.sql ← CREATE TABLE ...
└── 20260514150000_add_published/
└── migration.sql ← ALTER TABLE ...Důležité migrace příkazy
bash
# Vytvoří + aplikuje migraci (dev only, interaktivní)
npx prisma migrate dev --name add-categories
# Aplikuje migrace v produkci (nepromptuje)
npx prisma migrate deploy
# Reset DB: smaže vše, znovu aplikuje všechny migrace
npx prisma migrate reset
# Jen vygeneruje Client bez migrace
npx prisma generate
# Push schématu do DB bez migrace (rychlý prototyping)
npx prisma db push
db pushvsmigrate dev:db pushneproduktuje migrace, jen aplikuje schema přímo. Hodí se pro rychlý prototyping. Pro produkci vždymigrate.
Důležité: v Prismě 7 už nedochází k automatickému seeding při
migrate dev. Pokud máš seed script, musíš ho spustit zvlášť (npx prisma db seednebotsx prisma/seed.ts).
Prisma Client v Prismě 7
Klíčová změna: PrismaClient se vytváří s driver adapterem.
tsx
// lib/prisma.ts
import { PrismaBetterSqlite3 } from "@prisma/adapter-better-sqlite3";
import { PrismaClient } from "@/app/generated/prisma/client";
const databaseUrl = process.env.DATABASE_URL ?? "file:./prisma/dev.db";
// V dev mode globální instance, aby hot-reload nezasypal connection pool
const globalForPrisma = globalThis as unknown as {
prisma?: PrismaClient;
};
function createPrismaClient() {
const adapter = new PrismaBetterSqlite3({ url: databaseUrl });
return new PrismaClient({ adapter });
}
export const prisma = globalForPrisma.prisma ?? createPrismaClient();
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}Adaptery pro různé DB
| DB | Adapter | Driver |
|---|---|---|
| SQLite (local) | @prisma/adapter-better-sqlite3 | better-sqlite3 |
| SQLite (Bun/edge) | @prisma/adapter-libsql | @libsql/client |
| PostgreSQL | @prisma/adapter-pg | pg |
| MySQL/MariaDB | @prisma/adapter-mariadb | mariadb |
| SQL Server | @prisma/adapter-mssql | mssql |
| Cloudflare D1 | @prisma/adapter-d1 | (built-in v Cloudflare) |
| PlanetScale | @prisma/adapter-planetscale | @planetscale/database |
Prisma Client: základní operace
Tahle část je stejná jako v Prismě 6, API operací se nezměnilo.
Čtení dat
tsx
// Najdi všechny posty
const posts = await prisma.post.findMany();
// Najdi konkrétní post podle id
const post = await prisma.post.findUnique({
where: { id: 1 },
});
// findUnique vrátí jeden záznam nebo null
// Najdi první match (víc je možné)
const first = await prisma.post.findFirst({
where: { published: true },
});
// Filtrování: jen publikované posty
const published = await prisma.post.findMany({
where: { published: true },
});
// Složitější filtry
const posts = await prisma.post.findMany({
where: {
published: true,
author: { name: "axo" }, // přes relaci
createdAt: { gte: new Date("2026-01-01") },
title: { contains: "Next" }, // SQL LIKE
},
});
// OR podmínka
const posts = await prisma.post.findMany({
where: {
OR: [{ title: { contains: "React" } }, { title: { contains: "Vue" } }],
},
});
// Řazení a stránkování
const posts = await prisma.post.findMany({
orderBy: { createdAt: "desc" },
take: 10, // LIMIT 10
skip: 20, // OFFSET 20 (3. stránka)
});Výběr sloupců a načítání relací
tsx
// SELECT: vyber jen některé sloupce
const posts = await prisma.post.findMany({
select: {
id: true,
title: true,
// content vynecháno, šetří se data
},
});
// INCLUDE: načti i relaci (JOIN)
const postsWithAuthor = await prisma.post.findMany({
include: {
author: true, // přidá celý Author objekt
},
});
// SELECT + INCLUDE kombinace (s SELECT na relaci)
const posts = await prisma.post.findMany({
select: {
id: true,
title: true,
author: {
select: { name: true, email: true },
},
},
});
// Hluboké relace
const author = await prisma.author.findUnique({
where: { id: 1 },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: "desc" },
take: 5,
},
},
});
selectvsinclude:selecturčuje co vrátit (limit fieldů).includepřidává relace navíc k defaultu (všechny scalar fields + tahle relace).
Vytvoření záznamu
tsx
const newPost = await prisma.post.create({
data: {
title: "Můj první post",
content: "Obsah článku",
author: {
connect: { id: 1 }, // připoj existujícího autora
},
},
});
// Nebo create autora + post najednou
const newPost = await prisma.post.create({
data: {
title: "První",
author: {
create: { name: "axo", email: "axo@example.com" },
},
},
include: { author: true },
});
// Batch insert
await prisma.post.createMany({
data: [
{ title: "A", authorId: 1 },
{ title: "B", authorId: 1 },
{ title: "C", authorId: 2 },
],
});Aktualizace záznamu
tsx
const updated = await prisma.post.update({
where: { id: 1 },
data: {
published: true,
title: "Nový název",
},
});
// Batch update
await prisma.post.updateMany({
where: { authorId: 1 },
data: { published: true },
});
// Upsert (update nebo create)
const post = await prisma.post.upsert({
where: { id: 1 },
update: { title: "Aktualizováno" },
create: { title: "Nový", authorId: 1 },
});Smazání záznamu
tsx
const deleted = await prisma.post.delete({
where: { id: 1 },
});
// Batch delete
await prisma.post.deleteMany({
where: { published: false },
});Agregace
tsx
const stats = await prisma.post.aggregate({
_count: { id: true },
where: { published: true },
});
const grouped = await prisma.post.groupBy({
by: ["authorId"],
_count: { id: true },
});Prisma Studio
Vizuální prohlížeč databáze.
bash
npx prisma studio
# Otevře prohlížeč na http://localhost:5555Můžeš procházet, přidávat, editovat záznamy bez SQL. Skvělý nástroj pro maturitu (ukaž zkoušejícímu data v Prisma Studiu, nepotřebuješ vědět žádné SQL).
.env soubor
V Prismě 7 se .env nenačítá automaticky. Musíš ho ručně loadovat:
bash
# .env
DATABASE_URL="file:./dev.db"tsx
// V kódu:
import "dotenv/config"; // PRVNÍ řádka, načte .envPro CLI příkazy (prisma migrate dev, atd.) Prisma 7 načte .env automaticky přes prisma.config.ts (env('DATABASE_URL')).
Soubor
.envpřidej do.gitignore, obsahuje citlivé údaje. Místo něj se commitne.env.examplese vzorem.
Celý workflow od začátku (Prisma 7)
bash
# 1. Setup projektu
npm init -y
npm install prisma --save-dev
npm install @prisma/client
npm install @prisma/adapter-better-sqlite3 better-sqlite3
npm install dotenv
# 2. ESM v package.json: přidej "type": "module"
# 3. Inicializace Prismy
npx prisma init
# 4. Uprav .env:
# DATABASE_URL="file:./dev.db"
# 5. Vytvoř prisma.config.ts (viz výše)
# 6. Definuj modely v prisma/schema.prisma
# 7. Vytvoř a aplikuj migraci
npx prisma migrate dev --name init
# 8. (Volitelně) zkontroluj data
npx prisma studio
# 9. Vytvoř lib/prisma.ts s adapterem (viz výše)
# 10. Použij v kódu
# import prisma from '@/lib/prisma';
# const posts = await prisma.post.findMany();Krátká zmínka o Entity Framework Core
Pro úplnost, kdyby zkoušející chtěl porovnání.
EF Core funguje podobně, jen v C#:
csharp
// Definice entit (modely)
public class Post {
public int Id { get; set; }
public string Title { get; set; } = "";
public Author Author { get; set; } = null!;
public int AuthorId { get; set; }
}
// DbContext (jako Prisma Client)
public class AppDbContext : DbContext {
public DbSet<Post> Posts => Set<Post>();
public DbSet<Author> Authors => Set<Author>();
}
// Použití
var posts = await db.Posts
.Where(p => p.Published)
.Include(p => p.Author)
.OrderByDescending(p => p.CreatedAt)
.ToListAsync();Migrace v EF Core
bash
dotnet ef migrations add InitialCreate
dotnet ef database updatenebo ve VS built-in CLI:
powershell
Add-Migration InitialCreate
Update-DatabaseČasté chyby (Prisma 7)
| Chyba | Důsledek | Řešení |
|---|---|---|
Import z '@prisma/client' | Cannot find module | Import z '../generated/prisma/client' |
new PrismaClient() bez adapteru | Runtime error | new PrismaClient({ adapter }) |
Zapomenutý import "dotenv/config" | process.env.DATABASE_URL je undefined | Přidat na 1. řádku |
url v schema.prisma | Validation error v Prismě 7 | Přesunout do prisma.config.ts |
package.json bez "type": "module" | ESM import nefunguje | Přidat "type": "module" |
Vytváření new PrismaClient() v každém API | Connection pool overflow | Sdílená instance v lib/prisma.ts |
| Editovat schema bez migrace | DB se neupdatuje | Vždy npx prisma migrate dev |
findUnique vrací null bez kontroly | Crash při .title na null | Vždy zkontrolovat if (!post) return ... |
.env v gitu | Bezpečnostní leak | Přidat do .gitignore, použít .env.example |
find v cyklu místo include | N+1 query, pomalé | Použít include |
Forgotten await | Promise místo dat | Vždy await u Prisma operací |
Použití db push v produkci | Žádná verzovatelnost změn | V produkci migrate deploy |
Část 2: Praktická úloha
Co může praktická úloha obsahovat
Typická úloha:
- Definice schématu v
prisma/schema.prisma(2-3 modely s relacemi) prisma.config.tss URL z env- Migrace databáze (
npx prisma migrate dev --name init) lib/prisma.tss driver adapterem a sdílenou instancí- API routes pro CRUD: GET (list), GET (detail), POST (create), DELETE (smazat)
includepro JOIN s relacíorderBy,take,skippro řazení a stránkování- SSR stránka v Next.js, která čte data přímo přes Prisma (bez fetch z vlastního API)
- Bonus: seed script s testovacími daty
Příklad zadání: Blog s Prisma 7 + Next.js
Vytvoř Next.js aplikaci s Prisma 7 pro správu blogu. Aplikace bude mít:
- Schéma s
PostaAuthor(relace 1:N) - Migraci
- API routes pro CRUD
- Stránku zobrazující data
Řešení
Setup
bash
npx create-next-app@latest blog --typescript --app
cd blog
npm install prisma --save-dev
npm install @prisma/client
npm install @prisma/adapter-better-sqlite3 better-sqlite3
npm install dotenv
npx prisma init --datasource-provider sqlite --output ../generated/prismapackage.json (relevantní část)
json
{
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}.env
bash
DATABASE_URL="file:./dev.db"prisma.config.ts (v rootu)
tsx
import "dotenv/config";
import { defineConfig } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: process.env["DATABASE_URL"],
},
});Schéma: prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../app/generated/prisma"
}
datasource db {
provider = "sqlite"
}
model Author {
id Int @id @default(autoincrement())
name String
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
createdAt DateTime @default(now())
author Author @relation(fields: [authorId], references: [id])
authorId Int
@@index([createdAt]) // index pro rychlejší řazení
}Migrace
bash
npx prisma migrate dev --name initTo vytvoří:
dev.db(SQLite soubor)prisma/migrations/.../migration.sqlapp/generated/prisma/(Prisma Client)
Prisma Client: lib/prisma.ts
tsx
// lib/prisma.ts
import { PrismaBetterSqlite3 } from "@prisma/adapter-better-sqlite3";
import { PrismaClient } from "@/app/generated/prisma/client";
const databaseUrl = process.env.DATABASE_URL ?? "file:./prisma/dev.db";
// V dev mode globální instance, aby hot-reload nezasypal connection pool
const globalForPrisma = globalThis as unknown as {
prisma?: PrismaClient;
};
function createPrismaClient() {
const adapter = new PrismaBetterSqlite3({ url: databaseUrl });
return new PrismaClient({ adapter });
}
export const prisma = globalForPrisma.prisma ?? createPrismaClient();
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}API: seznam + vytvoření (app/api/posts/route.ts)
tsx
import prisma from '@/lib/prisma';
import { NextResponse } from 'next/server';
// GET /api/posts
export async function GET() {
const posts = await prisma.post.findMany({
include: { author: true },
orderBy: { createdAt: 'desc' }
});
return NextResponse.json(posts);
}
// POST /api/posts
// Body: { title, content?, authorId }
export async function POST(req: Request) {
try {
const data = await req.json();
// Validace
if (!data.title || !data.authorId) {
return NextResponse.json(
{ error: 'title a authorId jsou povinné' },
{ status: 400 }
);
}
const newPost = await prisma.post.create({
data: {
title: data.title,
content: data.content ?? null,
author: { connect: { id: data.authorId } }
},
include: { author: true }
});
return NextResponse.json(newPost, { status: 201 });
} catch (err) {
return NextResponse.json(
{ error: 'Něco se pokazilo' },
{ status: 500 }
);
}
}API: detail + smazání (app/api/posts/[id]/route.ts)
tsx
import prisma from '@/lib/prisma';
import { NextResponse } from 'next/server';
// GET /api/posts/[id]
export async function GET(
req: Request,
{ params }: { params: { id: string } }
) {
const id = Number(params.id);
const post = await prisma.post.findUnique({
where: { id },
include: { author: true }
});
if (!post) {
return NextResponse.json({ error: 'Not Found' }, { status: 404 });
}
return NextResponse.json(post);
}
// DELETE /api/posts/[id]
export async function DELETE(
req: Request,
{ params }: { params: { id: string } }
) {
const id = Number(params.id);
try {
await prisma.post.delete({ where: { id } });
return new NextResponse(null, { status: 204 });
} catch {
return NextResponse.json({ error: 'Not Found' }, { status: 404 });
}
}Stránka se seznamem (app/page.tsx)
tsx
import prisma from '@/lib/prisma';
import Link from 'next/link';
// Server Component (SSR): čte data přímo přes Prisma, bez fetch
export default async function HomePage() {
const posts = await prisma.post.findMany({
include: { author: true },
orderBy: { createdAt: 'desc' },
take: 20
});
return (
<main style={{ maxWidth: 800, margin: '2rem auto', padding: '1rem' }}>
<h1>Blog</h1>
{posts.length === 0 ? (
<p>Zatím žádné posty.</p>
) : (
<ul>
{posts.map(post => (
<li key={post.id}>
<Link href={`/posts/${post.id}`}>
<strong>{post.title}</strong>
</Link>
<p>
{post.author.name} ·{' '}
{new Date(post.createdAt).toLocaleDateString('cs-CZ')}
</p>
{post.content && <p>{post.content.substring(0, 150)}...</p>}
</li>
))}
</ul>
)}
</main>
);
}Bonus: seed script
V Prismě 7 už automaticky neběží při migrate. Musíš ho spustit ručně.
tsx
// prisma/seed.ts
import "dotenv/config";
import { PrismaBetterSqlite3 } from "@prisma/adapter-better-sqlite3";
import { PrismaClient } from "../src/generated/prisma/client";
const adapter = new PrismaBetterSqlite3({
url: process.env.DATABASE_URL ?? "file:./dev.db"
});
const prisma = new PrismaClient({ adapter });
async function main() {
// Smaž existující data (pro idempotenci)
await prisma.post.deleteMany();
await prisma.author.deleteMany();
// Vytvoř autora
const axo = await prisma.author.create({
data: { name: 'axo', email: 'axo@example.com' }
});
const martin = await prisma.author.create({
data: { name: 'martin', email: 'martin@example.com' }
});
// Vytvoř posty
await prisma.post.createMany({
data: [
{ title: 'První post', content: 'Obsah...', published: true, authorId: axo.id },
{ title: 'Next.js základy', content: 'App Router...', published: true, authorId: axo.id },
{ title: 'Draft', content: null, published: false, authorId: martin.id }
]
});
console.log('Seed hotov');
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());bash
# Spuštění
npx tsx prisma/seed.tsCo se v řešení děje
Setup: Instalace Prismy + driver adapter pro SQLite + dotenv. package.json musí mít "type": "module" kvůli ESM. prisma init vytvoří schema.prisma + prisma.config.ts.
prisma.config.ts: Centrální konfigurace, kterou používá CLI (migrate dev, studio...). Z .env čte DATABASE_URL. Tahle separace umožňuje různé URL pro migrace vs runtime.
Schéma: Dva modely s 1:N relací. Generator má provider = "prisma-client" (Prisma 7) a explicit output. Datasource block nemá url (je v prisma.config.ts). @@index([createdAt]) zrychluje řazení podle data.
Migrace: prisma migrate dev načte URL z prisma.config.ts, porovná schéma s předchozím stavem, vygeneruje SQL a aplikuje ho. Také regeneruje Prisma Client do src/generated/prisma/.
lib/prisma.ts: Sdílená instance Prisma Clienta. Klíčové změny v Prismě 7:
import "dotenv/config"na 1. řádce, jinakprocess.env.DATABASE_URLje undefined- Import z
'../app/generated/prisma/client', ne z'@prisma/client' - Vytvoření adapteru
PrismaBetterSqlite3s URL new PrismaClient({ adapter })s tím adapterem
GET /api/posts: findMany s include: { author: true } udělá JOIN. orderBy seřadí podle data sestupně.
POST /api/posts: Přijme JSON tělo, validuje povinná pole. create s author.connect propojí na existujícího autora.
GET /api/posts/[id]: Dynamická route, params.id jako string, převedeno na number. findUnique vrátí null pokud neexistuje.
DELETE /api/posts/[id]: delete v try-catch (Prisma vyhodí chybu pokud záznam neexistuje). Vrátí 204 No Content.
Stránka jako Server Component: Čte z DB přímo přes Prisma (žádný fetch). Tohle je obrovská výhoda Next.js: API route ani neexistuje pro tuhle stránku, je to přímé volání DB ze serveru.
Část 3: Tipy pro obhajobu
Co u obhajoby říct
"V zadání jsem vytvořil Next.js aplikaci s Prismou 7. V schema.prisma jsem definoval modely Author a Post s 1:N relací: Post má authorId jako cizí klíč a navigation property author, Author má kolekci posts. Použil jsem code-first přístup: schéma je zdrojová pravda, migrace ho převede na SQL. Důležité je, že Prisma 7 má URL v novém prisma.config.ts, ne v schema.prisma. Po prisma migrate dev se vytvořil SQLite soubor s tabulkami a generated Prisma Client v src/generated/prisma. Pro Prisma Client jsem vytvořil sdílenou instanci v lib/prisma.ts. Klíčová věc Prismy 7: PrismaClient potřebuje driver adapter, pro SQLite je to PrismaBetterSqlite3 z @prisma/adapter-better-sqlite3. Také musíš ručně importovat dotenv/config, protože env variables se v Prismě 7 nenačítají automaticky. API routes používají findMany s include pro JOIN, findUnique pro detail s null-checkem, create s connect pro propojení autora. Hlavní stránka je Server Component, který čte z DB přímo přes Prisma, bez vlastní fetch na API."
Klíčové pojmy pro teorii
| Pojem | Rychlá odpověď |
|---|---|
| ORM | Object-Relational Mapper, vrstva mezi kódem a DB |
| Code-first | Definuji schéma v kódu, migrace vygeneruje DB |
| DB-first | DB existuje, generuji kód z ní |
| Prisma 7 | TypeScript ORM s deklarativním schématem, vyžaduje driver adapter |
schema.prisma | Soubor s definicí modelů |
prisma.config.ts | NOVÉ v Prismě 7: konfigurace pro CLI (URL, schema path) |
| Driver adapter | NOVÉ v Prismě 7: most mezi Prisma Client a DB driverem |
@prisma/adapter-better-sqlite3 | Adapter pro SQLite |
@prisma/adapter-pg | Adapter pro PostgreSQL |
| Prisma Client | Vygenerovaný typed API pro práci s DB (v Prismě 7 v src/generated/prisma/) |
| Migrace | Verzování změn DB schématu (jako git) |
migrate dev | Vytvoří + aplikuje migraci (development) |
migrate deploy | Aplikuje migrace v produkci (bez interakce) |
db push | Aplikuje schema bez migrace (prototyping) |
findMany | Vrátí pole záznamů |
findUnique | Vrátí jeden záznam nebo null |
include | Načte i relaci (JOIN) |
select | Vyber jen některé sloupce |
@id, @unique, @default | Atributy v Prisma schématu |
@relation(...) | Definice cizího klíče |
| Prisma Studio | GUI pro procházení DB |
DATABASE_URL | Připojovací řetězec v .env |
| N+1 problem | Cyklus s dotazem v každé iteraci, řeší include |
| EF Core | C# ekvivalent Prismy, LINQ-based |
| Drizzle | Modernější TypeScript ORM, SQL-blízký |
Časté chytáky
| Otázka | Odpověď |
|---|---|
| Co je ORM? | Vrstva mezi kódem a databází. Píšeš metody a objekty, ORM je přeloží na SQL. |
| Proč ORM místo SQL? | Typová bezpečnost, přenositelnost mezi DB, automatické migrace, méně chyb, lepší refactoring. |
| Co je code-first? | Definuji strukturu DB v kódu (schéma), migrace ji vygeneruje. Opak je db-first (DB existuje, generuji modely z ní). |
| Co dělá migrace? | Porovná aktuální schéma s předchozím a vygeneruje SQL (CREATE TABLE, ALTER TABLE) pro tu změnu. Aplikuje ji na DB. |
Co je prisma.config.ts? | Nový konfigurační soubor v Prismě 7. Obsahuje URL k DB a cestu ke schématu. CLI z něj čte při migrate, generate, studio. |
| Co je driver adapter? | Most mezi Prisma Client a JS database driverem. V Prismě 7 povinný. Příklady: @prisma/adapter-better-sqlite3, @prisma/adapter-pg. |
| Proč v Prismě 7 driver adapter? | Prisma 7 odstranila Rust engine, klient běží čistě v JS/WASM. Driver adapter zajišťuje skutečné připojení k DB. |
| Rozdíl findMany a findUnique? | findMany vrátí vždy pole (může být prázdné). findUnique vrátí jeden záznam nebo null. |
| Rozdíl include a select? | Include přidá relaci k defaultu (všechny scalar fields + relace). Select určuje, co se vrátí (jen vyjmenované). |
| Proč sdílená instance Prisma Client? | Aby se v dev modu (s hot reload) nevytvářely nové instance a nezaplňovaly connection pool. |
| Co je Prisma Studio? | GUI prohlížeč DB v prohlížeči, můžeš procházet/editovat data bez SQL. |
| Proč DATABASE_URL v .env? | Citlivá data nepatří do kódu. .env je v .gitignore. Plus snadné přepínání mezi dev/prod. |
Co dělá prisma generate? | Vygeneruje TypeScript Client podle aktuálního schématu do src/generated/prisma/. |
| Co je N+1 problém? | Cyklus, kde voláš dotaz v každé iteraci. Místo 1 dotazu s JOIN máš 1+N dotazů. Řešení: include. |
Časté chyby v praktické úloze
- Import z
'@prisma/client'(Prisma 7 generuje dosrc/generated/prisma/) new PrismaClient()bez adapteru (runtime crash)- Zapomenutý
import "dotenv/config"(env je undefined) urlponechané vschema.prisma(validation error)package.jsonbez"type": "module"(ESM nefunguje)- Vytváření
new PrismaClient()v každém API route (connection pool overflow) - Změna
schema.prismabezprisma migrate dev(DB neaktualizovaná) findUniquebez null check (crash při.titlena null)deletebez try-catch (crash při neexistujícím ID)includebez potřeby (over-fetching, pomalé)findv cyklu místoincludenebowhere: { in: [...] }(N+1)- Hardcoded
DATABASE_URLv kódu (citlivá data v gitu) .envv gitu (security leak)- Chybějící
awaitu Prisma operací (vrátí Promise místo dat) createManys relacemi (nepodporuje connect)- POST endpoint bez validace vstupu (zápis garbage do DB)
- Použití
db pushv produkci (žádné verzování změn)