The easiest way to add GraphQL to any Nitro application
🚀 Auto-discovery • 📝 Type Generation • 🎮 Apollo Sandbox • 🔧 Zero Config
Important
v2.0 Beta (Current - Main Branch) This is the v2.0 beta branch with Nitro v3 and H3 v2 support. Includes Rolldown optimization, improved chunking, and enhanced Vite integration.
Looking for v1.x?
For the stable v1 version (Nitro v2), see the v1 branch.
- Nuxt 4 Integration - Step-by-step Nuxt setup
- Standalone Nitro - Basic Nitro integration
- ⚡ 5-minute setup - From zero to GraphQL in minutes
- 🔍 Auto-discovery - Scans your files, builds your schema
- 📝 Type-safe - Full TypeScript support with auto-generated types
- 🎯 Universal - Works with Nuxt, Nitro, and any Nitro-based framework
- 🎮 Developer-friendly - Built-in Apollo Sandbox for testing
- 🔧 Zero config - Sensible defaults, customize when needed
- 🚀 Nitro v3 & H3 v2 - Full compatibility with the latest Nitro and H3
- ⚙️ Rolldown Support - Optimized for both Rolldown (Vite 7+) and Rollup
- 📦 Smart Chunking - GraphQL code split into separate chunks (~98% size reduction)
- 🔍 Debug Dashboard - Built-in diagnostics at
/_nitro/graphql/debug(dev only) - 🎨 Enhanced Vite Integration - Better custom path support and virtual module resolution
GraphQL Yoga (recommended):
pnpm add nitro-graphql@beta graphql-yoga graphql graphql-configApollo Server:
pnpm add nitro-graphql@beta @apollo/server graphql graphql-config🔧 Nitro Project
// nitro.config.ts
import graphql from 'nitro-graphql'
import { defineNitroConfig } from 'nitro/config'
export default defineNitroConfig({
modules: [
graphql({
framework: 'graphql-yoga', // or 'apollo-server'
}),
],
})⚡ Vite + Nitro Project
// vite.config.ts
import { defineConfig } from 'vite'
import { nitro } from 'nitro/vite'
import graphql from 'nitro-graphql'
import { graphql as graphqlVite } from 'nitro-graphql/vite'
export default defineConfig({
plugins: [
graphqlVite(), // ⚠️ Must be before nitro()
nitro(),
],
nitro: {
modules: [
graphql({
framework: 'graphql-yoga',
}),
],
},
})
⚠️ Important: Thegraphql()plugin must be placed beforenitro()to prevent Vite from trying to parse GraphQL files as JavaScript.
🟢 Nuxt Project
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nitro-graphql/nuxt'],
nitro: {
graphql: {
framework: 'graphql-yoga',
},
},
})# server/graphql/schema.graphql
type Query {
hello: String!
greeting(name: String!): String!
}
type Mutation {
_empty: String
}// server/graphql/hello.resolver.ts
import { defineResolver } from 'nitro-graphql/define'
export const helloResolver = defineResolver({
Query: {
hello: () => 'Hello from GraphQL!',
greeting: (_, { name }) => `Hello, ${name}!`,
},
})pnpm dev🎉 That's it! Your GraphQL server is ready at:
- Endpoint:
http://localhost:3000/api/graphql - Playground:
http://localhost:3000/api/graphql(browser) - Health:
http://localhost:3000/api/graphql/health - Debug Dashboard:
http://localhost:3000/_nitro/graphql/debug(dev mode only)
Try these working examples:
| Example | Description | Demo |
|---|---|---|
| Nitro Basic | Standalone Nitro with GraphQL | pnpm playground:nitro |
| Vite + Nitro | Vite with Nitro GraphQL integration | cd playgrounds/vite && pnpm dev |
| Nuxt Integration | Full Nuxt app with client types | pnpm playground:nuxt |
| Apollo Federation | Federated GraphQL services | pnpm playground:federation |
| Drizzle ORM | Drizzle ORM + Zod validation integration | cd examples/drizzle-orm && pnpm dev |
Real-world test projects using nitro-graphql v2:
- Vite + Nitro + Rolldown - Testing with Rolldown bundler (Vite 7+)
- Vite + Nitro (Main) - Standard Vite integration tests
Let's create a complete user management system:
# server/graphql/users/user.graphql
type User {
id: ID!
name: String!
email: String!
createdAt: DateTime!
}
input CreateUserInput {
name: String!
email: String!
}
extend type Query {
users: [User!]!
user(id: ID!): User
}
extend type Mutation {
createUser(input: CreateUserInput!): User!
}// server/graphql/users/user.resolver.ts
import { defineQuery, defineMutation } from 'nitro-graphql/define'
export const userQueries = defineQuery({
users: async (_, __, { storage }) => {
return await storage.getItem('users') || []
},
user: async (_, { id }, { storage }) => {
const users = await storage.getItem('users') || []
return users.find(user => user.id === id)
}
})
export const userMutations = defineMutation({
createUser: async (_, { input }, { storage }) => {
const users = await storage.getItem('users') || []
const user = {
id: Date.now().toString(),
...input,
createdAt: new Date()
}
users.push(user)
await storage.setItem('users', users)
return user
}
})mutation {
createUser(input: {
name: "John Doe"
email: "john@example.com"
}) {
id
name
email
createdAt
}
}
query {
users {
id
name
email
}
}🎛️ Custom File Generation & Paths
Control which files are auto-generated and customize their output paths. Perfect for library development, monorepos, or custom project structures.
Disable all scaffold files for library/module development:
// nitro.config.ts
import graphql from 'nitro-graphql'
import { defineNitroConfig } from 'nitro/config'
export default defineNitroConfig({
modules: [
graphql({
framework: 'graphql-yoga',
scaffold: false, // Disable all scaffold files
clientUtils: false, // Disable client utilities
}),
],
})Control each file individually:
import graphql from 'nitro-graphql'
import { defineNitroConfig } from 'nitro/config'
export default defineNitroConfig({
modules: [
graphql({
framework: 'graphql-yoga',
// Scaffold files
scaffold: {
graphqlConfig: false, // Don't generate graphql.config.ts
serverSchema: true, // Generate server/graphql/schema.ts
serverConfig: true, // Generate server/graphql/config.ts
serverContext: false, // Don't generate server/graphql/context.ts
},
// Client utilities (Nuxt only)
clientUtils: {
index: true, // Generate app/graphql/index.ts
ofetch: false, // Don't generate ofetch wrappers
},
// SDK files
sdk: {
main: true, // Generate default SDK
external: true, // Generate external service SDKs
},
// Type files
types: {
server: true, // Generate server types
client: true, // Generate client types
external: true, // Generate external service types
},
}),
],
})Customize where files are generated:
import graphql from 'nitro-graphql'
import { defineNitroConfig } from 'nitro/config'
export default defineNitroConfig({
modules: [
graphql({
framework: 'graphql-yoga',
// Method 1: Global paths (affects all files)
paths: {
serverGraphql: 'src/server/graphql',
clientGraphql: 'src/client/graphql',
buildDir: '.build',
typesDir: '.build/types',
},
// Method 2: Specific file paths
scaffold: {
serverSchema: 'lib/graphql/schema.ts',
serverConfig: 'lib/graphql/config.ts',
},
sdk: {
main: 'app/graphql/organization/sdk.ts',
external: 'app/graphql/{serviceName}/client-sdk.ts',
},
types: {
server: 'types/graphql-server.d.ts',
client: 'types/graphql-client.d.ts',
},
}),
],
})Use placeholders in custom paths:
| Placeholder | Description | Example |
|---|---|---|
{serviceName} |
External service name | github, stripe |
{buildDir} |
Build directory | .nitro or .nuxt |
{rootDir} |
Root directory | /Users/you/project |
{framework} |
Framework name | nuxt or nitro |
{typesDir} |
Types directory | .nitro/types |
{serverGraphql} |
Server GraphQL dir | server/graphql |
{clientGraphql} |
Client GraphQL dir | app/graphql |
Example:
sdk: {
external: '{clientGraphql}/{serviceName}/sdk.ts'
}
// → app/graphql/github/sdk.ts
// → app/graphql/stripe/sdk.tsCustomize paths for individual external services:
export default defineNuxtConfig({
nitro: {
graphql: {
framework: 'graphql-yoga',
// Global default for all external services
sdk: {
external: 'app/graphql/{serviceName}/sdk.ts'
},
externalServices: [
{
name: 'github',
endpoint: 'https://api.github.com/graphql',
schema: 'https://api.github.com/graphql',
// GitHub-specific paths (override global config)
paths: {
sdk: 'app/graphql/organization/github-sdk.ts',
types: 'types/github.d.ts',
ofetch: 'app/graphql/organization/github-client.ts'
}
},
{
name: 'stripe',
endpoint: 'https://api.stripe.com/graphql',
schema: 'https://api.stripe.com/graphql',
// Stripe-specific paths
paths: {
sdk: 'app/graphql/payments/stripe-sdk.ts',
types: 'types/payments/stripe.d.ts',
// ofetch uses global config
}
},
{
name: 'shopify',
endpoint: 'https://api.shopify.com/graphql',
// No paths → uses global config
// → app/graphql/shopify/sdk.ts
}
]
}
}
})When resolving file paths, the system follows this priority order:
- Service-specific path (for external services):
service.paths.sdk - Category config:
sdk.externalorsdk.main - Global paths:
paths.clientGraphql - Framework defaults: Nuxt vs Nitro defaults
Example:
// Given this config:
{
paths: { clientGraphql: 'custom/graphql' },
sdk: { external: '{clientGraphql}/{serviceName}/sdk.ts' },
externalServices: [
{
name: 'github',
paths: { sdk: 'app/org/github-sdk.ts' } // ← Wins (priority 1)
},
{
name: 'stripe',
// Uses sdk.external (priority 2)
// → custom/graphql/stripe/sdk.ts
}
]
}Monorepo structure:
paths: {
serverGraphql: 'packages/api/src/graphql',
clientGraphql: 'packages/web/src/graphql',
typesDir: 'packages/types/src/generated',
}Multiple external service organizations:
externalServices: [
{
name: 'github',
paths: { sdk: 'app/graphql/vcs/github-sdk.ts' }
},
{
name: 'gitlab',
paths: { sdk: 'app/graphql/vcs/gitlab-sdk.ts' }
},
{
name: 'stripe',
paths: { sdk: 'app/graphql/billing/stripe-sdk.ts' }
}
]Library development (no scaffolding):
{
scaffold: false,
clientUtils: false,
sdk: { enabled: true }, // Only generate SDKs
types: { enabled: true }, // Only generate types
}🎭 Custom Directives
Create reusable GraphQL directives:
// server/graphql/directives/auth.directive.ts
import { defineDirective } from 'nitro-graphql/define'
export const authDirective = defineDirective({
name: 'auth',
locations: ['FIELD_DEFINITION'],
args: {
requires: { type: 'String', defaultValue: 'USER' }
},
transformer: (schema) => {
// Add authentication logic
}
})Use in schema:
type Query {
users: [User!]! @auth(requires: "ADMIN")
profile: User! @auth
}🌐 External GraphQL Services
Connect to multiple GraphQL APIs:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
graphql: {
framework: 'graphql-yoga',
externalServices: [
{
name: 'github',
schema: 'https://api.github.com/graphql',
endpoint: 'https://api.github.com/graphql',
headers: () => ({
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
})
}
]
}
}
})🔄 Apollo Federation
Build federated GraphQL services:
// nitro.config.ts
import graphql from 'nitro-graphql'
import { defineNitroConfig } from 'nitro/config'
export default defineNitroConfig({
modules: [
graphql({
framework: 'apollo-server',
federation: {
enabled: true,
serviceName: 'users-service',
},
}),
],
})
⚠️ Breaking Change: Utilities are NOT auto-imported. You must explicitly import them fromnitro-graphql/define:
import { defineResolver, defineQuery, defineMutation, defineField, defineDirective } from 'nitro-graphql/define'| Function | Purpose | Example |
|---|---|---|
defineResolver |
Complete resolvers | defineResolver({ Query: {...}, Mutation: {...} }) |
defineQuery |
Query-only resolvers | defineQuery({ users: () => [...] }) |
defineMutation |
Mutation-only resolvers | defineMutation({ createUser: (...) => {...} }) |
defineField |
Custom type resolvers | defineField({ User: { posts: (parent) => [...] } }) |
defineDirective |
Custom directives | defineDirective({ name: 'auth', ... }) |
defineGraphQLConfig |
GraphQL server config | defineGraphQLConfig({ maskedErrors: {...} }) |
defineSchema |
Schema with Zod integration | defineSchema({ Book: selectBookSchema }) |
Additional Utilities from nitro-graphql/utils:
createDefaultMaskError()- Error handler for ZodError and HTTPError (use indefineGraphQLConfig)
Automatic TypeScript types are generated:
- Server types:
#graphql/server- Use in resolvers and server code - Client types:
#graphql/client- Use in frontend components
// Server-side
import type { User, CreateUserInput } from '#graphql/server'
// Client-side
import type { GetUsersQuery, CreateUserMutation } from '#graphql/client'server/
├── graphql/
│ ├── schema.graphql # Main schema
│ ├── hello.resolver.ts # Basic resolvers
│ ├── users/
│ │ ├── user.graphql # User schema
│ │ └── user.resolver.ts # User resolvers
│ ├── directives/ # Custom directives
│ └── config.ts # Optional GraphQL config
⚠️ Important: Use named exports for all resolvers:// ✅ Correct export const userQueries = defineQuery({...}) // ❌ Deprecated export default defineQuery({...})
Common Issues
GraphQL endpoint returns 404
- ✅ Check
nitro-graphqlis in modules - ✅ Set
graphql.frameworkoption - ✅ Create at least one
.graphqlfile
Types not generating
- ✅ Restart dev server
- ✅ Check file naming:
*.graphql,*.resolver.ts - ✅ Verify exports are named exports
Import errors / "defineQuery is not defined"
- ✅ Requires explicit imports: Add
import { defineQuery } from 'nitro-graphql/define'to resolver files - ✅ Use correct import path:
nitro-graphql/define(notnitro-graphql) - ✅ Use named exports in resolvers
Example fix:
// Add this to the top of your resolver file
import { defineQuery, defineMutation } from 'nitro-graphql/define'
export const myQueries = defineQuery({ ... })Vite: "Parse failure: Expected ';', '}' or " on GraphQL files
- ✅ Add
graphql()plugin fromnitro-graphql/vite - ✅ Ensure
graphql()is placed beforenitro()in plugins array - ✅ Example:
import { graphql } from 'nitro-graphql/vite' export default defineConfig({ plugins: [ graphql(), // ← Must be first nitro(), ] })
RollupError: "[exportName]" is not exported by "[file].resolver.ts"
This error occurs when the resolver scanner can't find the expected export in your resolver file. Common causes:
-
Using default export instead of named export ❌
// ❌ WRONG - Will not be detected export default defineQuery({ users: () => [...] })
// ✅ CORRECT - Use named export export const userQueries = defineQuery({ users: () => [...] })
-
Not using a define function ❌
// ❌ WRONG - Plain object won't be detected export const resolvers = { Query: { users: () => [...] } }
// ✅ CORRECT - Use defineResolver, defineQuery, etc. export const userResolver = defineResolver({ Query: { users: () => [...] } })
-
File naming doesn't match export ❌
// ❌ File: uploadFile.resolver.ts but export is named differently export const fileUploader = defineMutation({...})
// ✅ CORRECT - Export name can be anything, as long as it uses a define function export const uploadFile = defineMutation({...}) export const fileUploader = defineMutation({...}) // Both work!
-
Syntax errors preventing parsing
- Check for TypeScript compilation errors in the file
- Ensure imports are valid
- Verify no missing brackets or syntax issues
How resolver scanning works:
- The module uses
oxc-parserto scan.resolver.tsfiles - It looks for named exports using these functions:
defineResolver- Complete resolver with Query, Mutation, etc.defineQuery- Query-only resolversdefineMutation- Mutation-only resolversdefineField- Custom type resolversdefineSubscription- Subscription resolversdefineDirective- Directive resolvers
- Only exports using these functions are included in the virtual module
Debugging steps:
- Check your resolver file uses named exports:
export const name = defineQuery({...}) - Verify you're using one of the define functions listed above
- Look for TypeScript/syntax errors in the file
- Restart the dev server after fixing
- If issues persist, simplify the resolver to test (single query)
This package powers production applications:
- Nitroping - Self-hosted push notification service
Once set up, you can ask Claude Code for help with:
"Add authentication to my GraphQL resolvers"
"Create a custom @auth directive for field-level permissions"
"Set up type generation for client-side queries"
"Add pagination to my users query"
"Connect to an external GitHub GraphQL API"
"Debug: my types aren't generating in .nitro/types/"
"Optimize resolver performance using DataLoader"
- Start specific: Include your framework (Nuxt/Nitro), version, and goal
- Reference docs: Mention "following nitro-graphql conventions" to align with best practices
- Show errors: Paste error messages for faster debugging
- Test iteratively: Run
pnpm devafter each change to verify
# Install dependencies
pnpm install
# Build module
pnpm build
# Watch mode
pnpm dev
# Run playgrounds
pnpm playground:nitro
pnpm playground:nuxt
pnpm playground:federation
# Lint
pnpm lintTip
Want to contribute? We believe you can play a role in the growth of this project!
- 💡 Share ideas via GitHub Issues
- 🐛 Report bugs with detailed information
- 📖 Improve docs - README, examples, guides
- 🔧 Code contributions - Bug fixes and features
- 🌟 Star the project to show support
- Performance benchmarks
- Video tutorials
- Database adapter guides
- VS Code extension
MIT License © 2023 productdevbook