Урок 05

Работа с БД

Server Components могут напрямую работать с базой данных.

Prisma

Самый популярный ORM для Next.js.

npm install prisma @prisma/client
npx prisma init

schema.prisma:

model Post {
    id        Int      @id @default(autoincrement())
    title     String
    body      String
    published Boolean  @default(false)
    createdAt DateTime @default(now())
}
npx prisma migrate dev
npx prisma generate

Prisma client singleton

lib/db.ts:

import { PrismaClient } from '@prisma/client';

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };

export const db = globalForPrisma.prisma ?? new PrismaClient();

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db;

Без singleton — много подключений в dev.

Использование

import { db } from '@/lib/db';

export default async function Posts() {
    const posts = await db.post.findMany({
        where: { published: true },
        orderBy: { createdAt: 'desc' },
        take: 10,
    });

    return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
}

CRUD

// Create
const post = await db.post.create({ data: { title: 'Новый' } });

// Read
const post = await db.post.findUnique({ where: { id: 1 } });
const posts = await db.post.findMany();

// Update
await db.post.update({ where: { id: 1 }, data: { title: 'Обновлён' } });

// Delete
await db.post.delete({ where: { id: 1 } });

Связи

model User {
    id    Int    @id @default(autoincrement())
    name  String
    posts Post[]
}

model Post {
    id       Int    @id @default(autoincrement())
    title    String
    author   User   @relation(fields: [authorId], references: [id])
    authorId Int
}

Запрос со связью:

const posts = await db.post.findMany({
    include: { author: true },
});

const user = await db.user.findUnique({
    where: { id: 1 },
    include: { posts: true },
});

Транзакции

await db.$transaction([
    db.post.create({ data: { title: 'A' } }),
    db.post.create({ data: { title: 'B' } }),
]);

Или интерактивно:

await db.$transaction(async (tx) => {
    const post = await tx.post.create({ data: { title: 'A' } });
    await tx.comment.create({ data: { postId: post.id, text: 'B' } });
});

Drizzle

Лёгкая альтернатива. Меньше магии, больше контроля.

npm install drizzle-orm postgres
npm install -D drizzle-kit

schema.ts:

import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';

export const posts = pgTable('posts', {
    id: serial('id').primaryKey(),
    title: text('title').notNull(),
    body: text('body').notNull(),
    published: boolean('published').default(false),
    createdAt: timestamp('created_at').defaultNow(),
});

lib/db.ts:

import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';

const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client, { schema });

Запрос:

const allPosts = await db.select().from(posts);

await db.insert(posts).values({ title: 'Новый', body: 'Текст' });

Prisma vs Drizzle

  • Prisma — больше магии, свой DSL, генерация типов, миграции из коробки
  • Drizzle — ближе к SQL, лёгкий, типы выводятся из схемы

Оба подходят. Prisma — если хочешь быстрый старт. Drizzle — если хочешь контроль.

Server Components

Прямой доступ к БД — одна из главных фишек. Не нужен API-слой.

export default async function Users() {
    const users = await db.user.findMany();
    return <UserList users={users} />;
}

Server Actions для мутаций

'use server';

export async function createPost(data: FormData) {
    await db.post.create({ data: { title: data.get('title') } });
    revalidatePath('/posts');
}

Итоги

  • Prisma и Drizzle — популярные ORM
  • Singleton для клиента
  • Прямой доступ в Server Components
  • Server Actions для мутаций
  • Транзакции для согласованности
  • Связи через include или join