drizzle-graphql-suite

Build GraphQL APIs from Drizzle PostgreSQL schemas with auto-generated CRUD, type-safe clients, and React Query hooks. Use when creating GraphQL servers from Drizzle ORM tables, building type-safe GraphQL clients, adding React data-fetching hooks with TanStack Query, or generating GraphQL SDL/types from Drizzle schemas.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "drizzle-graphql-suite" with this command: npx skills add annexare/drizzle-graphql-suite/annexare-drizzle-graphql-suite-drizzle-graphql-suite

drizzle-graphql-suite

Three-layer toolkit that turns Drizzle ORM PostgreSQL schemas into fully working GraphQL APIs with end-to-end type safety.

Packages

ImportPackagePurpose
drizzle-graphql-suite/schema@drizzle-graphql-suite/schemaServer-side GraphQL schema builder with CRUD, filtering, hooks, and codegen
drizzle-graphql-suite/client@drizzle-graphql-suite/clientType-safe GraphQL client with entity-based API
drizzle-graphql-suite/query@drizzle-graphql-suite/queryTanStack React Query hooks wrapping the client

Data flow: Drizzle schema -> buildSchema() -> GraphQL server -> createDrizzleClient() -> <GraphQLProvider> + hooks

Peer dependencies:

  • ./schema: drizzle-orm >=0.44.0, graphql >=16.3.0
  • ./client: drizzle-orm >=0.44.0
  • ./query: react >=18.0.0, @tanstack/react-query >=5.0.0

When to Use

Use this skill when the user is:

  • Creating a GraphQL server from Drizzle ORM table definitions
  • Building type-safe GraphQL clients for a drizzle-graphql-suite server
  • Adding React data-fetching with TanStack Query for GraphQL
  • Generating GraphQL SDL or static TypeScript types when client and server are in separate repos
  • Configuring hooks, table exclusion, relation depth, or operation filtering
  • Setting up runtime permissions or role-based schema variants
  • Implementing row-level security with WHERE clause injection
  • Working with relation-level filtering (some/every/none quantifiers)
  • Debugging GraphQL schema generation or client type inference

Quick Start

1. Define Drizzle Schema

// db/schema.ts
import { relations } from 'drizzle-orm'
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'

export const user = pgTable('user', {
  id: uuid().primaryKey().defaultRandom(),
  name: text().notNull(),
  email: text().notNull(),
})

export const post = pgTable('post', {
  id: uuid().primaryKey().defaultRandom(),
  title: text().notNull(),
  body: text().notNull(),
  userId: uuid().notNull(),
})

export const userRelations = relations(user, ({ many }) => ({
  posts: many(post),
}))

export const postRelations = relations(post, ({ one }) => ({
  author: one(user, { fields: [post.userId], references: [user.id] }),
}))

2. Build GraphQL Server

import { buildSchema } from 'drizzle-graphql-suite/schema'
import { createYoga } from 'graphql-yoga'
import { createServer } from 'node:http'
import { db } from './db'

const { schema } = buildSchema(db, {
  tables: { exclude: ['session'] },
  hooks: {
    user: {
      query: {
        before: async ({ context }) => {
          if (!context.user) throw new Error('Unauthorized')
        },
      },
    },
  },
})

const yoga = createYoga({ schema })
createServer(yoga).listen(4000)

3. Create Type-Safe Client

import { createDrizzleClient } from 'drizzle-graphql-suite/client'
import * as schema from './db/schema'

const client = createDrizzleClient({
  schema,
  config: { suffixes: { list: 's' } },
  url: '/api/graphql',
  headers: () => ({ Authorization: `Bearer ${getToken()}` }),
})

const users = await client.entity('user').query({
  select: { id: true, name: true, posts: { id: true, title: true } },
  where: { name: { ilike: '%john%' } },
  limit: 10,
})

4. Add React Hooks

import { GraphQLProvider, useEntity, useEntityList } from 'drizzle-graphql-suite/query'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

