JS Tower
Node.jsМодуль 5: Базы данных

PostgreSQL и Prisma

Работа с PostgreSQL через Prisma ORM

Цель урока

В этом уроке ты научишься:

  • Настраивать Prisma
  • Создавать схемы и миграции
  • Выполнять типизированные запросы

Установка

npm install prisma @prisma/client
npx prisma init

Схема Prisma

// prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  password  String
  role      Role     @default(USER)
  posts     Post[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  tags      Tag[]
  createdAt DateTime @default(now())
}

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

enum Role {
  USER
  ADMIN
}

Миграции

# Создать миграцию
npx prisma migrate dev --name init

# Применить миграции в production
npx prisma migrate deploy

# Сбросить базу данных
npx prisma migrate reset

# Синхронизация без миграции (dev)
npx prisma db push

Подключение

// lib/prisma.js
const { PrismaClient } = require('@prisma/client');

const prisma = new PrismaClient({
  log: ['query', 'info', 'warn', 'error']
});

module.exports = prisma;

CRUD операции

Create

const prisma = require('./lib/prisma');

// Создание
const user = await prisma.user.create({
  data: {
    email: 'ivan@example.com',
    name: 'Иван',
    password: 'hashedpassword'
  }
});

// С связями
const post = await prisma.post.create({
  data: {
    title: 'Мой пост',
    content: 'Контент',
    author: {
      connect: { id: 1 }
    },
    tags: {
      connectOrCreate: [
        { where: { name: 'JavaScript' }, create: { name: 'JavaScript' } }
      ]
    }
  }
});

// Множественное создание
await prisma.user.createMany({
  data: [
    { email: 'a@example.com', name: 'A' },
    { email: 'b@example.com', name: 'B' }
  ]
});

Read

// Все записи
const users = await prisma.user.findMany();

// С фильтром
const users = await prisma.user.findMany({
  where: {
    role: 'ADMIN',
    email: { contains: 'example.com' }
  }
});

// Одна запись
const user = await prisma.user.findUnique({
  where: { id: 1 }
});

const user = await prisma.user.findFirst({
  where: { email: { contains: 'ivan' } }
});

// С связями
const user = await prisma.user.findUnique({
  where: { id: 1 },
  include: {
    posts: true
  }
});

// Выбор полей
const users = await prisma.user.findMany({
  select: {
    id: true,
    name: true,
    email: true
  }
});

// Сортировка и пагинация
const posts = await prisma.post.findMany({
  orderBy: { createdAt: 'desc' },
  skip: 10,
  take: 10
});

Update

// Обновление
const user = await prisma.user.update({
  where: { id: 1 },
  data: { name: 'Новое имя' }
});

// Множественное обновление
await prisma.user.updateMany({
  where: { role: 'USER' },
  data: { role: 'ADMIN' }
});

// Upsert
const user = await prisma.user.upsert({
  where: { email: 'ivan@example.com' },
  update: { name: 'Иван' },
  create: { email: 'ivan@example.com', name: 'Иван' }
});

Delete

// Удаление
await prisma.user.delete({
  where: { id: 1 }
});

// Множественное удаление
await prisma.user.deleteMany({
  where: { role: 'INACTIVE' }
});

Фильтры

const users = await prisma.user.findMany({
  where: {
    // Равенство
    role: 'ADMIN',
    
    // Сравнение
    id: { gt: 10 },      // >
    id: { gte: 10 },     // >=
    id: { lt: 100 },     // <
    id: { lte: 100 },    // <=
    
    // Строки
    email: { contains: 'example' },
    email: { startsWith: 'ivan' },
    email: { endsWith: '.com' },
    
    // Логические
    AND: [{ role: 'USER' }, { name: { not: null } }],
    OR: [{ role: 'ADMIN' }, { role: 'MODERATOR' }],
    NOT: { email: { contains: 'test' } },
    
    // Связи
    posts: {
      some: { published: true }
    }
  }
});

Транзакции

// Автоматическая транзакция
const [user, post] = await prisma.$transaction([
  prisma.user.create({ data: { email: 'a@example.com' } }),
  prisma.post.create({ data: { title: 'Пост', authorId: 1 } })
]);

// Интерактивная транзакция
await prisma.$transaction(async (tx) => {
  const user = await tx.user.create({
    data: { email: 'ivan@example.com' }
  });
  
  await tx.post.create({
    data: { title: 'Пост', authorId: user.id }
  });
});

Практика

Задание: API с Prisma

Задача: Создай CRUD для пользователей.

Решение:

const express = require('express');
const prisma = require('./lib/prisma');

const app = express();
app.use(express.json());

app.get('/users', async (req, res) => {
  const users = await prisma.user.findMany({
    select: { id: true, name: true, email: true }
  });
  res.json(users);
});

app.post('/users', async (req, res) => {
  const user = await prisma.user.create({
    data: req.body
  });
  res.status(201).json(user);
});

app.put('/users/:id', async (req, res) => {
  const user = await prisma.user.update({
    where: { id: parseInt(req.params.id) },
    data: req.body
  });
  res.json(user);
});

app.delete('/users/:id', async (req, res) => {
  await prisma.user.delete({
    where: { id: parseInt(req.params.id) }
  });
  res.status(204).send();
});

Проверь себя

  1. Как создать миграцию?
  2. Что делает include?
  3. Как выполнить транзакцию?