Авто-генерація GraphQL CRUD, типобезпечні клієнти та React Query хуки з Drizzle PostgreSQL схем. Повний вивід типів, без кодогенерації.
bun add graphql-suite CRUD-частину GraphQL-API ніхто не хоче писати руками — і все одно всі пишуть. Схема вже знає таблиці, колонки, зв’язки й типи, а ви однаково вручну описуєте резолвери для кожної, потім вручну описуєте типи клієнта під них, а далі вічно тримаєте всі три речі синхронними.
graphql-suite генерує цей шар зі схеми Drizzle ORM і проводить типи аж до React-хуків.
Приклади
Описали схему один раз — віддали як API:
import { buildSchema } from '@graphql-suite/schema'
import { createYoga } from 'graphql-yoga'
import { db } from './db'
const { schema } = buildSchema(db, {
tables: { exclude: ['session', 'verification'] },
})
const yoga = createYoga({ schema })
Запитуйте з типами, виведеними з тих самих таблиць Drizzle — без кодогенерації й без згенерованих файлів у репозиторії:
import { createDrizzleClient } from '@graphql-suite/client'
import * as schema from './db/schema'
const client = createDrizzleClient({ schema, url: '/api/graphql' })
const users = await client.entity('user').query({
select: { id: true, name: true, posts: { id: true, title: true } },
where: { name: { ilike: '%john%' } },
limit: 10,
})
users типізується за вибіркою, тож якщо прибрати name із select, кожне подальше user.name стане помилкою компіляції.
Навіщо це
Я починав з використання drizzle-graphql, який гарно показує, що ідея працює. Але мені постійно бракувало саме того, що перетворює згенерований API на придатний до продакшену: фільтрації через зв’язки, а не лише за колонками верхнього рівня; хуків, щоб виконати авторизацію до резолвера; count-запитів для пагінації; рідної підтримки JSON/JSONB-колонок; налаштовуваних суфіксів у назвах; зв’язків таблиці на саму себе з окремим лімітом глибини; контролю над тим, які таблиці взагалі потрапляють назовні.
У певний момент обгортка стала більшою за те, що вона обгортає, — і зробилася окремим проєктом.
Чим відрізняється
| Підхід | Що це |
|---|---|
graphql-suite | згенерований CRUD з Drizzle плюс типізований клієнт і React-хуки в одному пакеті |
| drizzle-graphql | вихідна ідея — генерація схеми з Drizzle, без фільтрації по зв’язках, хуків і дозволів |
| Hasura, PostGraphile | окремий сервіс перед базою, зі своїми метаданими, деплоєм і моделлю дозволів |
| Резолвери вручну | повний контроль і повна відповідальність за підтримку |
Беріть Hasura чи PostGraphile, якщо хочете продукт над базою з консоллю та інтерфейсом дозволів — вони вміють значно більше й експлуатуються як інфраструктура. Пишіть резолвери руками, коли ваш API свідомо не повторює таблиці. Цей інструментарій — для випадку, коли API і є здебільшого ваша схема, і потрібна наскрізна типобезпека без ще одного сервісу.
Що всередині
Три пакети, три набори peer-залежностей
| Імпорт | Потребує |
|---|---|
@graphql-suite/schema | drizzle-orm, graphql |
@graphql-suite/client | drizzle-orm |
@graphql-suite/query | react, @tanstack/react-query |
Бекенду не доведеться ставити React, щоб збудувати схему. Кожен пакет — @graphql-suite/schema, @graphql-suite/client і @graphql-suite/query — публікується окремо, а загальний graphql-suite просто залежить від усіх трьох: це зручний спосіб установити їх разом і в узгоджених версіях. Якщо потрібен лише бекенд, ставте сам @graphql-suite/schema — по суті, розширений drizzle-graphql.
Хуки — для логіки, яку генерація не вгадає
const { schema, withPermissions } = buildSchema(db, {
hooks: {
user: {
query: {
before: async ({ context }) => {
if (!context.user) throw new Error('Unauthorized')
},
},
},
},
})
Схеми під ролі
import { permissive, restricted, readOnly } from '@graphql-suite/schema'
const schemas = {
admin: schema,
editor: withPermissions(permissive('editor', { audit: false, user: readOnly() })),
viewer: withPermissions(restricted('viewer', { post: { query: true } })),
}
Ролі перетворюються на окрему схему для кожної, тож недозволене поле відсутнє у схемі, а не відхиляється під час виконання.
React Query хуки
Хуки очікують над собою обидва провайдери — від TanStack Query і від цього пакета, який несе клієнт, зібраний через @graphql-suite/client:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { GraphQLProvider, useEntity, useEntityList } from '@graphql-suite/query'
const queryClient = new QueryClient()
const App = () => (
<QueryClientProvider client={queryClient}>
<GraphQLProvider client={graphqlClient}>
<UserList />
</GraphQLProvider>
</QueryClientProvider>
)
const UserList = () => {
const user = useEntity('user')
const { data, isLoading } = useEntityList(user, {
select: { id: true, name: true, email: true },
limit: 20,
})
// рендеримо data…
}
Кешування, пагінація й інвалідація — від TanStack Query; цей шар лише дає типізовані ключі та функції завантаження.
Нотатки про рішення
Виведення типів — головна фіча. Жодного додаткового кроку збірки, жодних згенерованих файлів у рев’ю: клієнт виводить запити, фільтри й типи результатів прямо з модуля схеми Drizzle, тож усе просто працює і лишається рівно настільки типобезпечним, наскільки типобезпечна сама схема — зміна таблиці одразу спливає як помилка типів, а не як застарілий артефакт. Кодогенерація теж є (buildSchemaFromDrizzle()), але лише для єдиного випадку, якому вона справді потрібна: клієнт в окремому репозиторії, який не може імпортувати схему.
Згенерована схема платить за глибину двічі. Глибокі зв’язки і зв’язки таблиці на саму себе роздувають і GraphQL-схему, і типи TypeScript, які мусить обчислювати редактор. Ліміти глибини (limitRelationDepth, окремо limitSelfRelationDepth для самозв’язків), обрізання окремих зв’язків і перемикачі для таблиць зменшують обидва: налаштована схема виходить до 90% меншою, а виведені типи лишаються досить компактними, щоб IDE встигала.
Дозволи дають схеми, а не перевірки. Якщо фільтрувати поле під час виконання, воно все одно видно в інтроспекції. Окрема схема на роль означає, що у схемі глядача справді немає полів, які йому не можна читати.
Один загальний пакет над трьома справжніми. Окремі пакети публікуються самостійно; загальний фіксує узгоджені версії всіх трьох, тож шари, згенеровані з тих самих типів, не можуть розійтися в lock-файлі. graphql-suite — зручний і узгоджений вибір за замовчуванням, окремий пакет — варіант під конкретний сервіс.
Статус
Живий, має власний сайт документації. Підтримуваний діалект — PostgreSQL: генерація спирається на типи Drizzle для Postgres, тож інші діалекти потребували б власного відображення фільтрів, а не просто прапорця.