Strona główna / Artykuły

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.

Wydawca: REACTAPP.TOP6 min czytania1411 słów

Ź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 Prisma prisma/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.
  • W najnowszych wersjach Prismy ustawienia połączenia przeniesiono do osobnego pliku konfiguracyjnego; jeśli linia 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:

    1. Zmieniaj modele w pliku db/schema.prisma.
    2. Regeneruj typy i zastosuj zmiany za pomocą dwóch skryptów.
    3. Używaj db w komponentach serwerowych, obsługujących ścieżki lub akcjach serwera.
  • Zanim dane zostaną wstawione, należy je zweryfikować za pomocą Zod lub podobnych narzędzi; typy Kysely sprawdzają strukturę zapytania, a nie wartości formularza.
  • 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 prisma i prisma-kysely jako 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 PostgresDialect z 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.
  • Przejdź od db push do prawidłowych migracji, zanim dane w bazie danych staną się istotne.
  • Literatura pokrewna