Skip to main content

Module API

As mentioned here Falcon Module packs implementation of various things that are required to achieve a particular feature in Falcon Server. Its powerful api allows to:

  • manage large code base
  • makes your code base testable
  • control the way your classes are instantiated
  • control lifetime of your instances
  • modify GraphQL context to include additional data you want to use in GraphQL resolvers
  • create DataSource for GraphQL that does not extend data sources provided by Falcon
  • and many more

To use all above Falcon Module features, your module main file needs to export class derived from FalconModule abstract class. To se how to create new Falcon Module see custom module section. Here is an Module abstract class definition:

abstract class FalconModule<TModuleConfig> {
protected config: TModuleConfig;

constructor(config: TModuleConfig = {}) {
this.config = config;
}

public servicesRegistry(registry: FalconModuleRegistryProps): void {}

public gqlResolvers<TGqlResolverContext>(): GqlResolversMap<TGqlResolverContext> {
return {};
}

protected mergeGqlResolvers<TGraphQLContext>(
resolversMapA: GqlResolversMap<TGraphQLContext>,
resolversMapB: GqlResolversMap<TGraphQLContext>
): GqlResolversMap<TGraphQLContext> {
return deepMerge(resolversMapA, resolversMapB);
}
}

Here is an list of all methods and poperies of FalconModule abstract class:

  • constructor(config: TConfig) - As you can see constructor of FalconModule accepts configuration object. It will be provided via Falcon Server when Module will be loaded. So you are able to configure your Module initialization.

  • config: TConfig - protected property which give yo an access to configuration extracted during module loading and injected via Module constructor.

  • servicesRegistry(registry: FalconModuleRegistryProps): void - through this method you are able to register all dependencies which are used by module, to learn how to bind new or rebind existing one, learn more in Services registry section. To learn why you need this, see Dependency Injection.

  • gqlResolvers(): GqlResolversMap - through this method you are able to define all GraphQL resolvers, to learn how to define new or override existing one GraphQL resolvers section.

  • mergeGqlResolvers(resolversMapA, resolversMapB): GqlResolversMap - protected method which allows you to merge two GraphQL resolvers object maps into one.

servicesRegistry

Through this function you are able to register all services (dependencies) into container registry. Exposed api allows you to control not only the way in which any particular service should be created, but also the lifetime of that instances. Service (dependency) stands for:

  • FalconModule extension class: DataSource, EventHandler or EndpointManager
  • custom written class, function or const which are used inside particular module and/or should be visible for FalconServer host application.
  • FalconServer host instances, like fetch, Logger, HttpSession, see full list of them

servicesRegistry method, through its argument, provide and set of method which should be used in order to manage all your module dependencies. We are using inversify under the hood, however api which we are exposing is slights changed.

type FalconModuleRegistryProps = {
bind: (serviceIdentifier: ServiceIdentifier) => FalconModuleBindingSyntax;
rebind: (serviceIdentifier: ServiceIdentifier) => FalconModuleBindingSyntax;
unbind: (serviceIdentifier: ServiceIdentifier) => void;
isBound: (serviceIdentifier: ServiceIdentifier) => boolean;
};
  • bind - a function which allows you to add new service implementation into the container registry for a particular service identifier
  • rebind - a function which allows you to replace underlining service implementation in container registry for a particular service identifier if that one was already added into container registry.
  • unbind - a function which allows you to remove service implementation from the container registry assigned to a particular service identifier.
  • isBound - a function which allows you to determine if the container registry contains already any service implementation for a particular service identifier.

To learn more about Inversion of Control pattern and Dependency Injection works, please see Dependency Injection

Binding services

In order to bind a new service, you need to use bind method:

registry.bind('Foo').to(Foo);
import { injectable } from 'inversify';
import { FalconModule, FalconModuleRegistryProps } from '@deity/falcon-server-env';

@injectable()
class Foo {}

export class FooModule extends FalconModule {
servicesRegistry(registry: FalconModuleRegistryProps) {
super.servicesRegistry(registry);

registry.bind('Foo').to(Foo);
}
}

Above example will bind 'Foo' to the Foo class constructor, and the container will care about proper Foo class object creation. If you are interested in more customization please look into inversify documentation

Since Data Sources, Rest Endpoint Handlers,and Event Handlers are Falcon Middleware common services, those require special treatment. You cannot for example control those instances lifetime. Because of that, we introduced dedicated method to manage their registrations

Binding Data Sources

In order to bind Data Source you need to use toDataSource or toDataSourceDynamicValue binding syntax methods.

