Setup and start
Server & Runtime setup

Server & Runtime Setup

While the demo/server uses an example setup to make the start seamless, you can customize your server installation in many ways as described here.

Server Start Script

When you run npm run server you execute the /bin/activeql.ts script that basically just creates an ActiveQLServer instance with a ActiveQLServerConfig and starts it.

import { ActiveQLServer, loadRuntimeConfig } from 'activeql-foundation';
 
(async () => {
  const runtimeConfig = await loadRuntimeConfig( `./runtime/runtime-configuration`);
  const activeql = await ActiveQLServer.create({ runtimeConfig });      
  activeql.start();
})();

ActiveQLServerConfig

You create the ActiveQLServer with an ActiveQLServerConfig holding the RuntimeConfig. Besides that you can influence the behavior of the server with the following parameters.

You can also set the values as environment variables. E.g. the following command would let the server listen on port 4001 (instead of 4000).

export ACTIVEQL_PORT=4001; npm run server

Config values from the ActiveQLServerConfig will have always precedence, though.

 Config TypeEnvironment Variable DefaultDescription
runtimeConfigRuntimeConfigconfiguration of the Runtime
 graphqlbooleanACTIVEQL_GRAPHQL trueGraphQL endpoint enabled/disabled
subscriptionsboolean ACTIVEQL_SUBSCRIPTIONStrueSubscriptions enabled/disabled
restbooleanACTIVEQL_REST trueREST endpoint enabled/disabled
scheduler boolean`ACTIVEQL_SCHEDULERtrueScheduler enabled/disabled
graphqlPathStringACTIVEQL_GRAPHQL_PATH /graphql  GraphQL endpoint path
restPathStringACTIVEQL_REST_PATH/restREST endpoint root path
portstring|numberACTIVEQL_PORT 4000http server port
openApiOpenApInfoObjectConfiguration for the OpenAPI schema (opens in a new tab)
beforeExpressAppInit(app:Express) => Express | Promise<Express> Callback before the Express app is initialized, to execute custom code
afterExpressAppInit(app:Express) => Express | Promise<Express>  Callback after the Express app is initialized, to execute custom code

RuntimeConfig

This RuntimeConfig defines your Runtime as follows:

ConfigTypeDefaultDescription
domainGraphDomainGrapha DomainGraph from Domain Configuration usually built by the DomainGraphBuilder
namestring ActiveQLName of this Runtime - used for logging/debugging
environmentRuntimeEnvironmentdevelopmentthe Runtime environment
dataStore() => Promise<DataStore>NeDB Datastore implementation factory method for the Datastore implementation
fileHandler() => Promise<FileHandler>ReferenceFileHandler  factory method for the FileHandler implementation
beforeInit(runtime:Runtime) => void  callback to be executed before the Runtime is initialized
afterInit(runtime:Runtime) => voidcallback to be executed after the Runtime is initialized
localeFn(req:IncomingMessage) => string () => 'en'callback to determine the locale that is set in the GraphQL context object. You have access to the http request and could use this (e.g. Cookie) to resolve the locale. 
addNonProductionType (environment, name) => booleantrue for all, unless in environment 'production' whether the internal (mostly) test types should be added to the API

Besides these configuration options you could add factory methods for the implementation of core ActiveQL core features. Which we cover in Implementing ActiveQL

Runtime Environment

The environment option can take the following values:

ValueDescription
developmentonly in this environment the Schema includes the seed mutation you can use to seed data into the DataStore
testyou can use this environment when running automated tests
stageyou can use this environment when testing your API in a near-production environment
productionthis should be the environment when running your API in production

Runtime Loader

As you have seen the start script uses the method loadRuntimeConfig to import a RuntimeConfig from a file path (./runtime/runtime-configuration.ts). This file must export a runtimeConfig.

While you can define your runtime configuration (which is just an object of type RuntimeConfig) as you see fit, the advantage of using the loadRuntimeConfig helper function however is that it evaluates an environment variable to determine which file / runtime to load.

ACTIVEQL_ENVIRONMENT

Let assume you start the ./bin/activeql.ts script with setting the ACTIVEQL_ENVIRONMENT variable

export ACTIVEQL_ENVIRONMENT=stage; npm run server

This would

  • try to find a file with the environment extension, e.g. ./runtime/runtime-configuration.stage.ts
  • if this file does not exist it falls back to ./runtime/runtime-configuration.ts
  • it sets the environment of the loaded RuntimeConfig to stage

If you don't set the ACTIVEQL_ENVIRONMENT variable, the load function will assume the development environment.

absolute vs. relative path

If the path you calling the loadRuntimeConfig method is an absolute path, it is used as such. If it is a relative path, loadRuntimeConfig will treat it relativ to process.cwd()

Error Handling

Another feature of the loadRuntimeConfig util function is that you will always get a RuntimeConfig even if anything failed while loading the runtime configuration or building the DomainGraph.

That means your ActiveQL server will always start and allow you to access it. The API will - however - only include a query named error which returns the error as string. This might become handy in automatic deployment scenarios where you don't have easy access to log files on the server but can rely on accessing your API.

DomainGraphBuilder

The DomainGraph is the sum of all Domain Configuration that describes your business domain. A typical implementation is to have a ./runtime/runtime-configuration.ts file that uses a DomainGraphBuilder to load config files from a folder and resolve these into a DomainGraph.

import { DomainGraphBuilder, RuntimeConfig } from 'activeql-foundation';
import { domainConfiguration } from './domain-configuration';
 
const domainGraphBuilder = new DomainGraphBuilder(__dirname + '/domain-configuration');
domainGraphBuilder.add(domainConfiguration);
 
export const runtimeConfig: RuntimeConfig = { 
  domainGraph: domainGraphBuilder.getDomainGraph()
};

In this example the DomainGraph is build from

  • the content of the config files in the folder ./runtime/domain-configuration and
  • the exported DomainConfiguration object in ./runtime/domain-configuration.ts

With this setup it would sufficient to place any ActiveQL domain-configuration files inside the ./runtime/domain-configuration folder (or any sub-folder) or add any configuration into the DomainConfiguration object.

You can however organize this differently for your needs. If you want to separate let's say seed configuration from entity configuration, you could have these in different folders and according to an environment variable or something else decide whether to add a folder to the DomainGraphBuilder or not. You can also have different DomainConfiguration objects merged into the graph.

You can add as many folders paths (string) or DomainConfiguration objects as you like. They will be merged into one DomainGraph.

import { DomainGraphBuilder, RuntimeConfig } from 'activeql-foundation';
import { domainConfiguration } from './domain-configuration';
 
const domainGraphBuilder = new DomainGraphBuilder();
domainGraphBuilder.add(__dirname + '/domain-configuration/domain');
if( env.ACTIVEQLENVIRONMENT != 'production' ){
  domainGraphBuilder.add(__dirname + '/domain-configuration/seed');
}
 
export const runtimeConfig: RuntimeConfig = { 
  domainGraph: domainGraphBuilder.getDomainGraph()
};

beforeInit / afterInit Callback

Server Configuration and Startup

The following gives an overview about the setup and startup of an ActiveQL Server.

Overview

  1. creation a DomainGraphBuilder and adding folder with config-files and/or DomainConfiguration object(s) to DomainGraphBuilder
  2. creation of a RuntimeConfig with DomainGraph from DomainGraphBuilder
  3. adding factory methods and config parameters to RuntimeConfig (optional)
  4. creation an ActiveQLServerConfig with the RuntimeConfig
  5. adding config parameters to ActiveQLServerConfig (optional)
  6. creation an ActiveQLServer instance with the ActiveQLServerConfig
  7. creation of a Runtime instance with RuntimeConfig
  8. initialization of Runtime (incl. calling pre/after hooks)
    1. creation of SchemaBuilder (entity, enum, ... )
    2. creation of Entity instances
    3. creation of GraphQL Schema
    4. calling factory methods for DataStore and FileHandler
    5. initialization of all created instances
  9. starting the ActiveQLServer instance (incl. calling pre/after hooks)
    1. preparing the ExpressJS http server and app
    2. starting the ExpressJS http server
    3. starting the scheduler

Server with multiple Runtimes

You could have many Runtime Configurations in your server package. The following example shows how you could use a command line parameter to decide which runtime the ActiveQL server should use:

import { ActiveQLServer, RuntimeConfig } from 'activeql-foundation';
import { generateUml } from './uml';
 
const loadRuntimeConfig = async (runtime:string) => {
  const runtimeFolder = `${__dirname}/../${runtime}`;
  const rtImport = await import(`${runtimeFolder}/runtime-configuration`);
  const runtimeConfig = rtImport.runtimeConfiguration as RuntimeConfig;
  runtimeConfig.name = runtimeFolder;
  return runtimeConfig;
}
 
const getRuntime = () => process.argv.length >= 3 ? process.argv[2] : 'runtime';
 
(async () => {
  const runtime = getRuntime();
  const runtimeConfig = await loadRuntimeConfig( runtime );
  const activeql = await ActiveQLServer.create({ runtimeConfig });  
  activeql.start();  
})();

With this you can state when starting the server

npm run server playground

Which would try to import a RuntimeConfig from the file ./playground/runtime-configuration.ts

Structure your configuration

If you have a non-trivial domain definition, you can split your configuration, even the same entity definition between any number of YAML or object configurations. Let's say you want to separate the seed data (see Seeds-Configuration) from the configuration of the attributes - thus making it easy to in-/exclude the seeds-configuration from the domain configuration, or even have separate seed-configurations. Let's further assume you prefer writing your entity configurations in YAML but need one of the function callbacks only available in object notation.

We recommend the following pattern:

  • one configuration folder for your domain definition
    • named after your business domain, e.g. ./cars-management or generic ./domain-configuration.
    • one YAML file per entity or enum in this folder with the file-name of the dasherized filename, e.g. entity VehicleFleet configuration is in file ./vehicle-fleet.yml.
  • one folder per set of seed-data if you want to separate the seed-data / configuration from the entity configuration
    • folder named after your business domain, e.g. ./cars-management-seeds or generic ./seeds
    • files named exactly as the file with the rest of the entity definition
  • one src folder for the configuration-object definitions
    • named after your business domain, e.g. ./cars-managemnt-src
    • one file per use-case