DocumentationReference

HazelJS GraphQL Package

npm downloads

@hazeljs/graphql provides decorator-based GraphQL server and client support for HazelJS, powered by graphql and graphql-http.

Quick Reference

  • Purpose: @hazeljs/graphql provides decorator-based GraphQL schema definition, resolvers, and typed clients for HazelJS applications.
  • When to use: Use @hazeljs/graphql when building a GraphQL API instead of REST. Use @hazeljs/core controllers for REST APIs.
  • Key concepts: @Resolver(), @Query(), @Mutation(), @ObjectType(), @Field(), @Arg() decorators, GraphQLModule, typed GraphQL client.
  • Dependencies: @hazeljs/core, graphql, graphql-http.
  • Common patterns: Define types with @ObjectType() and @Field() → create resolvers with @Resolver() → add queries with @Query() and mutations with @Mutation() → register GraphQLModule.
  • Common mistakes: Not adding @Field() to all type properties (missing from schema); mixing REST and GraphQL on the same endpoint path; not handling N+1 query problem with DataLoaders.

Purpose

GraphQL APIs typically require schema definitions, resolver wiring, and HTTP handling. The @hazeljs/graphql package simplifies this by providing:

  • Decorator-Based Schema: Use @Resolver, @Query, @Mutation, @ObjectType, @Field, and @Arg to define your API
  • Automatic HTTP Handling: GraphQL endpoint served at a configurable path (default /graphql) with GraphQL over HTTP
  • DI Integration: Resolvers are resolved from the HazelJS container—inject services and other providers
  • Typed Client: GraphQLClient for executing queries and mutations with optional decorators
  • Module Pattern: Familiar GraphQLModule.forRoot() configuration

Architecture

The package uses decorator metadata to build a GraphQL schema and wires it to an HTTP handler:

graph TD
  A["@Resolver, @Query, @Mutation<br/>(Decorators)"] --> B["GraphQLModule.forRoot()<br/>(Module Configuration)"]
  B --> C["SchemaBuilder<br/>(Builds Schema from Decorators)"]
  C --> D["GraphQLServer<br/>(graphql-http Handler)"]
  D --> E["Early Handler<br/>(/graphql on HTTP Server)"]
  F["@ObjectType, @Field<br/>(Optional Object Types)"] --> C
  
  style A fill:#8b5cf6,stroke:#a78bfa,stroke-width:2px,color:#fff
  style B fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
  style C fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff
  style D fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
  style E fill:#f59e0b,stroke:#fbbf24,stroke-width:2px,color:#fff

Key Components

  1. GraphQLModule: Configures the GraphQL server with path and resolvers
  2. GraphQLServer: Builds schema from decorators and serves via graphql-http
  3. SchemaBuilder: Converts @Resolver, @Query, @Mutation metadata into a GraphQL schema
  4. GraphQLClient: Typed client for queries and mutations
  5. Decorators: @Resolver, @Query, @Mutation, @ObjectType, @Field, @Arg for server; @GraphQLClientClass, @GraphQLQuery, @GraphQLMutation for client

Advantages

1. Code-First Schema

Define your API with TypeScript classes and decorators—no separate SDL files to maintain.

2. Decorator-Based Resolvers

Use @Query() and @Mutation() to declare handlers—clean and aligned with HazelJS patterns.

3. Full DI Integration

Resolvers are resolved from the HazelJS container—inject services, repositories, and other providers.

4. Integrated HTTP

GraphQL is served on the same HTTP server as your REST API, via an early handler before body parsing.

5. Typed Client

GraphQLClient for executing queries and mutations with optional decorator-based client classes.

Installation

npm install @hazeljs/graphql @hazeljs/core

Quick Start

1. Create Resolvers

import { Service } from '@hazeljs/core';
import { Resolver, Query, Mutation, Arg } from '@hazeljs/graphql';

@Service()
@Resolver()
export class UserResolver {
  @Query()
  hello() {
    return 'Hello, GraphQL!';
  }

  @Query()
  user(@Arg('id') id: string) {
    return { id, name: `User ${id}` };
  }

  @Mutation()
  createUser(@Arg('name') name: string) {
    return { id: '1', name };
  }
}

2. Register GraphQL Module

import { HazelModule } from '@hazeljs/core';
import { GraphQLModule } from '@hazeljs/graphql';
import { UserResolver } from './user.resolver';

@HazelModule({
  imports: [
    GraphQLModule.forRoot({
      path: '/graphql',
      resolvers: [UserResolver],
    }),
  ],
})
export class AppModule {}

3. Start the App

import { HazelApp } from '@hazeljs/core';

const app = new HazelApp(AppModule);
app.listen(3000);
// GraphQL available at http://localhost:3000/graphql

