Главная / Статьи

Prisma без Prisma Client: типобезопасные SQL-запросы в Next.js

Узнайте, как использовать Prisma для работы со схемами, генерировать типы Kysely с помощью prisma-kysely, выбирать диалект Neon или pg, а также выполнять запросы с обеспечением типобезопасности из серверного кода Next.js.

Издатель: REACTAPP.TOP6 мин чтения1411 слов

Источники: **Using Prisma for Schema and Kysely for Queries in a Next.js App**

Prisma отлично подходит для проектирования схем, но Prisma Client представляет собой довольно большую зависимость, которую необходимо включать в каждую функцию без сервера, хотя всё, что нужно, — это SQL с типобезопасностью. Практическое решение: Prisma управляет схемой во время разработки, генератор преобразует её в типы TypeScript, а Kysely выполняет запросы в производственной среде. В этом руководстве описывается настройка такой схемы в проекте Next.js App Router, а также сравнивается диалект Neon HTTP с классическим пулом pg.

Если вы предпочитаете традиционный подход с использованием Prisma Client во время выполнения, ознакомьтесь с настройкой Prisma 7 в проекте Next.js 16.

Как распределяются обязанности

У каждого инструмента в этой конфигурации есть одна чётко определённая задача:

  • Prisma отвечает за проектирование базы данных: файл схемы, синхронизацию или миграцию базы данных, а также инструменты вроде Prisma Studio для просмотра данных.
  • Kysely — это инструмент для формирования запросов во время выполнения. Вы пишете запросы, похожие на SQL, на TypeScript; компилятор проверяет имена таблиц и столбцов, а сама библиотека имеет небольшие размеры.
  • prisma-kysely служит связующим звеном: это генератор Prisma, который создает определения типов Kysely непосредственно на основе вашей схемы Prisma.

Prisma остаётся частью вашего рабочего процесса разработчика, в то время как Kysely является единственным слоем базы данных в развернутом коде. Компромисс заключается в том, что вы теряете API Prisma Client с поддержкой отношений, такое как вложенные операции include и вложенные записи, а также возможность самостоятельно выполнять объединение записей. Если вы всё ещё выбираете слой для работы с данными, сравнение raw SQL, Prisma и Drizzle поможет принять более полное решение.

Установка пакетов

Для работы программы требуются Kysely вместе с диалектом и драйвером; это устанавливает стек на основе Neon (здесь используется pnpm, но подойдут также npm или yarn):

pnpm add kysely kysely-neon @neondatabase/serverless
pnpm add -D prisma prisma-kysely

Обратите внимание, что prisma и prisma-kysely являются зависимостями разработки: они отвечают только за управление схемой и генерацию кода.

Определение схемы и генерация типов Kysely

Схема является обычной Prisma, за исключением того, что генератор — prisma-kysely (запись в ./types), а не стандартный клиентский генератор. Он определяет типы User и Post с отношением один-ко-многим.

// db/schema.prisma

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

generator kysely {
  provider = "prisma-kysely"
  output   = "./types"
}

model User {
  id    Int    @id @default(autoincrement())
  name  String
  email String  @unique
  posts Post[]
}
model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  authorId  Int
  createdAt DateTime @default(now())
  author    User     @relation(fields: [authorId], references: [id])
}

prisma generate теперь генерирует типы Kysely вместо клиента, а prisma db push применяет схему к базе данных:

pnpm prisma generate
pnpm prisma db push