registry.bind('FooDataSource').toDataSource(FooDataSource);
registry.bind('FooDataSource').toDataSourceDynamicValue(() => new FooDataSource('foo'));
  • toDataSource - expect class constructor, is useful if DataSource has parameter-less constructor or all its arguments are marked via @inject() decorator - which means all of its arguments can be resolved dynamically via dependency container.
  • toDataSourceDynamicValue - expect an instance of the class so you need to pass a function which will create one, is useful if DataSource has arguments which cannot or you don't want to be resolved dynamically via dependency container.

To learn more about DataSources in Falcon Server please see

Binding Rest Endpoint Manager

In order to bind Rest Endpoint Manager you need to use toEndpointManager or toEndpointManagerDynamicValue binding syntax methods.

registry.bind('FooEndpointManager').toEndpointManager(FooEndpointManager);
registry.bind('FooEndpointManager').toEndpointManagerDynamicValue(() => new FooEndpointManager());

To learn more about Rest Endpoint Managers in Falcon Server please see

Binding Event Handlers

In order to bind Event Handler you need to use toEventHandler or toEventHandlerDynamicValue binding syntax methods.

registry.bind('FooEventHandler').toEventHandler(FooEventHandler);
registry.bind('FooEventHandler').toEventHandlerDynamicValue(() => new FooEventHandler());

To learn more about Event Handlers in Falcon Server please see

Rebinding services

In order to rebind new service, you need to use rebind method:

registry.rebind('Foo').to(Foo);
import { injectable } from 'inversify';
import { FalconModuleRegistryProps } from '@deity/falcon-server-env';
import { FooModule: FooModuleBase, Foo: FooBase } from 'foo-module'

@injectable()
class Foo extends FooBase {}

export class FooModule extends FooModuleBase {
servicesRegistry(registry: FalconModuleRegistryProps) {
super.servicesRegistry(registry);

registry.rebind('Foo').to(Foo);
}
}

Binding syntax returned from rebind method is exactly the same as for bind, which means if you want to rebind e.g. Data Source implementation you should use corresponding to bind method syntax:

registry.rebind('FooDataSource').toDataSource(ExtendedDataSource);

gqlResolvers

Basically, the resolvers map is a tree of objects, where object keys are mapped to Queries and Mutations defined in GQL schema file, while values of that keys are functions (resolvers) which define a way how the value of specific filed should be calculated.

Single resolver function can be described in following way:

(source: TSource, args: TArgs, context: TContext, info: GraphQLResolveInfo) => TReturn;

where:

  • source - is an value returned from parent resolver, in case when one resolver is called by another
  • args - is an object with all arguments provided to Query or Mutation
  • context - provides resolver execution context, like http request context, http session, data sources map, read more
  • info - provides graphQL specific metadata about executed resolver, read more

For more information about GraphQL resolver API please see graphql.org or apollo-server documentation.

GraphQL resolver Context

GraphQL Resolver third argument - is provided to every resolver and holds important contextual information, like http request context, http session, data sources map. To learn more about context see apollo-server documentation.

However Falcon Server, provides couple of cool things through it. It can be described by following type:

type GraphQLContext<TDataSources extends DataSourcesMap = DataSourcesMap> = {
scope: Record<string, string>;
dataSources: TDataSources;
cache: ICache;
session?: Record<string, any>;
headers: Record<string, string>;
koa: KoaContext;;
container: IContainer;
};

where:

  • scope - an object key-value map of Extension name and Extension Scope Id see the details
  • dataSources - an object key-value map of DataSource name and its instance, see the details
  • cache - give you direct access to cache.
  • session - http session cookie, which is an object key-value map of Extension name, and actual session value.
  • headers- request header object.
  • koa - a koa Context which encapsulates node's request and response objects into a single object.
  • container - gives you access to IoC container.

Context DataSources map

To read more about DataSources see DataSources section;


Dependency Injection

To allow all above, Falcon Module introducing support of Inversion of Control. It is possible due to usage of inversify under the hood as an Dependency Injection framework. If you are not familiar with inversify nor Inversion of Control pattern, please look here: (new section needed )

TODO: prepare some good introduction of this topic:

Module with common services auto-discovery

With auto-discovery feature, you can just export classes which extends Falcon Common Services:

Then Falcon Server will do the required services registration by himself during startup.

However, as you may suspect, common services auto-discovery doesn't cover all the possible things you can do in FalconServer. This mechanism was especially created to implement basic scenarios, and also to make migration from Falcon Platform v2 into v3 easier. It will work until you do not want to create any custom service. To learn more how to create that kind of Falcon Module please see custom module