Configuration

Configure the GraphQL module via GraphQLModule.forRoot():

GraphQLModule.forRoot({
  path: '/graphql',           // Endpoint path (default: /graphql)
  resolvers: [UserResolver, PostResolver],  // Resolver classes
  playground: true,          // Enable GraphiQL in development (optional)
  introspection: true,      // Enable introspection (default: true)
});

Object Types (Optional)

For complex return types, use @ObjectType and @Field:

import { ObjectType, Field } from '@hazeljs/graphql';

@ObjectType('User')
class User {
  @Field()
  id!: string;

  @Field()
  name!: string;

  @Field('emailAddress')
  email!: string;
}

Decorators Reference

Server Decorators

DecoratorDescription
@Resolver(name?)Marks a class as a GraphQL resolver
@Query(name?)Marks a method as a Query field
@Mutation(name?)Marks a method as a Mutation field
@Arg(name, type?)Marks a parameter as a GraphQL argument
@ObjectType(name?)Marks a class as a GraphQL object type
@Field(name?)Marks a property or method as a GraphQL field

Client Decorators

DecoratorDescription
@GraphQLClientClass(url, headers?)Marks a class as a GraphQL client
@GraphQLQuery()Marks a method as a query executor
@GraphQLMutation()Marks a method as a mutation executor

GraphQL Client

Use GraphQLClient for typed queries and mutations:

import { GraphQLClient } from '@hazeljs/graphql';

const client = new GraphQLClient({
  url: 'http://localhost:3000/graphql',
  headers: { Authorization: 'Bearer token' },
});

// Query
const data = await client.query(`
  query {
    hello
    user(id: "1") { id name }
  }
`);

// Mutation
const result = await client.mutate(`
  mutation {
    createUser(name: "Alice") { id name }
  }
`);

Complete Example

// user.resolver.ts
import { Service } from '@hazeljs/core';
import { Resolver, Query, Mutation, Arg } from '@hazeljs/graphql';

@Service()
@Resolver()
export class UserResolver {
  @Query()
  hello() {
    return 'Hello, GraphQL!';
  }

  @Query()
  user(@Arg('id') id: string) {
    return { id, name: `User ${id}` };
  }

  @Mutation()
  createUser(@Arg('name') name: string) {
    return { id: Date.now().toString(), name };
  }
}

// app.module.ts
import { HazelModule } from '@hazeljs/core';
import { GraphQLModule } from '@hazeljs/graphql';
import { UserResolver } from './user.resolver';

@HazelModule({
  imports: [
    GraphQLModule.forRoot({
      path: '/graphql',
      resolvers: [UserResolver],
    }),
  ],
})
export class AppModule {}

// main.ts
import { HazelApp } from '@hazeljs/core';

const app = new HazelApp(AppModule);
app.listen(3000);

Best Practices

  1. Use dependency injection: Inject services and repositories into resolvers—they're regular HazelJS providers.

  2. Return strings for scalar fields: When the schema infers String, return string values. For objects, consider JSON.stringify() or define proper @ObjectType classes.

  3. Keep resolvers focused: One resolver per domain (e.g., UserResolver, PostResolver).

  4. Use @Arg for arguments: Always annotate parameters with @Arg('name') for GraphQL to map them correctly.

  5. Test with the client: Use GraphQLClient or tools like GraphiQL to verify your API.

What's Next?

  • Explore WebSocket for real-time subscriptions (GraphQL subscriptions can be added separately)
  • Check out Swagger for REST API documentation alongside GraphQL
  • Learn about Discovery for microservice coordination

Recipes

Recipe: GraphQL Query and Mutation

// File: src/users/users.resolver.ts
import { Resolver, Query, Mutation, Args } from '@hazeljs/graphql';
import { Service } from '@hazeljs/core';

@Resolver('User')
@Service()
export class UsersResolver {
  private users = [{ id: '1', name: 'Alice', email: 'alice@example.com' }];

  @Query('users')
  findAll() {
    return this.users;
  }

  @Query('user')
  findOne(@Args('id') id: string) {
    return this.users.find(u => u.id === id);
  }

  @Mutation('createUser')
  create(@Args('name') name: string, @Args('email') email: string) {
    const user = { id: String(this.users.length + 1), name, email };
    this.users.push(user);
    return user;
  }
}

Recipe: Register GraphQL Module

// File: src/app.module.ts
import { HazelModule } from '@hazeljs/core';
import { GraphQLModule } from '@hazeljs/graphql';
import { UsersResolver } from './users/users.resolver';

@HazelModule({
  imports: [
    GraphQLModule.register({
      autoSchemaFile: true,
      playground: true,
      path: '/graphql',
    }),
  ],
  providers: [UsersResolver],
})
export class AppModule {}