Результатом является файл вроде db/types/kysely.d.ts с интерфейсом DB, описывающим каждую таблицу. Есть три важных момента:

  • Схема находится в db/schema.prisma, а не в стандартном для Prisma prisma/schema.prisma; поэтому необходимо указать CLI на этот файл (например, с помощью --schema) в соответствии с требованиями вашей версии Prisma.
  • db push синхронизирует базу данных без сохранения файлов миграций. Для производственных данных команды prisma migrate dev и prisma migrate deploy генерируют версионированные миграции, не меняя остальную часть рабочего процесса.
  • В новых версиях Prisma настройки подключения перемещаются в отдельный файл конфигурации; если строка url отклоняется, проверьте актуальную документацию.
  • Скрипты пакета для повседневной работы

    Несколько скриптов делают рабочий процесс понятным для команды:

    {
      "scripts": {
        "db:push": "prisma db push",
        "db:generate": "prisma generate",
        "db:studio": "prisma studio"
      }
    }
    

    С их наличием изменение схемы сводится к двум командам:

    # To generate the types
    pnpm db:generate
    # To push to the remote DB
    pnpm db:push
    

    Таким образом база данных и типы Kysely остаются согласованными, поэтому компилятор выдает предупреждения при использовании запросов, касающихся переименованных или удалённых столбцов.

    Выбор диалекта Kysely

    Kysely не взаимодействует непосредственно с базой данных. Между конструктором запросов и драйвером находится диалект, который знает, как компилировать и отправлять запросы для определенной базы данных и способа подключения. Официальные диалекты поддерживают PostgreSQL, MySQL и SQLite; сообщественные диалекты — такие сервисы, как Neon, PlanetScale и Turso. Для Postgres существует два варианта, которые покрывают большинство случаев.

    Neon через HTTP для безсерверных развертываний

    На Vercel или подобной платформе HTTP-драйвер Neon отлично подходит: каждый запрос является безсостоянийным, поэтому при кратковременных вызовах не требуется поддерживать постоянное соединение. Настройка заключается в оборачивании функции neon() объектом NeonDialect и указании типа Kysely с использованием генерированного типа DB:

    // db/index.ts
    import { Kysely } from 'kysely'
    import { NeonDialect } from 'kysely-neon'
    import { neon } from '@neondatabase/serverless'
    import type { DB } from './types/kysely'
    
    const db = new Kysely<DB>({
      dialect: new NeonDialect({
        neon: neon(process.env.DATABASE_URL!),
      }),
    })
    
    export default db
    

    Символ ! лишь отключает проверки в TypeScript; он ничего не верифицирует. Более серьезным ограничением является то, что kysely-neon через HTTP не может выполнять транзакции или запросы, основанные на сессиях, поэтому он подходит скорее для простых операций CRUD и страниц с интенсивным чтением, чем для задач, требующих выполнения нескольких операций. С помощью драйвера WebSocket от Neon вам совсем не нужен kysely-neon: достаточно передать пул соединений от Neon в встроенный PostgresDialect от Kysely. API kysely-neon также менялся между версиями, поэтому сравнивайте его с текущим README.

    node-postgres для серверов с длительной работой

    Для сервера Node.js, работающего длительное время, официальный драйвер pg является наиболее полным решением, предоставляющим поддержку транзакций, пулов соединений и заранее подготовленных запросов. Он интегрируется с собственным PostgresDialect от Kysely:

    // db/index.ts
    import { Kysely, PostgresDialect } from 'kysely'
    import { Pool } from 'pg'
    import type { DB } from './types/kysely'
    
    const db = new Kysely<DB>({
      dialect: new PostgresDialect({
        pool: new Pool({
          connectionString: process.env.DATABASE_URL,
        }),
      }),
    })
    
    export default db
    

    Выбор между ними

    Разница заключается в модели подключения. kysely-neon — это безсостоянийный HTTP: легкий и подходящий для серверов без управления, но без возможности транзакций. pg поддерживает открытыми пулы TCP-соединений с полным набором функций, что подходит для серверов, работающих постоянно. Используйте kysely-neon на платформах без управления серверами, таких как Vercel или Netlify, а pg или пул WebSocket Neon — когда вам понадобятся транзакции. Остерегайтесь использования пула pg внутри функций без управления серверами: каждая инстанция открывает собственные соединения, что может привести к исчерпанию лимитов базы данных без внешнего пулера.

    Запросы из компонента сервера

    Любой код с серверной стороны может импортировать модуль db. Этот компонент сервера выводит список пользователей, отбирая три столбца в порядке увеличения идентификатора:

    // app/users/page.tsx
    import db from '@/db'
    
    export default async function UsersPage() {
      const users = await db
        .selectFrom('user')
        .select(['id', 'name', 'email'])
        .orderBy('id')
        .execute()
    
      return (
        <div>
          <h1>Users</h1>
          <ul>
            {users.map(u => (
              <li key={u.id}>{u.name} ({u.email})</li>
            ))}
          </ul>
        </div>
      )
    }
    

    Поскольку DB берется из схемы Prisma, ошибка в написании имени таблицы или столбца приводит к сбою проверки типов, и для поля users автоматически определяются только выбранные поля.

    Есть один нюанс: Prisma по умолчанию называет таблицы в соответствии с моделями, поэтому эта схема создает таблицу User, и генерируемые типы используют именно это название. Функция selectFrom('user') выполняет проверку типов только в том случае, если к модели добавлено @@map("user"); в противном случае используется запрос к таблице 'User'. Также не следует использовать db в клиентских компонентах, поскольку он загружает секретные данные, доступные только на сервере.

    Ежедневный процесс работы

    Как только всё настроено, цикл выглядит следующим образом:

    1. Изменяются модели в файле db/schema.prisma.
    2. Генерируются заново типы, после чего изменения применяются с помощью двух скриптов.
    3. db используется в серверных компонентах, обработчиках маршрутов или серверных действиях.
  • Проверяйте входные данные с помощью Zod или аналогичных инструментов перед вставкой; типы Kysely проверяют структуру запроса, а не значения форм.
  • В результате всё, что связано с базой данных, находится внутри папки db/:

    📦 my-next-app
    ├── 📁 app
    │   └── 📁 users
    │       └── page.tsx
    ├── 📁 db
    │   ├── schema.prisma
    │   ├── index.ts
    │   └── 📁 types
    │       └── kysely.d.ts
    ├── 📁 node_modules
    ├── .env
    ├── package.json
    ├── pnpm-lock.yaml
    └── tsconfig.json
    

    Итоги

    Такое разделение позволяет сохранить рабочий процесс схемы Prisma и интерфейс Studio без включения Prisma Client, а также предоставляет небольшой слой запросов в формате SQL, типы которого отслеживают структуру схемы.

    • Сохраняйте prisma и prisma-kysely как зависимости для разработки; в продакшен отправляются только Kysely, диалект и драйвер.
    • Для простых безсерверных задач используйте диалект Neon HTTP, а при необходимости в транзакциях — переключитесь на диалект PostgresDialect с пулом соединений.
    • Обращайте внимание на названия таблиц: генерируемые типы соответствуют названиям базы данных, которые по умолчанию совпадают с названиями моделей Prisma.
  • Перейдите от использования db push к правильным миграциям до того, как данные в базе данных станут иметь реальное значение.