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

npm version npm downloads bundle License

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

🚀 Quick Start📖 Documentation🎮 Playground💬 Community


"GraphQL is not just a query language; it's a new way to think about APIs and client-server interaction."

✨ Features

  • 🚀 Multi-Framework Support: Works with GraphQL Yoga and Apollo Server
  • 🔧 Auto-Discovery: Automatically scans and loads GraphQL schema and resolver files
  • 📝 Type Generation: Automatic TypeScript type generation from GraphQL schemas (server & client)
  • 🎮 Apollo Sandbox: Built-in GraphQL playground for development
  • 🏥 Health Check: Built-in health check endpoint
  • 🔌 Universal Compatibility: Works with any Nitro-based application (Nuxt, standalone Nitro, etc.)
  • 🎯 Zero Configuration: Sensible defaults with optional customization
  • 📂 File-Based Organization: Domain-driven resolver and schema organization
  • 🔄 Hot Reload: Development mode with automatic schema and resolver updates
  • 📦 Optimized Bundling: Smart chunking and dynamic imports for production
  • 🌐 Nuxt Integration: First-class Nuxt.js support with dedicated module

🚀 Quick Start

Step 1: Installation

Choose your GraphQL framework and install required dependencies:

For GraphQL Yoga:

# npm
npm install nitro-graphql graphql-yoga graphql

# pnpm (recommended)
pnpm add nitro-graphql graphql-yoga graphql

# yarn
yarn add nitro-graphql graphql-yoga graphql

For Apollo Server:

# npm
npm install nitro-graphql @apollo/server graphql

# pnpm (recommended)
pnpm add nitro-graphql @apollo/server graphql

# yarn
yarn add nitro-graphql @apollo/server graphql

Step 2: Setup Your Project

🔧 For Standalone Nitro Projects
  1. Update your nitro.config.ts:
import { defineNitroConfig } from 'nitropack/config'

export default defineNitroConfig({
  modules: ['nitro-graphql'],
  graphql: {
    framework: 'graphql-yoga', // or 'apollo-server'
  },
})
  1. Create the GraphQL directory structure:
mkdir -p server/graphql
🟢 For Nuxt.js Projects
  1. Update your nuxt.config.ts:
export default defineNuxtConfig({
  modules: [
    'nitro-graphql/nuxt',
  ],
  nitro: {
    modules: ['nitro-graphql'],
    graphql: {
      framework: 'graphql-yoga',
    },
  },
})
  1. Create the GraphQL directory structure:
mkdir -p server/graphql

Step 3: Create Your First Schema

Create your main schema file:

# server/graphql/schema.graphql
scalar DateTime
scalar JSON

type Query {
  hello: String!
  greeting(name: String!): String!
}

type Mutation {
  _empty: String
}

Step 4: Create Your First Resolver

Create a resolver for your queries:

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

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

// You can also export multiple resolvers from the same file
export const additionalResolver = defineResolver({
  Query: {
    // Additional query resolvers
  },
})

Step 5: Start Your Server

# For Nitro
pnpm dev

# For Nuxt
pnpm dev

Step 6: Test Your GraphQL API

🎉 That's it! Your GraphQL server is now running at:

  • GraphQL Endpoint: http://localhost:3000/api/graphql
  • Apollo Sandbox: http://localhost:3000/api/graphql (in browser)
  • Health Check: http://localhost:3000/api/graphql/health

📖 Documentation

Project Structure

The module uses a domain-driven file structure under server/graphql/:

server/
├── graphql/
│   ├── schema.graphql              # Main schema with scalars and base types
│   ├── hello.resolver.ts           # Global resolvers (use named exports)
│   ├── users/
│   │   ├── user.graphql           # User schema definitions
│   │   ├── user-queries.resolver.ts # User query resolvers (use named exports)
│   │   └── create-user.resolver.ts  # User mutation resolvers (use named exports)
│   ├── posts/
│   │   ├── post.graphql           # Post schema definitions
│   │   ├── post-queries.resolver.ts # Post query resolvers (use named exports)
│   │   └── create-post.resolver.ts  # Post mutation resolvers (use named exports)
│   └── config.ts                   # Optional GraphQL configuration
│   └── schema.ts                   # Changing Special Return types

Tip

New Named Export Pattern: The module now supports named exports for GraphQL resolvers, allowing you to export multiple resolvers from a single file. This provides better organization and flexibility in structuring your resolver code.