const queryClient = new QueryClient()

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <GraphQLProvider client={graphqlClient}>
        <UserList />
      </GraphQLProvider>
    </QueryClientProvider>
  )
}

function UserList() {
  const user = useEntity('user')
  const { data, isLoading } = useEntityList(user, {
    select: { id: true, name: true, email: true },
    limit: 20,
  })

  if (isLoading) return <div>Loading...</div>
  return <ul>{data?.map((u) => <li key={u.id}>{u.name}</li>)}</ul>
}

Core API

Schema Package (drizzle-graphql-suite/schema)

// Build complete GraphQL schema from Drizzle db instance
buildSchema(db, config?): { schema: GraphQLSchema; entities: GeneratedEntities; withPermissions: (p: PermissionConfig) => GraphQLSchema }

// Build entities only (queries, mutations, inputs, types) without full schema
buildEntities(db, config?): GeneratedEntities

// Build schema from Drizzle exports without a db connection (for codegen only)
buildSchemaFromDrizzle(drizzleSchema, config?): { schema: GraphQLSchema; entities: GeneratedEntities; withPermissions: (p: PermissionConfig) => GraphQLSchema }

// Permission helpers — build PermissionConfig objects for withPermissions()
permissive(id, tables?): PermissionConfig             // All tables allowed by default; overrides deny
restricted(id, tables?): PermissionConfig             // Nothing allowed by default; overrides grant
readOnly(): TableAccess                               // Shorthand: queries only, no mutations

// Row-level security and hook composition
withRowSecurity(rules): HooksConfig                   // Generate WHERE-injecting hooks from rules
mergeHooks(...configs): HooksConfig                   // Deep-merge multiple HooksConfig objects

// Code generation (for separate-repo setups where client can't import Drizzle schema)
generateSDL(schema): string                          // GraphQL SDL string
generateTypes(schema, options?): string               // TypeScript types (wire, filters, inputs, orderBy)
generateEntityDefs(schema, options?): string           // Runtime entity descriptors + EntityDefs type

// Custom scalar
GraphQLJSON  // JSON scalar type for json/jsonb columns

Client Package (drizzle-graphql-suite/client)

// Recommended: create client from Drizzle schema (full type inference)
createDrizzleClient(options): GraphQLClient

// Alternative: create client from pre-generated schema descriptor
createClient(config): GraphQLClient

// Build schema descriptor from Drizzle schema (for codegen workflows)
buildSchemaDescriptor(schema, config?): SchemaDescriptor

// Error classes
GraphQLClientError  // GraphQL response errors (has .errors, .status)
NetworkError        // HTTP/network failures (has .status)

Entity operations (via client.entity('name')):

MethodDescription
query({ select, where?, limit?, offset?, orderBy? })List query returning T[]
querySingle({ select, where?, offset?, orderBy? })Single query returning T | null
count({ where? })Count matching rows
insert({ values, returning? })Insert array, returns T[]
insertSingle({ values, returning? })Insert one, returns T | null
update({ set, where?, returning? })Update matching rows
delete({ where?, returning? })Delete matching rows

Query Package (drizzle-graphql-suite/query)

// Provider (wrap app)
<GraphQLProvider client={graphqlClient}>

// Access hooks
useGraphQLClient()                                    // Get client from context
useEntity(entityName)                                 // Get typed EntityClient

// Query hooks
useEntityQuery(entity, params, options?)              // Single entity (T | null)
useEntityList(entity, params, options?)               // List query (T[])
useEntityInfiniteQuery(entity, params, options?)      // Paginated infinite query

// Mutation hooks
useEntityInsert(entity, returning?, options?)         // Insert mutation
useEntityUpdate(entity, returning?, options?)         // Update mutation
useEntityDelete(entity, returning?, options?)         // Delete mutation

Configuration

BuildSchemaConfig (Server)

