The easiest way to add GraphQL to any Nitro application
๐ Auto-discovery โข ๐ Type Generation โข ๐ฎ Apollo Sandbox โข ๐ง Zero Config
Quick Start โข Examples โข Documentation โข Community
- 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
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 Project
// nitro.config.ts
import { defineNitroConfig } from 'nitropack/config'
export default defineNitroConfig({
modules: ['nitro-graphql'],
graphql: {
framework: 'graphql-yoga', // or 'apollo-server'
},
})
๐ข 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
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
Try 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
}
}
๐ญ Custom Directives
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
}
๐ 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
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:
- 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-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
- โ
Use correct path:
nitro-graphql/utils/define
- โ Use named exports in resolvers
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-parser
to scan.resolver.ts
files - It looks for named exports using these functions:
defineResolver
- Complete resolver with Query, Mutation, etc.defineQuery
- Query-only resolversdefineMutation
- Mutation-only resolversdefineType
- 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
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.
๐ข Nuxt Project
## 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
- Check .nuxt/types/nitro-graphql-server.d.ts exists (types auto-generated)
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
- Add mutations: "Add createUser and deleteUser mutations with H3 storage"
- Extend context: "Add database connection to context.ts and use it in resolvers"
- Use types: "Import and use TypeScript types from #graphql/server in resolvers"
- Add auth: "Add authentication middleware using context in resolvers"
- Custom config: "Configure GraphQL Yoga plugins in config.ts"
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:
- Run: pnpm add nitro-graphql graphql-yoga graphql
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:
- Files: *.graphql for schemas, *.resolver.ts for resolvers
- MUST use named exports (not default export)
- defineQuery and defineMutation are auto-imported
- Context is the third parameter (access H3 event context)
- Endpoint: http://localhost:3000/api/graphql
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:
- Run: pnpm add nitro-graphql @apollo/server @apollo/utils.withrequired @as-integrations/h3 graphql
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:
- framework: 'apollo-server' in config
- defineResolver for complete resolver maps
- Named exports required (export const name = ...)
- Apollo Sandbox: http://localhost:3000/api/graphql
- Supports Apollo Federation with federation: { enabled: true }
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 posts[index] = { ...posts[index], ...input } await context.storage?.setItem('posts', posts) return posts[index] }, 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:
- .nitro/types/nitro-graphql-server.d.ts (server types)
- .nuxt/types/nitro-graphql-server.d.ts (for Nuxt)
Import types: import type { Post, CreatePostInput } from '#graphql/server'
KEY RULES:
- Use "extend type" to add to existing Query/Mutation
- Named exports required
- Context has H3 event properties
- Types auto-generate on file changes
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
[!TIP] 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