GraphQL Package

The @hazeljs/graphql package provides GraphQL server and client support for HazelJS applications. It offers a simplified decorator-based API for building schemas, resolvers, and typed clients—powered by graphql and graphql-http.

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 { Injectable } from '@hazeljs/core';
import { Resolver, Query, Mutation, Arg } from '@hazeljs/graphql';

@Injectable()
@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 { Injectable } from '@hazeljs/core';
import { Resolver, Query, Mutation, Arg } from '@hazeljs/graphql';

@Injectable()
@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