buildSchema(db, {
  mutations: true,                  // Generate mutations (default: true)
  limitRelationDepth: 3,            // Max relation nesting depth (default: 3, 0 = no relations)
  limitSelfRelationDepth: 1,        // Self-relation depth (default: 1 = omitted)
  suffixes: { list: '', single: 'Single' },  // Query name suffixes
  tables: {
    exclude: ['session', 'migration'],        // Omit tables entirely
    config: {
      auditLog: { queries: true, mutations: false },  // Per-table operation control
    },
  },
  pruneRelations: {
    'user.sensitiveData': false,              // Omit relation entirely
    'post.comments': 'leaf',                  // Expand with scalars only
    'org.members': { only: ['profile'] },     // Expand with listed relations only
  },
  hooks: { /* see Hooks section */ },
  debug: true,                                // Log schema size diagnostics
})

ClientSchemaConfig (Client)

The client config must align with the server config for correct query generation:

createDrizzleClient({
  schema,
  config: {
    mutations: true,                          // Must match server
    suffixes: { list: 's', single: 'Single' },  // Must match server
    tables: { exclude: ['session'] },         // Must match server
    pruneRelations: { 'user.secret': false }, // Must match server
  },
  url: '/api/graphql',
})

See references/configuration.md for full details.

Hooks

Hooks intercept query/mutation execution on the server. Two patterns:

Before/After Hooks

hooks: {
  user: {
    query: {
      before: async ({ args, context, info }) => {
        if (!context.user) throw new Error('Unauthorized')
        // Optionally return { args } to override resolver arguments
        // Optionally return { data } to pass data to after hook
      },
      after: async ({ result, beforeData, context }) => {
        // Transform or log result
        return result
      },
    },
  },
}

Resolve Hooks (replace entire resolver)

hooks: {
  post: {
    insert: {
      resolve: async ({ args, context, info, defaultResolve }) => {
        args.values = args.values.map((v) => ({ ...v, authorId: context.user.id }))
        return defaultResolve(args)
      },
    },
  },
}

Hooks apply to all 7 operation types: query, querySingle, count, insert, insertSingle, update, delete.

See patterns/hooks-patterns.md for common recipes.

Permissions

Build filtered GraphQLSchema variants per role or user — introspection fully reflects what each role can see and do.

import { buildSchema, permissive, restricted, readOnly } from 'drizzle-graphql-suite/schema'

const { schema, withPermissions } = buildSchema(db)

// Full schema (admin)
const adminSchema = schema

// Permissive: everything allowed except audit (excluded) and users (read-only)
const maintainerSchema = withPermissions(
  permissive('maintainer', { audit: false, users: readOnly() }),
)

// Restricted: nothing allowed except posts and comments (queries only)
const userSchema = withPermissions(
  restricted('user', { posts: { query: true }, comments: { query: true } }),
)

// Restricted with nothing granted — only Query { _empty: Boolean }
const anonSchema = withPermissions(restricted('anon'))

Permission Helpers

HelperDescription
permissive(id, tables?)All tables allowed by default; overrides deny
restricted(id, tables?)Nothing allowed by default; overrides grant
readOnly()Shorthand for { query: true, insert: false, update: false, delete: false }

TableAccess

Each table can be set to true (all operations), false (excluded entirely), or a TableAccess object:

type TableAccess = {
  query?: boolean   // list + single + count
  insert?: boolean  // insert + insertSingle
  update?: boolean
  delete?: boolean
}

In permissive mode, omitted fields default to true. In restricted mode, omitted fields default to false.

Caching

Schemas are cached by id — calling withPermissions with the same id returns the same GraphQLSchema instance.

See references/permissions.md for full API details and examples/permissions.md for multi-role examples.

Row-Level Security

Generate hooks that inject WHERE clauses for row-level filtering. Compose with other hooks using mergeHooks.

import { buildSchema, withRowSecurity, mergeHooks } from 'drizzle-graphql-suite/schema'

const { schema } = buildSchema(db, {
  hooks: mergeHooks(
    withRowSecurity({
      posts: (context) => ({ authorId: { eq: context.user.id } }),
    }),
    myOtherHooks,
  ),
})