Building Your First Feature

Let's create a complete user management feature:

👤 Step 1: Define User 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!
}
🔍 Step 2: Create Query Resolvers
// server/graphql/users/user-queries.resolver.ts
import { defineQuery } from 'nitro-graphql/utils/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)
  }
})

// You can also split queries into separate named exports
export const additionalUserQueries = defineQuery({
  userCount: async (_, __, { storage }) => {
    const users = await storage.getItem('users') || []
    return users.length
  },
})
✏️ Step 3: Create Mutation Resolvers
// server/graphql/users/create-user.resolver.ts
import { defineMutation } from 'nitro-graphql/utils/define'

export const createUserMutation = 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
  }
})

// You can also export multiple mutations from the same file
export const updateUserMutation = defineMutation({
  updateUser: async (_, { id, input }, { storage }) => {
    const users = await storage.getItem('users') || []
    const userIndex = users.findIndex(user => user.id === id)
    if (userIndex === -1)
      throw new Error('User not found')

    users[userIndex] = { ...users[userIndex], ...input }
    await storage.setItem('users', users)
    return users[userIndex]
  }
})
🧪 Step 4: Test Your Feature

Open Apollo Sandbox at http://localhost:3000/api/graphql and try:

# Create a user
mutation {
  createUser(input: {
    name: "John Doe"
    email: "john@example.com"
  }) {
    id
    name
    email
    createdAt
  }
}

# Query users
query {
  users {
    id
    name
    email
  }
}

Type Generation

The module automatically generates TypeScript types for you:

  • Server types: .nitro/types/nitro-graphql-server.d.ts
  • Client types: .nitro/types/nitro-graphql-client.d.ts
  • Auto-imports: Available for defineResolver and other utilities

Your IDE will automatically provide type safety and autocomplete!

Using Generated Types

You can import and use the generated types in your code:

Client-side types (#graphql/client):

// Import generated types for queries, mutations, and operations
import type { GetUsersQuery, CreateUserInput } from '#graphql/client'

// Use in Vue components
const users = ref<GetUsersQuery['users']>([])

// Use in composables
export function useUsers() {
  const createUser = async (input: CreateUserInput) => {
    // Type-safe input
  }
}

Server-side types (#graphql/server):

// Import generated types and interfaces
import type { User, Post, CreateUserInput } from '#graphql/server'

// Use types in your server code
function validateUser(user: User): boolean {
  return user.email.includes('@')
}

// Use in data layer
async function getUserPosts(user: User): Promise<Post[]> {
  // user is fully typed with all fields
  return await db.posts.findMany({ where: { authorId: user.id } })
}

// Use input types for validation
function validateCreateUserInput(input: CreateUserInput): void {
  if (!input.email || !input.name) {
    throw new Error('Email and name are required')
  }
}

These imports provide full TypeScript support with autocompletion, type checking, and IntelliSense in your IDE.

Tip

Nitro Auto-Imports: Thanks to Nitro's auto-import feature, you don't need to manually import defineResolver, defineQuery, defineMutation, and other utilities in your resolver files. They're available globally! However, if you prefer explicit imports, you can use:

import { defineResolver } from 'nitro-graphql/utils/define'

Configuration

⚙️ Runtime Configuration
// nitro.config.ts
export default defineNitroConfig({
  modules: ['nitro-graphql'],
  graphql: {
    framework: 'graphql-yoga', // or 'apollo-server'
  },
  runtimeConfig: {
    graphql: {
      endpoint: {
        graphql: '/api/graphql', // GraphQL endpoint
        healthCheck: '/api/graphql/health' // Health check endpoint
      },
      playground: true, // Enable Apollo Sandbox
    }
  }
})
🔧 Advanced Configuration
// server/graphql/config.ts
import { defineGraphQLConfig } from 'nitro-graphql/utils/define'

export default defineGraphQLConfig({
  // Custom GraphQL Yoga or Apollo Server configuration
  plugins: [
    // Add custom plugins
  ],
  context: async ({ request }) => {
    // Enhanced context with custom properties
    return {
      user: await authenticateUser(request),
      db: await connectDatabase(),
    }
  },
})

🎮 Playground

Try out the examples:

Both examples include working GraphQL schemas, resolvers, and demonstrate the module's capabilities.

🔧 API Reference

Core Utilities

Note

Auto-Import Available: All utilities are automatically imported in your resolver files thanks to Nitro's auto-import feature. You can use them directly without import statements, or use explicit imports if you prefer:

import { defineMutation, defineQuery, defineResolver } from 'nitro-graphql/utils/define'

Important

Named Exports Required: All GraphQL resolvers must now use named exports instead of default exports. This allows you to export multiple resolvers from a single file, providing better organization and flexibility. For example:

// ✅ Correct - Named exports
export const userQueries = defineQuery({ ... })
export const userMutations = defineMutation({ ... })

// ❌ Incorrect - Default exports (deprecated)
export default defineQuery({ ... })
defineResolver - Define complete resolvers
import { defineResolver } from 'nitro-graphql/utils/define'

export const mainResolver = defineResolver({
  Query: {
    // Query resolvers
  },
  Mutation: {
    // Mutation resolvers
  },
  // Custom type resolvers
})

// You can also export multiple resolvers from the same file
export const additionalResolver = defineResolver({
  Query: {
    // Additional query resolvers
  },
})
defineQuery - Define only Query resolvers
import { defineQuery } from 'nitro-graphql/utils/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)
  }
})

