@stonecrop/nuxt-grafserv
v0.13.0
Published
Pluggable Grafserv GraphQL server as Nuxt Module
Readme
Nuxt Grafserv
Pluggable Grafserv GraphQL server as a Nuxt Module. Uses the Grafast execution engine for high-performance GraphQL.
Features
- 🚀 Grafserv Server Integration
- ⚡️ Grafast Execution Engine (faster than
graphql-js) - 🔄 Schema Stitching Support
- 🔍 Graphile Preset System for Advanced Configuration
- 📝 TypeScript Support
- 🔍 GraphiQL/Ruru Interface
- ⚡️ Hot Module Reloading
- 🎯 Separate Route Handlers for GraphQL/UI and Static Assets
Architecture
This module uses modern Grafserv patterns with three key components:
- Preset-Based Configuration: Leverages Graphile's preset system for extensibility and plugin support
- Separate Route Handlers: Two dedicated handlers for GraphQL operations/UI and static assets
- Objects Structure: Uses Grafast's modern
objects/interfaces/enumsschema building pattern for better type safety
The module automatically registers these handlers:
{url}- Unified GraphQL operations and Ruru UI endpoint/ruru-static/**- Static assets for the Ruru IDE
Quick Setup
PostGraphile Integration (Recommended)
For PostGraphile users, this is the recommended configuration approach:
- Add dependencies:
# Using pnpm
pnpm add @stonecrop/nuxt-grafserv
# Using yarn
yarn add @stonecrop/nuxt-grafserv
# Using npm
npm install @stonecrop/nuxt-grafserv- Set
DATABASE_URLin your environment (.envor shell):
DATABASE_URL=postgresql://localhost/mydb- Configure in
nuxt.config.ts:
export default defineNuxtConfig({
modules: ['@stonecrop/nuxt-grafserv'],
grafserv: {
type: 'postgraphile', // Required: specify configuration type
url: '/graphql',
graphiql: true,
// fieldCasing: 'pascal', // optional — defaults to 'camel'
// schemas: ['public', 'auth'], // optional — defaults to ['public']
// explain: true, // optional — enables Ruru Explain tab (dev only)
}
})For custom plugins, a non-standard connection, or advanced configuration, create a preset file:
// server/graphile.preset.ts
import { createStonecropPreset, makePgService, createStonecropPlugin } from '@stonecrop/graphql-middleware'
export default {
extends: [createStonecropPreset()],
pgServices: [
makePgService({
connectionString: process.env.DATABASE_URL,
schemas: ['public'],
}),
],
plugins: [createStonecropPlugin(), MyCustomPlugin],
}Then reference it in nuxt.config.ts:
grafserv: {
type: 'postgraphile',
preset: './server/graphile.preset.ts',
}Custom Schema Configuration
For custom GraphQL schemas with your own resolvers:
- Add dependency:
pnpm add @stonecrop/nuxt-grafserv- Configure in
nuxt.config.ts:
export default defineNuxtConfig({
modules: ['@stonecrop/nuxt-grafserv'],
grafserv: {
type: 'schema', // Required: specify configuration type
schema: 'server/**/*.graphql',
resolvers: 'server/resolvers.ts',
url: '/graphql',
graphiql: true,
}
})Configuration
The module supports two configuration types using a discriminated union pattern:
PostGraphile Configuration
Use type: 'postgraphile' for PostGraphile-based GraphQL APIs:
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| type | 'postgraphile' | ✅ | Configuration type discriminator |
| preset | string | ❌ | Path to preset file. When omitted, a default preset is synthesized from DATABASE_URL. |
| fieldCasing | 'camel' \| 'pascal' | ❌ | Column → field name casing (default: 'camel'). Ignored when preset is set. |
| schemas | string[] | ❌ | PostgreSQL schemas to expose (default: ['public']). Ignored when preset is set. |
| explain | boolean | ❌ | Enable Ruru Explain tab. Never use in production. Ignored when preset is set. |
| url | string | ❌ | GraphQL endpoint URL (default: /graphql/) |
| graphiql | boolean | ❌ | Enable GraphiQL IDE (default: true) |
Minimal example (no preset file):
// nuxt.config.ts — DATABASE_URL is read from process.env automatically
export default defineNuxtConfig({
grafserv: {
type: 'postgraphile',
url: '/graphql/',
graphiql: true,
}
})Explicit preset file (for custom plugins and advanced configuration):
// server/graphile.preset.ts
import { createStonecropPreset, makePgService, createStonecropPlugin } from '@stonecrop/graphql-middleware'
export default {
extends: [createStonecropPreset()],
pgServices: [
makePgService({
connectionString: process.env.DATABASE_URL,
schemas: ['public'],
}),
],
plugins: [createStonecropPlugin(), MyCustomPlugin],
}Schema Configuration
Use type: 'schema' for custom GraphQL schemas with Grafast resolvers:
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| type | 'schema' | ✅ | Configuration type discriminator |
| schema | string \| string[] \| SchemaProvider | ✅ | Path(s) to .graphql files or schema provider function |
| resolvers | string | ❌ | Path to resolvers file (required for .graphql files) |
| url | string | ❌ | GraphQL endpoint URL (default: /graphql/) |
| graphiql | boolean | ❌ | Enable GraphiQL IDE (default: true) |
Example with files:
export default defineNuxtConfig({
grafserv: {
type: 'schema',
schema: 'server/**/*.graphql',
resolvers: 'server/resolvers.ts',
url: '/graphql/',
}
})Example with schema provider function:
export default defineNuxtConfig({
grafserv: {
type: 'schema',
schema: async () => {
// Return a GraphQLSchema instance
return myCustomSchema
},
}
})Configuration Comparison
| Feature | PostGraphile Config | Schema Config |
|---------|---------------------|---------------|
| Type discriminator | type: 'postgraphile' | type: 'schema' |
| Schema source | Generated from preset | Files or function |
| Resolvers | Auto-generated by PostGraphile | Must provide via resolvers file |
| Primary use case | PostgreSQL-backed APIs | Custom GraphQL APIs |
| Setup complexity | Minimal (DB connection only) | Moderate (schema + resolvers) |
| Plugin system | PostGraphile plugins | Grafast standard steps |
Basic Usage
Schema Configuration Example
For custom schemas with resolvers:
- Create your GraphQL schema (
server/schema.graphql):
type Query {
hello: String!
ping: Boolean!
}
type Mutation {
echo(message: String!): String!
}- Create your resolvers (
server/resolvers.ts):
import { constant, lambda, type GrafastSchemaConfig } from 'grafast'
const resolvers: GrafastSchemaConfig['objects'] = {
Query: {
plans: {
hello: () => constant('world'),
ping: () => constant(true)
}
},
Mutation: {
plans: {
echo: (_source, fieldArgs) => {
const { $message } = fieldArgs
return $message
}
}
}
}
export default resolvers- Configure Nuxt:
export default defineNuxtConfig({
modules: ['@stonecrop/nuxt-grafserv'],
grafserv: {
type: 'schema',
schema: 'server/schema.graphql',
resolvers: 'server/resolvers.ts',
}
})PostGraphile Configuration Example
For database-backed GraphQL APIs:
- Create your preset file
server/graphile.preset.ts:
import { createStonecropPreset, makePgService } from '@stonecrop/graphql-middleware'
const preset = {
extends: [createStonecropPreset()],
pgServices: [
makePgService({
connectionString: process.env.DATABASE_URL,
schemas: ['public'],
}),
],
}
export default preset- Configure PostGraphile in
nuxt.config.ts:
export default defineNuxtConfig({
modules: ['@stonecrop/nuxt-grafserv'],
grafserv: {
type: 'postgraphile',
preset: './server/graphile.preset.ts',
}
})- That's it! PostGraphile automatically generates your GraphQL schema from your PostgreSQL database.
Advanced Usage
Custom Grafserv Plugins
Add custom plugins for authentication, logging, or rate limiting through your preset file:
// server/graphile/plugins.ts
import type { GraphileConfig } from 'graphile-config'
const loggingPlugin: GraphileConfig.Plugin = {
name: 'request-logging',
version: '1.0.0',
grafserv: {
middleware: {
processGraphQLRequestBody: async (next, event) => {
console.log('Processing:', event.request.url)
return next()
}
}
}
}
const authPlugin: GraphileConfig.Plugin = {
name: 'authentication',
version: '1.0.0',
grafserv: {
middleware: {
processGraphQLRequestBody: async (next, event) => {
const token = event.request.headers.get('authorization')
// Validate token and add user to context
if (!token) {
throw new Error('Unauthorized')
}
return next()
}
}
}
}
export default [loggingPlugin, authPlugin]Import the plugins in your preset:
// server/graphile/graphile.preset.ts
import { createStonecropPreset, makePgService } from '@stonecrop/graphql-middleware'
import plugins from './plugins'
const preset = {
extends: [createStonecropPreset()],
pgServices: [makePgService({ /* ... */ })],
plugins
}
export default presetAvailable Middleware Hooks
These are hooks provided by the grafserv package itself via the Graphile preset system — they are not specific to @stonecrop/nuxt-grafserv. The hook used in the example above (processGraphQLRequestBody) is the most common entry point. For the full list of available hooks, refer to the Grafserv documentation.
PostGraphile Integration
PostGraphile v5+ automatically generates your GraphQL schema from PostgreSQL. When using a custom preset file (preset option), you have access to all PostGraphile preset keys — these are PostGraphile features, not Stonecrop-specific ones.
With Community Plugins
Third-party PostGraphile plugins can be added via the plugins array in a custom preset file:
// server/graphile.preset.ts
import { createStonecropPreset, makePgService, createStonecropPlugin } from '@stonecrop/graphql-middleware'
import PgSimplifyInflectorPlugin from '@graphile-contrib/pg-simplify-inflector'
export default {
extends: [createStonecropPreset()],
plugins: [createStonecropPlugin(), PgSimplifyInflectorPlugin],
pgServices: [
makePgService({
connectionString: process.env.DATABASE_URL,
schemas: ['public'],
}),
],
}For further PostGraphile preset configuration (behaviours, JWT, subscriptions, smart tags, etc.), refer to the PostGraphile v5 documentation.
Why File-Based Presets?
PostGraphile presets must be in separate files, not inline in nuxt.config.ts.
The module uses PostGraphile's instance-based approach:
- At build time, the preset file is imported and a PostGraphile instance is created
- The instance is exposed via Nitro's virtual module system
- At runtime, handlers call
pgl.getSchema()to get the schema
This approach avoids GraphQL module duplication, follows PostGraphile's recommended pattern, supports watch mode, and properly handles database connections.
Your preset file needs a default export:
import { createStonecropPreset } from '@stonecrop/graphql-middleware'
const preset = {
extends: [createStonecropPreset()],
// ...
}
export default presetImport Requirements:
Relative imports in preset files work with file extensions:
// server/graphile/graphile.preset.ts
import { createStonecropPreset, makePgService } from '@stonecrop/graphql-middleware'
import { MyPlugin } from './plugins/my-plugin'
import { AnotherPlugin } from './plugins/another'
const preset = {
extends: [createStonecropPreset()],
pgServices: [makePgService({/*...*/})],
plugins: [MyPlugin, AnotherPlugin],
}
export default presetSchema Building with Grafast
Grafast uses a step-based execution model. Common patterns:
import { constant, lambda, access, object, filter, type GrafastSchemaConfig } from 'grafast'
const resolvers: GrafastSchemaConfig['objects'] = {
Query: {
plans: {
// Static values use constant()
hello: () => constant('world'),
// Arguments accessed via fieldArgs with $ prefix
user: (_source, fieldArgs) => {
const { $id } = fieldArgs
return lambda($id, (id) => getUserById(id))
},
// Use filter() for list filtering
userOrders: (_source, fieldArgs) => {
const { $userId } = fieldArgs
const $allOrders = constant(getAllOrders())
return filter($allOrders, $order =>
lambda([access($order, 'userId'), $userId],
([orderUserId, userId]) => orderUserId === userId
)
)
}
}
},
Mutation: {
plans: {
// Use object() to compose objects from steps
createUser: (_source, fieldArgs) => {
const { $name, $email } = fieldArgs
const $id = constant(generateId())
const $now = constant(new Date().toISOString())
const $user = object({
id: $id,
name: $name,
email: $email,
role: constant('user'),
createdAt: $now,
updatedAt: $now
})
return lambda($user, user => {
saveUser(user)
return user
})
}
}
},
// Field resolvers for types
User: {
plans: {
fullName: ($user) => {
return lambda($user, (user) => {
const typed = user as { firstName: string; lastName: string }
return `${typed.firstName} ${typed.lastName}`
})
}
}
},
// Related type resolvers
Order: {
plans: {
// Use access() to extract properties before lookups
user: ($order) => {
const $userId = access($order, 'userId')
return lambda($userId, userId => getUserById(userId as string) ?? null)
}
}
}
}
export default resolversKey Concepts:
- Resolvers return steps (constant, lambda, access, object, filter), not plain values
- Arguments are accessed via
fieldArgs.$argumentName constant()- static valueslambda()- transform step values at execution timeaccess()- extract object properties efficientlyobject()- compose objects from stepsfilter()- filter list steps
See Grafast Standard Steps for the complete reference.
Development
This package lives inside the Stonecrop Rush monorepo. Use rushx instead of pnpm run to invoke scripts.
# Install all monorepo dependencies (run from the repo root)
rush install
# Bootstrap the playground database (first time only — requires Docker)
cd playground && node scripts/bootstrap.mjs
# Generate type stubs
rushx dev:prepare
# Develop with the playground (from nuxt_grafserv/)
rushx dev
# Build the module
rushx build
# Run ESLint
rushx lint
# Run tests
rushx test
rushx test:watchThe playground runs against a PostgreSQL container managed by Docker Compose. The bootstrap script handles container startup, .env creation, and running the initial migration. On subsequent runs rushx dev is all that's needed (the container persists via a named volume).