withRowSecurity(rules)

Generates a HooksConfig with before hooks on query, querySingle, count, update, and delete operations. Each rule is a function that receives the GraphQL context and returns a WHERE filter object.

mergeHooks(...configs)

Deep-merges multiple HooksConfig objects:

  • before hooks — chained sequentially; each receives the previous hook's modified args
  • after hooks — chained sequentially; each receives the previous hook's result
  • resolve hooks — last one wins (cannot be composed)

See patterns/hooks-patterns.md for composition recipes.

Relation Filtering

Filter across relations using EXISTS subqueries:

// One-to-one: direct filter
where: { author: { name: { eq: 'Alice' } } }

// One-to-many: quantifier object
where: {
  comments: {
    some: { body: { ilike: '%bug%' } },    // At least one comment matches
    every: { approved: { eq: true } },      // All comments match
    none: { spam: { eq: true } },           // No comments match
  },
}

// Logical OR
where: {
  OR: [
    { title: { ilike: '%graphql%' } },
    { author: { name: { eq: 'Alice' } } },
  ],
}

Error Handling

Server (Schema Package)

Errors thrown in hooks or resolvers are caught and re-thrown as GraphQLError:

// Errors are prefixed: "Drizzle-GraphQL Error: ..."
// In custom hooks, throw standard Error — it will be wrapped in GraphQLError

Client

import { GraphQLClientError, NetworkError } from 'drizzle-graphql-suite/client'

try {
  await client.entity('user').query({ select: { id: true } })
} catch (e) {
  if (e instanceof NetworkError) {
    console.error('HTTP error:', e.status, e.message)
  }
  if (e instanceof GraphQLClientError) {
    console.error('GraphQL errors:', e.errors)  // GraphQLErrorEntry[]
    console.error('HTTP status:', e.status)
  }
}

Generated Operation Names

Table userGenerated Name
List queryuser (or users with suffixes.list: 's')
Single queryuserSingle (customizable via suffixes.single)
Count queryuserCount
InsertinsertIntoUser
Insert singleinsertIntoUserSingle
UpdateupdateUser
DeletedeleteFromUser

Resources

Reference Documentation

  • Schema API — Full schema package API with all function signatures
  • Client API — Client package API, EntityClient methods, error classes
  • Query API — React hooks API, options, cache invalidation
  • Configuration — BuildSchemaConfig and ClientSchemaConfig details
  • Permissions — Permission helpers, withPermissions, TableAccess, RLS, mergeHooks
  • Type Mapping — PostgreSQL column to GraphQL type mapping
  • Code Generation — SDL/type generation for separate-repo setups

Examples

Patterns

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

openclaw-version-monitor

监控 OpenClaw GitHub 版本更新,获取最新版本发布说明,翻译成中文, 并推送到 Telegram 和 Feishu。用于:(1) 定时检查版本更新 (2) 推送版本更新通知 (3) 生成中文版发布说明

Archived SourceRecently Updated
Coding

ask-claude

Delegate a task to Claude Code CLI and immediately report the result back in chat. Supports persistent sessions with full context memory. Safe execution: no data exfiltration, no external calls, file operations confined to workspace. Use when the user asks to run Claude, delegate a coding task, continue a previous Claude session, or any task benefiting from Claude Code's tools (file editing, code analysis, bash, etc.).

Archived SourceRecently Updated
Coding

ai-dating

This skill enables dating and matchmaking workflows. Use it when a user asks to make friends, find a partner, run matchmaking, or provide dating preferences/profile updates. The skill should execute `dating-cli` commands to complete profile setup, task creation/update, match checking, contact reveal, and review.

Archived SourceRecently Updated
Coding

clawhub-rate-limited-publisher

Queue and publish local skills to ClawHub with a strict 5-per-hour cap using the local clawhub CLI and host scheduler.

Archived SourceRecently Updated