// You can also split queries into separate named exports
export const userStatsQueries = defineQuery({
  userCount: async (_, __, { storage }) => {
    const users = await storage.getItem('users') || []
    return users.length
  },
})
defineMutation - Define only Mutation resolvers
import { defineMutation } from 'nitro-graphql/utils/define'

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

// You can also export multiple mutations from the same file
export const userUpdateMutations = defineMutation({
  updateUser: async (_, { id, input }, { storage }) => {
    const users = await storage.getItem('users') || []
    const userIndex = users.findIndex(user => user.id === id)
    if (userIndex === -1)
      throw new Error('User not found')

    users[userIndex] = { ...users[userIndex], ...input }
    await storage.setItem('users', users)
    return users[userIndex]
  }
})
defineSubscription - Define Subscription resolvers
import { defineSubscription } from 'nitro-graphql/utils/define'

export const userSubscriptions = defineSubscription({
  userAdded: {
    subscribe: () => pubsub.asyncIterator('USER_ADDED'),
  },
  postUpdated: {
    subscribe: withFilter(
      () => pubsub.asyncIterator('POST_UPDATED'),
      (payload, variables) => payload.postUpdated.id === variables.postId
    ),
  }
})

// You can also export multiple subscriptions from the same file
export const notificationSubscriptions = defineSubscription({
  notificationAdded: {
    subscribe: () => pubsub.asyncIterator('NOTIFICATION_ADDED'),
  },
})
defineType - Define custom type resolvers
import { defineType } from 'nitro-graphql/utils/define'

export const userTypes = defineType({
  User: {
    posts: async (parent, _, { storage }) => {
      const posts = await storage.getItem('posts') || []
      return posts.filter(post => post.authorId === parent.id)
    },
    fullName: parent => `${parent.firstName} ${parent.lastName}`,
  },
})

// You can also export multiple type resolvers from the same file
export const postTypes = defineType({
  Post: {
    author: async (parent, _, { storage }) => {
      const users = await storage.getItem('users') || []
      return users.find(user => user.id === parent.authorId)
    },
  },
})
defineSchema - Define custom schema with validation

You can override schema types if needed. StandardSchema supported — Zod, Valibot, anything works:

# server/graphql/schema.ts
import { defineSchema } from 'nitro-graphql/utils/define'
import { z } from 'zod'

export default defineSchema({
  Todo: z.object({
    id: z.string(),
    title: z.string(),
    completed: z.boolean(),
    createdAt: z.date(),
  }),
  User: z.object({
    id: z.string(),
    name: z.string(),
    email: z.string().email(),
    age: z.number().min(0),
  }),
})

With Drizzle Schema:

import { defineSchema } from 'nitro-graphql/utils/define'
import { z } from 'zod'
import { userSchema } from './drizzle/user'

export default defineSchema({
  Todo: z.object({
    id: z.string(),
    title: z.string(),
  }),
  User: userSchema, // Import from Drizzle schema
})

🚨 Troubleshooting

Common Issues

GraphQL endpoint returns 404

Solution: Make sure you have:

  1. Added nitro-graphql to your modules
  2. Set the graphql.framework option
  3. Created at least one schema file

