# GraphQL Suite — Full Documentation > Auto-generated GraphQL CRUD, type-safe clients, and React Query hooks from Drizzle PostgreSQL schemas. GraphQL Suite turns Drizzle ORM table definitions into a production-ready GraphQL stack with zero code generation and full TypeScript inference. ## Packages - @graphql-suite/schema: Generates a complete GraphQL schema with CRUD operations from Drizzle PostgreSQL tables (peer: drizzle-orm >=0.44.0, graphql >=16.3.0) - @graphql-suite/client: Type-safe GraphQL client that infers types from your Drizzle schema (peer: drizzle-orm >=0.44.0) - @graphql-suite/query: React hooks wrapping TanStack Query for the client (peer: react >=18.0.0, @tanstack/react-query >=5.0.0) - graphql-suite: Umbrella package that includes all three ## Installation Individual packages: bun add @graphql-suite/schema graphql drizzle-orm bun add @graphql-suite/client drizzle-orm bun add @graphql-suite/query @tanstack/react-query react Or all-in-one: bun add graphql-suite graphql drizzle-orm @tanstack/react-query react Imports always use the scoped form regardless of install method: import { buildSchema } from '@graphql-suite/schema' import { createDrizzleClient } from '@graphql-suite/client' import { GraphQLProvider, useEntity } from '@graphql-suite/query' --- ## Schema Package (@graphql-suite/schema) ### Key Functions buildSchema(db, config?) — Main entry point. Requires live DB connection. Returns: { schema, entities, withPermissions, clearPermissionCache } buildSchemaFromDrizzle(drizzleSchema, config?) — Build without DB (stub resolvers for introspection/codegen). buildEntities(db, config?) — Returns GeneratedEntities only for composing into existing schemas. ### Configuration (BuildSchemaConfig) All options are optional: - mutations: boolean (default: true) — enable insert/update/delete - suffixes: { list?: string, single?: string } (default: { list: '', single: 'Single' }) - { list: 's', single: '' } → articles / article - { list: 'List', single: '' } → articleList / article - limitRelationDepth: number (default: 3) — max nesting for relations - limitSelfRelationDepth: number (default: 1) — self-referencing relation limit - tables.exclude: string[] — exclude tables entirely - tables.config: { [table]: { queries?: bool, mutations?: bool } } — per-table control - pruneRelations: { [table.relation]: false | 'leaf' | { only: string[] } } - hooks: HooksConfig — lifecycle hooks per table/operation - debug: boolean | { schemaSize?: bool, relationTree?: bool } ### Generated Operations For table "article" with suffixes { list: 's', single: '' }: - Queries: articles (list), article (single), articleCount - Mutations: insertIntoArticle, insertIntoArticleSingle, updateArticle, deleteFromArticle ### Filters Column filters: eq, ne, lt, lte, gt, gte, like, notLike, ilike, notIlike, inArray, notInArray, isNull, isNotNull Logical: AND, OR (arrays of filter conditions) Relation filters (one-to-many): some, every, none quantifiers where: { comments: { some: { approved: { eq: true } } } } One-to-one / many-to-one: direct nested filter (no quantifier) where: { author: { role: { eq: 'admin' } } } ### Ordering orderBy: { column: { direction: 'asc' | 'desc', priority: number } } Lower priority number = higher precedence. ### Permissions permissive(id, tables?) — Allow all by default, restrict per table restricted(id, tables?) — Deny all by default, whitelist per table readOnly() — Shorthand for { query: true, insert: false, update: false, delete: false } Apply: const securedSchema = withPermissions(permissive('admin')) ### Hooks Per-table lifecycle hooks for: query, querySingle, count, insert, insertSingle, update, delete Hook types: - before(ctx) → modify args or short-circuit with { data: ... } - after(ctx) → transform result - resolve(ctx) → replace resolver; call ctx.defaultResolve(args?) Row-level security helper: const hooks = withRowSecurity({ post: (ctx) => ({ authorId: { eq: ctx.user.id } }) }) const merged = mergeHooks(hooks, loggingHooks) ### Code Generation generateSDL(schema) → GraphQL SDL string generateTypes(schema, options?) → TypeScript types generateEntityDefs(schema, options?) → Entity definitions + schema descriptor for client --- ## Client Package (@graphql-suite/client) ### Creating a Client createDrizzleClient({ schema, config, url, headers }) — Recommended. - schema: your Drizzle schema module (import * as schema from './db/schema') - config: must match server config (especially suffixes) - url: string or () => string - headers: object, () => object, or () => Promise ### Entity Operations client.entity('tableName') returns an entity client with: query({ select, where?, orderBy?, limit?, offset? }) → T[] querySingle({ select, where?, orderBy?, offset? }) → T | null count({ where? }) → number insert({ values, returning? }) → T[] insertSingle({ values, returning? }) → T | null update({ set, where?, returning? }) → T[] delete({ where?, returning? }) → T[] execute(query, variables) → raw GraphQL response The select parameter drives both the GraphQL query shape and TypeScript return type. ### Error Classes GraphQLClientError — GraphQL response errors (status, errors[], message) NetworkError — Transport failures (status, message) ### Type Inference InferEntityDefs — Infer entity definitions from schema + config InferResult — Compute return type from select shape SelectInput — Constrain select to valid fields --- ## Query Package (@graphql-suite/query) ### Setup Wrap app with QueryClientProvider > GraphQLProvider: ### Query Hooks useEntityList(entity, params, options?) → { data: T[], isPending, error, refetch } useEntityQuery(entity, params, options?) → { data: T | null, ... } useEntityInfiniteQuery(entity, params, options?) → { data, fetchNextPage, hasNextPage } - params includes pageSize instead of limit - Each page: { items: T[], count: number } ### Mutation Hooks useEntityInsert(entity, returning?, options?) → { mutate, isPending, error } useEntityUpdate(entity, returning?, options?) → { mutate, isPending, error } useEntityDelete(entity, returning?, options?) → { mutate, isPending, error } Cache invalidation: default invalidates all ['gql'] queries on mutation success. Options: invalidate (bool), invalidateKey (string[]), onSuccess, onError ### Context Hooks useGraphQLClient() → GraphQLClient from context useEntity(entityName) → entity client from context --- ## Guides ### Schema Tuning - Start with limitRelationDepth: 2 - Use pruneRelations for expensive relations - Mark junction tables as read-only - Exclude internal tables (session, account, etc.) ### Testing - Schema tests: use buildSchemaFromDrizzle (no DB needed) - E2E tests: mock DB + yoga server - Component tests: mock client + linkedom ### SSR - Fetch on server, pass as props - Prefill TanStack Query cache to avoid client refetch --- ## Key Concepts - No code generation: types flow through TypeScript inference - Single source of truth: Drizzle schema defines everything - Config sync: BuildSchemaConfig must match between server and client - Wire format: Date fields serialized as ISO strings - Permission caching: cached by ID; call clearPermissionCache(id?) to refresh ## Links - Source: https://github.com/annexare/graphql-suite - npm: https://www.npmjs.com/org/graphql-suite - Docs: https://graphql-suite.annexare.com