Prisma bez Prisma Client: Bezpieczne zapytania typowe w Next.js
Dowiedz się, jak używać Prisma do pracy z schematami, generować typy Kysely za pomocą prisma-kysely, wybrać dialekt Neon lub pg oraz bezpiecznie wysyłać zapytania z kodu serwera Next.js.
Źródła: **Using Prisma for Schema and Kysely for Queries in a Next.js App**
Prisma doskonale nadaje się do projektowania schematów, ale Prisma Client stanowi spore obciążenie w postaci zależności, którą trzeba włączać do każdej funkcji serverless, gdy chce się jedynie używać bezpiecznego pod względem typów SQL. Praktyczne rozwiązanie to takie, w którym Prisma zarządza schematem podczas rozwoju, generator przekształca go na typy TypeScript, a Kysely wykonywa zapytania w środowisku produkcyjnym. Ten przewodnik pokazuje, jak to skonfigurować w projekcie Next.js App Router, oraz porównuje dialekt Neon HTTP z klasycznym buforem pg.
Jeśli zamiast tego chcesz konwencjonalnego podejścia z Prisma Client w czasie wykonywania, zapoznaj się z konfiguracją Prismy 7 w projekcie Next.js 16.
Jak dzielą się obowiązki
Każde narzędzie w tym rozwiązaniu ma jedno jasne zadanie:
- Prisma odpowiada za projekt bazy danych: plik schematu, synchronizację lub migrację bazy danych oraz narzędzia takie jak Prisma Studio do przeglądania danych.
- Kysely to narzędzie do budowania zapytań w czasie wykonywania. Piszesz zapytania w formacie SQL w języku TypeScript, kompilator sprawdza nazwy tabel i kolumn, a sama biblioteka jest niewielka.
- prisma-kysely pełni rolę łącznika: jest to generator Prisma, który generuje definicje typów Kysely bezpośrednio na podstawie twojego schematu Prisma.
Prisma pozostaje w Twoim procesie rozwoju, podczas gdy Kysely jest jedyną warstwą bazy danych w uruchomionym kodzie. Kompromis polega na tym, że tracisz API Prisma Client obsługujące relacje, takie jak nawiasowe include i nawiasowe operacje zapisu, oraz konieczność ręcznego tworzenia połączeń zapisowych. Jeśli nadal wybierasz warstwę danych, porównanie surowego SQL, Prismy i Drizzle omawia szersze aspekty tej decyzji.
Instalacja pakietów
Czas działania wymaga Kysely wraz z dialektem i sterownikiem; instaluje to stack oparty na Neonie (tutaj pnpm, ale działają też npm lub yarn):
pnpm add kysely kysely-neon @neondatabase/serverless
pnpm add -D prisma prisma-kysely
Należy pamiętać, że prisma i prisma-kysely to zależności deweloperskie: zarządzają one jedynie schematem i generują kod.
Definiowanie schematu i generowanie typów Kysely
Schemat jest zwykłym schematem Prisma, z tą różnicą, że generator to prisma-kysely (zapisujący dane do ./types) zamiast standardowego generatora klienta. Definiuje on User i Post z relacją jeden-do-wielu.
// 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 generuje teraz typy Kysely zamiast klienta, a prisma db push aplikuje schemat do bazy danych:
pnpm prisma generate
pnpm prisma db push
Wynikiem jest plik taki jak db/types/kysely.d.ts zawierający interfejs DB opisujący każdą tabelę. Trzy uwagi:
- Schemat znajduje się w
db/schema.prisma, a nie w domyślnym pliku Prismaprisma/schema.prisma, więc należy wskazać na niego CLI (na przykład za pomocą--schema) zgodnie z wymaganiami wersji Prisma.
db push synchronizuje bazę danych bez zapisywania plików migracji. Dla danych produkcyjnych prisma migrate dev oraz prisma migrate deploy umożliwiają tworzenie wersjonowanych migracji przy zachowaniu całego pozostałego procesu.url zostanie odrzucona, należy sprawdzić aktualną dokumentację.Skrypty pakietowe do codziennej pracy
Kilka skryptów ułatwia zespołowi zrozumienie procesu pracy:
{
"scripts": {
"db:push": "prisma db push",
"db:generate": "prisma generate",
"db:studio": "prisma studio"
}
}
Gdy te skrypty są już wdrożone, zmiana schematu sprowadza się do dwóch poleceń:
# To generate the types
pnpm db:generate
# To push to the remote DB
pnpm db:push
Baza danych oraz typy Kysely pozostają w zgodności, dzięki czemu kompilator sygnalizuje każde zapytanie dotyczące przemianowanej lub usuniętej kolumny.
Wybór dialektu Kysely
Kysely nie komunikuje się bezpośrednio z bazą danych. Pomiędzy konstruktorem zapytań a sterownikiem znajduje się dialekt, który wie, jak kompilować i wysyłać zapytania dla danej bazy danych oraz metody połączenia. Oficjalne dialekty obejmują PostgreSQL, MySQL i SQLite; te społecznościowe są przeznaczone dla usług takich jak Neon, PlanetScale i Turso. W przypadku Postgresa dwie opcje wystarczają w większości sytuacji.
Neon przez HTTP do implementacji bez serwera
Na Vercel lub podobnej platformie sterownik HTTP Neon doskonale się sprawdza: każde zapytanie jest żądaniem bez stanu, więc nie ma potrzeby utrzymywania trwałego połączenia między krótkimi wywołaniami. Konfiguracja otacza funkcję neon() obiektem NeonDialect i typuje Kysely przy użyciu wygenerowanego typu 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
Symbol ! jedynie wyłącza funkcje TypeScript; nie weryfikuje niczego. Większym ograniczeniem jest to, że kysely-neon przez HTTP nie może przeprowadzać transakcji ani zapytań opartych na sesjach, więc nadaje się raczej do prostych operacji CRUD i stron wymagających dużo odczytów, a nie do zadań wymagających wielu instrukcji. Z użyciem sterownika WebSocket od Neon nie potrzebujesz w ogóle kysely-neon: przekaż Pool od Neon do wbudowanego PostgresDialect w Kysely. API kysely-neon również zmieniało się pomiędzy wersjami, więc porównaj je z aktualnym plikiem README.
node-postgres dla serwerów działających długo
Dla serwera Node.js pracującego przez długi czas oficjalny sterownik pg jest najpełniejszym wyborem, oferującym transakcje, zarządzanie zasobami oraz przygotowane zapytania. Łączy się on z własnym PostgresDialect w 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
Wybór między nimi
Różnica polega na modelu połączeń. kysely-neon to HTTP bez stanu: lekki i przyjazny dla rozwiązań serverless, ale bez możliwości przeprowadzania transakcji. pg utrzymuje otwarte połączenia TCP z pulą, oferując pełny zestaw funkcji, co nadaje się do serwerów, które działają nieprzerwanie. Używaj kysely-neon na platformach serverless takich jak Vercel lub Netlify, a pg lub pulę WebSocket Neon, gdy potrzebujesz transakcji. Uważaj na użycie puli pg w funkcjach serverless: każda instancja otwiera własne połączenia i może wyczerpać limit bazy danych bez zewnętrznego narzędzia do zarządzania połączeniami.
Zapytania z komponentu serwera
Każdy kod po stronie serwera może importować bibliotekę db. Ten komponent serwera wyświetla listę użytkowników, wybierając trzy kolumny uporządkowane według id:
// 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>
)
}
Ponieważ DB pochodzi ze schematu Prisma, błędnie napisana tabela lub kolumna powoduje niepowodzenie sprawdzenia typów, a users jest ustalany na podstawie dokładnie wybranych pól.
Jedna trudność: Prisma domyślnie nazywa tabele według modeli, więc ten schemat tworzy tabelę User, a generowane typy używają tego nazwy. selectFrom('user') sprawdza typy tylko wtedy, gdy do modelu dodasz @@map("user"); w przeciwnym razie zapytanie odnosi się do 'User'. Unikaj również używania db w komponentach klienckich, ponieważ odczytuje on tajemnicę dostępną tylko na serwerze.
Powszednia procedura pracy
Gdy wszystko jest już skonfigurowane, pętla wygląda następująco:
- Zmieniaj modele w pliku
db/schema.prisma. - Regeneruj typy i zastosuj zmiany za pomocą dwóch skryptów.
- Używaj
dbw komponentach serwerowych, obsługujących ścieżki lub akcjach serwera.
Otrzymany układ przechowuje wszystko związane z bazą danych pod katalogiem 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
Podsumowanie
Taki podział pozwala zachować standardową metodologię pracy z schematem w Prisma oraz Studio, bez konieczności wysyłania Prisma Client, a także dostarcza niewielką warstwę zapytań w formacie SQL, której typy odzwierciedlają strukturę schematu.
- Zachowaj
prismaiprisma-kyselyjako zależności deweloperskie; do środowiska produkcyjnego wysyła się tylko Kysely, odpowiedni dialekt oraz driver. - Dla prostych zadań serverless użyj dialektu Neon HTTP, a gdy potrzebujesz transakcji, przejdź na
PostgresDialectz zarządzanym połączeniem. - Uważaj na nazewnictwo tabel: generowane typy odpowiadają nazwom bazy danych, które domyślnie tożsame są z nazwami modeli w Prisma.
db push do prawidłowych migracji, zanim dane w bazie danych staną się istotne.Literatura pokrewna
- Auto-generowanie szyfrowanego klienta API Next.js z NestJS Swagger — Dowiedz się, jak usunąć duplikowane typy API przy użyciu NestJS Swagger i Orval do automatycznego generowania bezpiecznych hooków React Query dla Next.js.
- Ustawianie Prisma 7 w projekcie Next.js 16 z TypeScript — Dowiedz się, jak zainstalować, skonfigurować i przeprowadzić migrację Prisma 7 z PostgreSQL w aplikacji Next.js 16, a następnie stworzyć klienta typu singleton, aby uniknąć wycieków połączeń.