Types not generating

Solution:

  1. Restart your dev server
  2. Check that your schema files end with .graphql
  3. Verify your resolver files end with .resolver.ts

Hot reload not working

Solution:

  1. Make sure you're in development mode
  2. Check file naming conventions
  3. Restart the dev server

Import errors with utilities

Solution:

// ❌ Incorrect import path
import { defineResolver } from 'nitro-graphql'

// ✅ Correct import path
import { defineResolver } from 'nitro-graphql/utils/define'

Export pattern errors

Solution:

// ❌ Incorrect - Default exports (deprecated)
export default defineResolver({ ... })

// ✅ Correct - Named exports
export const myResolver = defineResolver({ ... })
export const anotherResolver = defineResolver({ ... })

🌟 Framework Support

GraphQL Yoga
// nitro.config.ts
export default defineNitroConfig({
  graphql: {
    framework: 'graphql-yoga',
  },
})
Apollo Server
// nitro.config.ts
export default defineNitroConfig({
  graphql: {
    framework: 'apollo-server',
  },
})

🔥 Advanced Features

Custom Scalars
// server/graphql/scalars/DateTime.resolver.ts
import { GraphQLScalarType } from 'graphql'
import { Kind } from 'graphql/language'
import { defineResolver } from 'nitro-graphql/utils/define'

export const dateTimeScalar = defineResolver({
  DateTime: new GraphQLScalarType({
    name: 'DateTime',
    serialize: (value: Date) => value.toISOString(),
    parseValue: (value: string) => new Date(value),
    parseLiteral: (ast) => {
      if (ast.kind === Kind.STRING) {
        return new Date(ast.value)
      }
      return null
    }
  })
})

// You can also export multiple scalars from the same file
export const jsonScalar = defineResolver({
  JSON: new GraphQLScalarType({
    name: 'JSON',
    serialize: value => value,
    parseValue: value => value,
    parseLiteral: (ast) => {
      if (ast.kind === Kind.STRING) {
        return JSON.parse(ast.value)
      }
      return null
    }
  })
})
Error Handling
// server/graphql/users/user-queries.resolver.ts
import { defineQuery } from 'nitro-graphql/utils/define'

export const userQueries = defineQuery({
  user: async (_, { id }, { storage }) => {
    try {
      const user = await storage.getItem(`user:${id}`)
      if (!user) {
        throw new Error(`User with id ${id} not found`)
      }
      return user
    }
    catch (error) {
      console.error('Error fetching user:', error)
      throw error
    }
  }
})

// You can also export additional error handling queries
export const safeUserQueries = defineQuery({
  userSafe: async (_, { id }, { storage }) => {
    try {
      const user = await storage.getItem(`user:${id}`)
      return user || null // Return null instead of throwing
    }
    catch (error) {
      console.error('Error fetching user:', error)
      return null
    }
  }
})
Nuxt Integration

For Nuxt.js applications, the module provides enhanced integration:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    'nitro-graphql/nuxt',
  ],
  nitro: {
    modules: ['nitro-graphql'],
    graphql: {
      framework: 'graphql-yoga',
    },
  },
})

Client-side GraphQL files are automatically detected in the app/graphql/ directory.

Client-Side Usage

The module automatically generates a GraphQL SDK and provides type-safe client access for frontend usage.

📁 GraphQL File Structure

Create your GraphQL queries and mutations in the app/graphql/ directory:

app/
├── graphql/
│   ├── queries.graphql         # GraphQL queries
│   ├── mutations.graphql       # GraphQL mutations
│   └── subscriptions.graphql   # GraphQL subscriptions (optional)
🔥 Creating GraphQL Files

Query File Example:

# app/graphql/queries.graphql
query GetUsers {
  users {
    id
    name
    email
    createdAt
  }
}

query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
    createdAt
  }
}

Mutation File Example:

# app/graphql/mutations.graphql
mutation CreateUser($input: CreateUserInput!) {
  createUser(input: $input) {
    id
    name
    email
    createdAt
  }
}

mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
  updateUser(id: $id, input: $input) {
    id
    name
    email
    createdAt
  }
}
Using the Generated SDK

The module automatically generates a type-safe SDK based on your GraphQL files:

// The SDK is automatically generated and available as an import
import { createGraphQLClient } from '#graphql/client'

