Home > Posts Backend ctx

Max Heinritz

Backend ctx

I like to have all backend service class methods take a ctx parameter, similar to Go’s context type.

Structure

The ctx looks like this:

export class CtxDto {
  readonly user?: CtxUserDto;
  readonly entryPoint: CtxEntryPoint;
}

It contains information about the current user (ctx.user) as well as the entry point that triggered the operation (ctx.entryPoint). These values are used to check authorization permissions for every operation.

Name

Calling it ctx instead of context helps reify the concept as a first-class idea in the application, separate from other notions of context at the language or framework level. It’s also fewer characters.

Usage

Idiomatically ctx is passed in as the first parameter:

export class FooBarMutateService {
  // ...

  async create(
    ctx: CtxDto,
    createFooBarDto: CreateFooBarDto
  ): {
    // ...
  };
}

The data in ctx is also used to populate the revisionSource for entity revisions, like this:

{
  "revisionSource": {
    "userQid": "qid::user:b969be65-8425-46ed-b108-25fbef01ff0e",
    "entryPoint": {
      "root": "cli",
      "runner": "CliSeedCustomerTenant"
    }
  },
}

Decorator

For GraphQL resolvers and REST controllers, it’s helpful to have an @Ctx() decorator for usage like:

@Ctx() ctx: CtxDto,

Implementation looks something like:

export const Ctx = createParamDecorator<unknown, ExecutionContext, CtxDto>(
  (_, context): CtxDto => {
    if (context.getType() == "http") {
      return ctxForHttp(context);
    }

    if (context.getType() === "graphql") {
      const gqlExecutionContext = GqlExecutionContext.create(context);
      const { path } = gqlExecutionContext.getInfo();
      const getCtxForPath: (path: Path) => CtxDto =
        gqlExecutionContext.getContext().getCtxForPath;

      return getCtxForPath(path);
    }

    throw new Error(`Unable to get ctx from session: ${context}`);
  }
);