GraphQL Modules

repository·master·Indexed 23 days ago

https://github.com/graphql-hive/graphql-modules

A toolset for building modular GraphQL servers, enabling the creation of reusable, testable, and scalable modules. It features a schema-first design, a dependency injection system with support for forward references and injection tokens, and comprehensive lifecycle management for GraphQL operations via OperationController and createContextBuilder.

Tokens
22.9K
Snippets
63
Records
106
Agent score
78%

What's inside graphql-modules

  1. What is GraphQL Modules

    master

    GraphQL Modules is a utility library designed to implement the Separation of Concerns design pattern in GraphQL. It allows you to split your GraphQL schema implementation into small, reusable, and easy-to-test pieces.

    Each module contains its own GraphQL type definitions and resolver implementations. While you develop using these separated modules, GraphQL Modules merges them at runtime to serve a single, unified GraphQL schema.

  2. What are GraphQL Modules?

    master
    GraphQL Modules is a toolset designed to help you create reusable, maintainable, testable, and extendable modules for your GraphQL server. It follows a schema-first design where modules are defined by their GraphQL schema and can be shared between different applications. This allows for a scalable structure that can manage multiple teams, features, microservices, and servers, providing a path from simple single-file modules to complex multi-repo/multi-server setups.
  3. What is a middleware and how to write one

    master

    A middleware is a function used to intercept individual resolve functions or a group of them. It can return a different result or throw an exception based on a condition.

    Middlewares can be sync or async and accept two arguments:

    1. An event object containing root, args, context, and info.
    2. A next function that calls the next middleware in the chain or the actual resolver.

    Every middleware must do one of three things:

    • Throw an exception.
    • Return the result of next().
    • Return a specific value.

    Warning: If a middleware returns undefined, it will be treated as the result of the field resolver.

    function middleware({ root, args, context, info }, next) {
      // ...
      return next()
    }
  4. Access GraphQL Context in Singletons using @ExecutionContext

    master

    In graphql-modules, Singleton providers cannot directly access operation-scoped services or the GraphQL context object because they are instantiated only once. To bridge this gap, you can use the @ExecutionContext property decorator.

    When you decorate a property with @ExecutionContext(), the library automatically injects the current GraphQL ExecutionContext (which includes both the GraphQL Context and the Operation-scoped Injector) into that property during the execution of a GraphQL operation.

    This pattern allows singletons to remain high-performance (instantiated once) while still being able to access request-specific data or operation-scoped dependencies via this.context.injector.get(TOKEN) or this.context.context.

    import { Injectable, ExecutionContext } from 'graphql-modules'
    import { Config } from './config'
    
    @Injectable()
    export class Data {
      constructor(private config: Config) {}
    
      @ExecutionContext()
      private context: ExecutionContext
    
      someMethod() {
        console.log('Environment', this.config.env)
    
        // Accessing an operation-scoped service via the injector
        const value = this.context.injector.get(SOME_TOKEN)
      }
    }
  5. Migrate Module Structure to Application

    master

    In v0, modules formed a hierarchy using imports. In v1, modules are flat, and you use createApplication to define the root of the injection container. This removes the need for strict module-to-module dependency trees.

    // V1: Flat structure with Application
    const moduleOne = createModule({ ... })
    const moduleTwo = createModule({ ... })
    
    const application = createApplication({
      modules: [moduleOne, moduleTwo]
    })
    
    // V0 (Deprecated): Hierarchical structure
    const moduleOne = new GraphQLModule({ ... })
    const moduleTwo = new GraphQLModule({ imports: [moduleOne], ... })
    
    const rootModule = new GraphQLModule({
      imports: [moduleTwo]
    })
  6. How Hierarchical Injectors work in GraphQL Modules

    master

    GraphQL Modules uses a hierarchical Dependency Injection system where every Module has its own isolated injector space, which in turn reuses a global space defined by the Application.

    • Module Injector: Responsible for managing its own Providers and InjectionTokens. Anything defined here is only accessible within that specific module and does not leak to other modules or the application.
    • Application Injector: The parent injector. If a Provider or InjectionToken is not found in the Module Injector, the system will look up the hierarchy to the Application Injector.

    This structure allows modules to consume application-level services while keeping module-specific logic encapsulated.

  7. How GraphQL Modules structure works

    master

    The architecture of a GraphQL Modules application is flat. At the top level, there is an Application which manages the overall schema. Below the application, multiple Modules exist on the same level.

    Each module is built using standard GraphQL components:

    • Type definitions: The schema parts specific to that module.
    • Resolver functions: The logic for those schema parts.

    As applications scale, modules can utilize Dependency Injection to further separate resolver logic from core business logic.

  8. Understand the role of Context in GraphQL Modules

    master

    In GraphQL, context is a shared object available to all resolvers during a specific execution. It is typically used to store authentication information, the current user, database connections, or data sources.

    GraphQL Modules follows the standard GraphQL approach: the context is passed as the third argument to every resolver. Because the context is managed by the underlying GraphQL server implementation (which populates it with data like HTTP request information), GraphQL Modules does not provide a specific API for building the context itself.

    const resolvers = {
      Query: {
        myQuery(root, args, context, info) {
          // context is available here
        }
      }
    }
  9. Understand middleware execution order

    master

    Middlewares follow a specific hierarchical execution order from the broadest scope (Application global) to the most specific (Module field). The order is:

    1. Application *.* (Global application middleware)
    2. Module *.* (Global module middleware)
    3. Application Type.* (Type-specific application middleware)
    4. Module Type.* (Type-specific module middleware)
    5. Application Type.Field (Field-specific application middleware)
    6. Module Type.Field (Field-specific module middleware)
    7. The actual resolver

    This means that a middleware registered on a specific field in a module will be the last one to run before the resolver itself.

  10. Performance considerations for @ExecutionContext

    master

    The @ExecutionContext decorator relies on Node.js async_hooks to ensure that the correct context is associated with the correct asynchronous execution flow. This is critical to prevent race conditions during concurrent parallel requests.

    Performance Note: Because async_hooks is used, there is a performance impact. However, graphql-modules is optimized to only enable async_hooks when the @ExecutionContext decorator is actually used in your code.

  11. How Providers and Tokens work in Dependency Injection

    master

    Dependency Injection (DI) in GraphQL Modules is built on two core concepts:

    1. InjectionToken: A symbol or class that represents an object or value within the DI space. It acts as the unique identifier used to request a dependency.
    2. Provider: A definition that maps a specific InjectionToken to a value or a way to create a value.

    By using these, you can decouple your business logic (Services) from the specific implementations or values they require.