Skip to content

A standalone Nitro module that integrates GraphQL servers into any Nitro application with automatic type generation, file watching, and seamless framework integration.

License

Notifications You must be signed in to change notification settings

productdevbook/nitro-graphql

Repository files navigation

Nitro GraphQL Logo

Nitro GraphQL

npm version Beta Status npm downloads bundle License Documentation

The easiest way to add GraphQL to any Nitro application

🚀 Auto-discovery • 📝 Type Generation • 🎮 Apollo Sandbox • 🔧 Zero Config

📚 DocumentationQuick StartExamplesCommunity

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.


🎥 Watch & Learn

✨ Why Nitro GraphQL?

  • 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

🆕 What's New in v2.0 Beta

  • 🚀 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

🚀 Quick Start

1. Install

GraphQL Yoga (recommended):

pnpm add nitro-graphql@beta graphql-yoga graphql graphql-config

Apollo Server:

pnpm add nitro-graphql@beta @apollo/server graphql graphql-config

2. Configure

🔧 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: The graphql() plugin must be placed before nitro() 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',
    },
  },
})

3. Create Your Schema

# server/graphql/schema.graphql
type Query {
  hello: String!
  greeting(name: String!): String!
}

type Mutation {
  _empty: String
}

4. Add Resolvers

// server/graphql/hello.resolver.ts
import { defineResolver } from 'nitro-graphql/define'

export const helloResolver = defineResolver({
  Query: {
    hello: () => 'Hello from GraphQL!',
    greeting: (_, { name }) => `Hello, ${name}!`,
  },
})

5. Start Development

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)

🎮 Examples

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

🧪 Test Projects

Real-world test projects using nitro-graphql v2:

🏗️ Building Your First Feature

Let's create a complete user management system:

1. Define Schema

# 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!
}

2. Create Resolvers

// 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
  }
})

3. Test in Apollo Sandbox

mutation {
  createUser(input: {
    name: "John Doe"
    email: "[email protected]"
  }) {
    id
    name
    email
    createdAt
  }
}

query {
  users {
    id
    name
    email
  }
}

🚀 Advanced Features

🎛️ Custom File Generation & Paths

Control which files are auto-generated and customize their output paths. Perfect for library development, monorepos, or custom project structures.

Library Mode

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
    }),
  ],
})

Fine-Grained Control

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
      },
    }),
  ],
})

Custom Paths

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',
      },
    }),
  ],
})

Path Placeholders

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.ts

Service-Specific Paths

Customize 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
        }
      ]
    }
  }
})

Path Resolution Priority

When resolving file paths, the system follows this priority order:

  1. Service-specific path (for external services): service.paths.sdk
  2. Category config: sdk.external or sdk.main
  3. Global paths: paths.clientGraphql
  4. 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
    }
  ]
}

Use Cases

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',
      },
    }),
  ],
})

📖 Documentation

Core Utilities

⚠️ Breaking Change: Utilities are NOT auto-imported. You must explicitly import them from nitro-graphql/define:

import { defineResolver, defineQuery, defineMutation, defineType, 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: (...) => {...} })
defineType Custom type resolvers defineType({ 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 in defineGraphQLConfig)

Type Generation

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'

Project Structure

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({...})

🚨 Troubleshooting

Common Issues

GraphQL endpoint returns 404

  • ✅ Check nitro-graphql is in modules
  • ✅ Set graphql.framework option
  • ✅ Create at least one .graphql file

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 (not nitro-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 from nitro-graphql/vite
  • ✅ Ensure graphql() is placed before nitro() 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:

  1. 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: () => [...]
    })
  2. 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: () => [...]
      }
    })
  3. 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!
  4. 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-parser to scan .resolver.ts files
  • It looks for named exports using these functions:
    • defineResolver - Complete resolver with Query, Mutation, etc.
    • defineQuery - Query-only resolvers
    • defineMutation - Mutation-only resolvers
    • defineType - Custom type resolvers
    • defineSubscription - Subscription resolvers
    • defineDirective - Directive resolvers
  • Only exports using these functions are included in the virtual module

Debugging steps:

  1. Check your resolver file uses named exports: export const name = defineQuery({...})
  2. Verify you're using one of the define functions listed above
  3. Look for TypeScript/syntax errors in the file
  4. Restart the dev server after fixing
  5. If issues persist, simplify the resolver to test (single query)

🌟 Production Usage

This package powers production applications:

  • Nitroping - Self-hosted push notification service

Working with Your GraphQL API

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"

Tips for Better Results

  • 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 dev after each change to verify

🛠️ Development

# 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 lint

💬 Community

Tip

Want to contribute? We believe you can play a role in the growth of this project!

Ways to Contribute

  • 💡 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

Help Wanted

  • Performance benchmarks
  • Video tutorials
  • Database adapter guides
  • VS Code extension

Sponsors

License

MIT License © 2023 productdevbook

About

A standalone Nitro module that integrates GraphQL servers into any Nitro application with automatic type generation, file watching, and seamless framework integration.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Contributors 3

  •  
  •  
  •