// Create a client instance
const client = createGraphQLClient({
  endpoint: '/api/graphql',
  headers: {
    Authorization: 'Bearer your-token-here'
  }
})

// Use the generated methods with full type safety
const getUsersData = await client.GetUsers()
console.log(getUsersData.users) // Fully typed response

const newUser = await client.CreateUser({
  input: {
    name: 'John Doe',
    email: 'john@example.com'
  }
})
console.log(newUser.createUser) // Fully typed response
🎯 Basic Usage Examples

Fetching Data:

// Import the generated client
import { createGraphQLClient } from '#graphql/client'

const client = createGraphQLClient()

// Query users
const { users } = await client.GetUsers()
console.log(users) // Array of User objects with full typing

// Query specific user
const { user } = await client.GetUser({ id: '123' })
console.log(user) // User object or null

Creating Data:

// Create a new user
const { createUser } = await client.CreateUser({
  input: {
    name: 'Jane Doe',
    email: 'jane@example.com'
  }
})
console.log(createUser) // Newly created user with full typing

Error Handling:

try {
  const { users } = await client.GetUsers()
  console.log(users)
}
catch (error) {
  console.error('GraphQL Error:', error)
  // Handle GraphQL errors appropriately
}
🔧 Client Configuration
import { createGraphQLClient } from '#graphql/client'

// Basic configuration
const client = createGraphQLClient({
  endpoint: '/api/graphql',
  headers: {
    'Authorization': 'Bearer your-token',
    'X-Client-Version': '1.0.0'
  },
  timeout: 10000
})

// Advanced configuration with dynamic headers
const client = createGraphQLClient({
  endpoint: '/api/graphql',
  headers: async () => {
    const token = await getAuthToken()
    return {
      'Authorization': token ? `Bearer ${token}` : '',
      'X-Request-ID': crypto.randomUUID()
    }
  },
  retry: 3,
  timeout: 30000
})

🛠️ Development

Scripts

  • pnpm build - Build the module
  • pnpm dev - Watch mode with automatic rebuilding
  • pnpm lint - ESLint with auto-fix
  • pnpm playground - Run the Nitro playground example
  • pnpm release - Build, version bump, and publish

Requirements

  • Node.js 20.x or later
  • pnpm (required package manager)

💬 Community & Contributing

Tip

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

🎯 How You Can Contribute

  • 💡 Share your ideas: Use GitHub Issues for new feature suggestions
  • 🐛 Report bugs: Report issues you encounter in detail
  • 📖 Improve documentation: Enhance README, examples, and guides
  • 🔧 Code contributions: Develop bug fixes and new features
  • 🌟 Support the project: Support the project by giving it a star

💬 Discussion and Support

  • GitHub Issues: Feature requests and bug reports
  • GitHub Discussions: General discussions and questions
  • Pull Requests: Code contributions

🚀 Contribution Process

  1. Open an issue: Let's discuss what you want to do first
  2. Fork & Branch: Fork the project and create a feature branch
  3. Write code: Develop according to existing code standards
  4. Test: Test your changes
  5. Send PR: Create a pull request with detailed description

Important

Please don't forget to read the Contribution Guidelines document before contributing.

📋 Community TODOs

Help us improve nitro-graphql! Pick any item and contribute:

🚀 Framework Examples

  • Nitro-compatible framework integrations
  • Nuxt + Pinia Colada example
  • StackBlitz playground demos

🧹 Code Quality

  • Performance benchmarks
  • Bundle size optimization
  • Testing utilities
  • Error handling patterns

📚 Documentation

  • Video tutorials
  • Migration guides
  • Best practices guide

🔧 Developer Tools

  • VS Code extension
  • CLI tools
  • Debug utilities

🌐 Integrations

  • Database adapters (Prisma, Drizzle)
  • Cache strategies
  • Deployment guides

Note

Have other ideas? Open an issue to discuss!

🛠️ VS Code Extensions

For the best development experience with GraphQL, install these recommended VS Code extensions:

These extensions will enable:

  • 🎨 Syntax highlighting for .graphql files
  • 📝 IntelliSense and autocompletion based on your schema
  • ✅ Real-time validation of GraphQL queries
  • 🔍 Go-to definition for types and fields
  • 💡 Hover information for GraphQL elements

🌟 Thank You

Thank you for using and developing this project. Every contribution makes the GraphQL ecosystem stronger!

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

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published