The easiest way to add GraphQL to any Nitro application
🚀 Auto-discovery • 📝 Type Generation • 🎮 Apollo Sandbox • 🔧 Zero Config
GraphQL Yoga (recommended):
pnpm add nitro-graphql graphql-yoga graphql
Apollo Server:
pnpm add nitro-graphql @apollo/server @apollo/utils.withrequired @as-integrations/h3 graphql
// nitro.config.ts
import { defineNitroConfig } from 'nitropack/config'
export default defineNitroConfig({
modules: ['nitro-graphql'],
graphql: {
framework: 'graphql-yoga', // or 'apollo-server'
},
})
// 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
export const helloResolver = defineResolver({
Query: {
hello: () => 'Hello from GraphQL!',
greeting: (_, { name }) => `Hello, ${name}!`,
},
})
pnpm dev
🎉 That's it! Your GraphQL server is ready at:
http://localhost:3000/api/graphqlhttp://localhost:3000/api/graphql (browser)http://localhost:3000/api/graphql/healthTry these working examples:
| Example | Description | Demo |
|---|---|---|
| Nitro Basic | Standalone Nitro with GraphQL | pnpm playground:nitro |
| Nuxt Integration | Full Nuxt app with client types | pnpm playground:nuxt |
| Apollo Federation | Federated GraphQL services | pnpm playground:federation |
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
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
}
}
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
export default defineNitroConfig({
graphql: {
framework: 'graphql-yoga',
scaffold: false, // Disable all scaffold files
clientUtils: false, // Disable client utilities
}
})
Control each file individually:
export default defineNitroConfig({
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:
export default defineNitroConfig({
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.ts
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
}
]
}
}
})
When resolving file paths, the system follows this priority order:
service.paths.sdksdk.external or sdk.mainpaths.clientGraphqlExample:
// 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
}
Create reusable GraphQL directives:
// server/graphql/directives/auth.directive.ts
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
}
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}`
})
}
]
}
}
})
Build federated GraphQL services:
// nitro.config.ts
export default defineNitroConfig({
graphql: {
framework: 'apollo-server',
federation: {
enabled: true,
serviceName: 'users-service'
}
}
})
All utilities are auto-imported in resolver files:
| 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', ... }) |
Automatic TypeScript types are generated:
#graphql/server - Use in resolvers and server code#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({...})
GraphQL endpoint returns 404
nitro-graphql is in modulesgraphql.framework option.graphql fileTypes not generating
*.graphql, *.resolver.tsImport errors
nitro-graphql/utils/defineVite: "Parse failure: Expected ';', '}' or
graphql() plugin from nitro-graphql/vitegraphql() is placed before nitro() in plugins arrayimport { 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** ❌
```ts
// ❌ WRONG - Will not be detected
export default defineQuery({
users: () => [...]
})
// ✅ CORRECT - Use named export
export const userQueries = defineQuery({
users: () => [...]
})
// ❌ WRONG - Plain object won't be detected
export const resolvers = {
Query: {
users: () => [...]
}
}
// ✅ CORRECT - Use defineResolver, defineQuery, etc.
export const userResolver = defineResolver({
Query: {
users: () => [...]
}
})
// ❌ 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!
How resolver scanning works:
oxc-parser to scan .resolver.ts filesdefineResolver - Complete resolver with Query, Mutation, etc.defineQuery - Query-only resolversdefineMutation - Mutation-only resolversdefineType - Custom type resolversdefineSubscription - Subscription resolversdefineDirective - Directive resolversDebugging steps:
export const name = defineQuery({...})This package powers production applications:
Speed up development with Claude Code — AI-powered assistance for setting up and building with nitro-graphql.
Copy and paste these prompts into Claude Code to scaffold a complete GraphQL API.
💡 Tip: After pasting, Claude Code will execute step-by-step and validate each action.
## GOAL
Set up nitro-graphql in this Nuxt project with a User management GraphQL API.
## PREREQUISITES
Check if this is a Nuxt project by looking for nuxt.config.ts in the root.
## STEP 1: INSTALL DEPENDENCIES
Action: Run this command
Command: pnpm add nitro-graphql graphql-yoga graphql
Validation: Check package.json contains these packages
## STEP 2: CONFIGURE NUXT
File: nuxt.config.ts
Action: EDIT (add to existing config, don't replace)
Add these properties:
export default defineNuxtConfig({
modules: ['nitro-graphql/nuxt'], // Add this module
nitro: {
graphql: {
framework: 'graphql-yoga',
},
},
})
Validation: Check the file has modules array and nitro.graphql config
## STEP 3: CREATE SCHEMA
File: server/graphql/schema.graphql
Action: CREATE NEW FILE (create server/graphql/ directory if needed)
Content:
type User {
id: ID!
name: String!
email: String!
}
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
_empty: String
}
Validation: File should be in server/graphql/ directory
## STEP 4: CREATE CONTEXT (Optional but recommended)
File: server/graphql/context.ts
Action: CREATE NEW FILE (auto-generated on first run, but create manually for clarity)
Content:
// Extend H3 event context with custom properties
declare module 'h3' {
interface H3EventContext {
// Add your custom context properties here
// Example:
// db?: Database
// auth?: { userId: string }
}
}
Note: This file lets you add custom properties to resolver context
Validation: File exists in server/graphql/
## STEP 5: CREATE CONFIG (Optional)
File: server/graphql/config.ts
Action: CREATE NEW FILE (auto-generated, customize if needed)
Content:
// Custom GraphQL Yoga configuration
export default defineGraphQLConfig({
// Custom context enhancer, plugins, etc.
// See: https://the-guild.dev/graphql/yoga-server/docs
})
Note: Use this to customize GraphQL Yoga options
Validation: File exists in server/graphql/
## STEP 6: CREATE RESOLVERS
File: server/graphql/users.resolver.ts
Action: CREATE NEW FILE
Content:
// ⚠️ CRITICAL: Use NAMED EXPORTS (not default export)
export const userQueries = defineQuery({
users: async (_, __, context) => {
// context is H3EventContext - access event, storage, etc.
return [
{ id: '1', name: 'John Doe', email: 'john@example.com' },
{ id: '2', name: 'Jane Smith', email: 'jane@example.com' }
]
},
user: async (_, { id }, context) => {
// Third parameter is context (H3EventContext)
const users = [
{ id: '1', name: 'John Doe', email: 'john@example.com' },
{ id: '2', name: 'Jane Smith', email: 'jane@example.com' }
]
return users.find(u => u.id === id) || null
}
})
Validation: File ends with .resolver.ts and uses named export
## STEP 7: START DEV SERVER
Command: pnpm dev
Expected Output: Server starts on http://localhost:3000
Wait for: "Nitro built in X ms" message
Note: context.ts and config.ts will auto-generate if you skipped steps 4-5
## VALIDATION CHECKLIST
- [ ] Navigate to http://localhost:3000/api/graphql - should show GraphQL playground
- [ ] Health check: http://localhost:3000/api/graphql/health - should return OK
- [ ] Run this query in playground:
```graphql
query {
users {
id
name
email
}
}
Expected: Returns 2 users
server/
graphql/
schema.graphql ← GraphQL type definitions
context.ts ← H3 event context augmentation (optional)
config.ts ← GraphQL Yoga config (optional)
users.resolver.ts ← Query resolvers
.nuxt/
types/
nitro-graphql-server.d.ts ← Auto-generated types
graphql.config.ts ← Auto-generated (for IDE tooling)
❌ DO NOT use default exports in resolvers Wrong: export default defineQuery({...}) Right: export const userQueries = defineQuery({...})
❌ DO NOT name files without .resolver.ts extension Wrong: users.ts or user-resolver.ts Right: users.resolver.ts or user.resolver.ts
✅ DO use named exports for all resolvers ✅ DO place files in server/graphql/ directory ✅ DO restart dev server if types don't generate
Issue: "GraphQL endpoint returns 404" Fix: Ensure 'nitro-graphql/nuxt' is in modules array (not just 'nitro-graphql')
Issue: "defineQuery is not defined" Fix: Restart dev server - auto-imports need to regenerate
Issue: "Types not generating" Fix: Check .nuxt/types/nitro-graphql-server.d.ts exists, if not restart dev server
Issue: "Module not found: nitro-graphql" Fix: Run pnpm install again, check package.json has the package
Now implement this setup step-by-step.
</details>
<details>
<summary>⚡ <strong>Nitro Project</strong></summary>
Set up nitro-graphql in this Nitro project following these exact specifications:
INSTALLATION:
CONFIGURATION (nitro.config.ts): import { defineNitroConfig } from 'nitro/config'
export default defineNitroConfig({ modules: 'nitro-graphql', graphql: { framework: 'graphql-yoga', }, })
SCHEMA (server/graphql/schema.graphql): type Product { id: ID! name: String! price: Float! }
input CreateProductInput { name: String! price: Float! }
type Query { products: Product!! product(id: ID!): Product }
type Mutation { createProduct(input: CreateProductInput!): Product! }
RESOLVERS (server/graphql/products.resolver.ts): // Use NAMED EXPORTS only export const productQueries = defineQuery({ products: async (, __, context) => { // Access H3 event context const products = await context.storage?.getItem('products') || return products }, product: async (, { id }, context) => { const products = await context.storage?.getItem('products') || return products.find(p => p.id === id) } })
export const productMutations = defineMutation({ createProduct: async (_, { input }, context) => { const products = await context.storage?.getItem('products') || const product = { id: Date.now().toString(), ...input } products.push(product) await context.storage?.setItem('products', products) return product } })
KEY RULES:
Now implement this setup.
</details>
<details>
<summary>🎮 <strong>Apollo Server Setup</strong></summary>
Set up nitro-graphql with Apollo Server following these exact specifications:
INSTALLATION:
CONFIGURATION (nitro.config.ts): import { defineNitroConfig } from 'nitro/config'
export default defineNitroConfig({ modules: 'nitro-graphql', graphql: { framework: 'apollo-server', }, })
SCHEMA (server/graphql/schema.graphql): type Book { id: ID! title: String! author: String! }
type Query { books: Book!! book(id: ID!): Book }
type Mutation { addBook(title: String!, author: String!): Book! }
RESOLVERS (server/graphql/books.resolver.ts): // IMPORTANT: Use NAMED EXPORTS export const bookResolver = defineResolver({ Query: { books: async () => { return { id: '1', title: '1984', author: 'George Orwell' } }, book: async (, { id }) => { return { id, title: '1984', author: 'George Orwell' } } }, Mutation: { addBook: async (, { title, author }) => { return { id: Date.now().toString(), title, author } } } })
KEY RULES:
Now implement this setup.
</details>
<details>
<summary>🔄 <strong>Add Feature to Existing Setup</strong></summary>
Add a complete blog posts feature to my nitro-graphql API following these specifications:
SCHEMA (server/graphql/posts/post.graphql): type Post { id: ID! title: String! content: String! authorId: ID! createdAt: String! }
input CreatePostInput { title: String! content: String! authorId: ID! }
input UpdatePostInput { title: String content: String }
extend type Query { posts(limit: Int = 10, offset: Int = 0): Post!! post(id: ID!): Post }
extend type Mutation { createPost(input: CreatePostInput!): Post! updatePost(id: ID!, input: UpdatePostInput!): Post deletePost(id: ID!): Boolean! }
RESOLVERS (server/graphql/posts/post.resolver.ts): // Use NAMED EXPORTS export const postQueries = defineQuery({ posts: async (, { limit, offset }, context) => { const posts = await context.storage?.getItem('posts') || return posts.slice(offset, offset + limit) }, post: async (, { id }, context) => { const posts = await context.storage?.getItem('posts') || return posts.find(p => p.id === id) || null } })
export const postMutations = defineMutation({ createPost: async (, { input }, context) => { const posts = await context.storage?.getItem('posts') || const post = { id: Date.now().toString(), ...input, createdAt: new Date().toISOString() } posts.push(post) await context.storage?.setItem('posts', posts) return post }, updatePost: async (, { id, input }, context) => { const posts = await context.storage?.getItem('posts') || const index = posts.findIndex(p => p.id === id) if (index === -1) return null postsindex = { ...postsindex, ...input } await context.storage?.setItem('posts', posts) return postsindex }, deletePost: async (_, { id }, context) => { const posts = await context.storage?.getItem('posts') || const filtered = posts.filter(p => p.id !== id) await context.storage?.setItem('posts', filtered) return filtered.length < posts.length } })
TYPE USAGE: After dev server restarts, types are auto-generated in:
Import types: import type { Post, CreatePostInput } from '#graphql/server'
KEY RULES:
Now implement this feature.
</details>
### 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
```bash
# 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
!TIPWant to contribute? We believe you can play a role in the growth of this project!
MIT License © 2023 productdevbook