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}`);
}
);