nitro-graphql
TypeScript icon, indicating that this package has built-in type declarations

1.1.0 • Public • Published

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

Readme

Keywords

none

Package Sidebar

Install

npm i nitro-graphql

Weekly Downloads

1,005

Version

1.1.0

License

MIT

Unpacked Size

244 kB

Total Files

162

Last publish

Collaborators

  • productdevbook