express.mediator v1.0.5-alpha.6
An lightweight and easy implementation of an mediator pattern with express.js.
const app = new ExpressMediator();
app.get("/", IndexRequest);
@body
export class IndexRequest extends IRequest {
name!;
}
@requestHandler(IndexRequest)
export class IndexRequestHandler implements IRequestHandler<IndexRequest, string> {
async handle(value: IndexRequest): Promise<string> {
return `Hello ${value.name}!`
}
}
See usage to learn all the different types of implementation.
Getting Started
Installation
npm i express.mediator
or if you want to access early builds
npm i express.mediator@X.X.X-alpha.X
Usage
The most magic of the mediator happens in the background. You basically just have to place some decorators onto your existing requests and replace the express app with the new ExpressMediator
. In the following captions are all possible types of implementations and examples. Just pick what fits best for your purpose.
Request (required)
A request defines the whole processing for the resource. If you request data is located specifically use an request type to tell the parse pipeline where exactly to expect the data.
!IMPORTANT If no request type is placed onto the given class the default type body will be used to parse the data.
@body
class ExampleRequest {
user!: string;
password!: string;
}
@body
class ExampleRequest {
user = "";
password = "";
}
Request Property Problematic
To ensure that all required properties are provided by the request you have to initialize them
class ExampleRequest {
exampleProperty = "";
}
or mark them as defined
class ExampleRequest {
exampleProperty!: string;
}
This is required due to the fact that typescript won't know the exact type of the provided class when not initialized in the runtime of the application. That will cause many problems for example if the requests does not have required properties because those couldn't be catched by the parse pipeline.
Request Type
All usable http request types are accessable via an specific typescript decorator. There are two different (class and property) decorator types with two (non nullable and nullable) features available. These decorator are needed for the parse pipeline to determine where the data is in the request.
The class decorator function require a functional constructor to access the name of the provided class.
{{HTTPTYPE}}<T extends { new (): {} }>(constructor: T);
An property decorator function require the property and a specific name. !!! NOT IMPLEMENTED YET !!!
{{HTTPTYPE}}Property(target: any, propertyName: string);
Therefor both decorators must be placed on the correct type otherwise an error will be thrown.
!NOTE Both decorators add an default auth entry into the IRequestAuthResolver without any role configuration and disabled anonymous request mode. This is required to ensure that every available handler request is covered by the auth component and a missed configuration will be highlighted correctly.
Request Type Body
Reads the data from the body of express request.
@body
@bodyNullable
Request Type Params
Reads the data from the params of express request.
@params
@paramsNullable
Request Type Query
Reads the data from the query of express request.
@query
@queryNullable
Request Type Empty
Ignores the parsing and won't proceed any value of the request.
@empty
Request Handler (required)
Every request has logic which is located in an handler.
The mediator assigns the handler to the request with a specific decorator.
@body
class ExampleRequest { ... }
@requestHandler(ExampleRequest)
class ExampleHandler implements IRequestHandler<ExampleRequest, string> { ... }
Request Handler in another file
If the handler is not in the same file like the request you have to put the following decorator onto the request. This step is required due to the fact that the handler won't be called by the application setup because the mediator only knowns the request.
Request File
@params
@handler(TestRequestHandler)
class TestRequest_Param {
data!: string;
}
Handler File
class TestRequestHandler implements IRequestHandler<TestRequest_Param, string> {
async handle(value: TestRequest_Param): Promise<string> {
return await Promise.resolve(value.data);
}
}
Access Root Express Application
To access the root express application just call the app
singleton on the ExpressMediator
instance.
const app = new ExpressMediator().app;
The singleton ensures that all the provided middlewares are registered correctly inside the express app.
Tests
The tests are located inside the folder ./tests/
and are constructed with jest respectively ts-jest. To configure jest use the config file jest.config.js
.
npm run test
Before uploading the files to npm run the following script.
npm run preupload
This script runs the jest test file jest ./tests/default.test.ts
which tests the basic functionalities of the build files in ./lib
.
All test files with the prefix
!
will be ignored by jest. If you want to change this behavior remove this line inside the jest configuration.
Component
Component Infrastructure
interface IExpressMediatorComponent {
key: string;
pipelines: IExpressMediatorPipelines;
allowCustom: boolean;
I__defaults: { new (): IExpressMediatorComponentDefaults };
I__instance: { new (): any };
dependencies?: { new (): IExpressMediatorComponent }[];
isActive?: boolean;
isSkeleton?: boolean;
defaults?: IExpressMediatorComponentDefaults;
instance?: any;
}
Property | Purpose |
---|---|
Key | Identifier of the component. Must be unique |
Pipelines | See pipelines |
AllowCustom | If disabled the component won't allow any custom instances or defaults |
I__Defaults | See defaults |
I__Instance | See instance |
Dependencies | Array of dependend components. The setup will ensure that these components are available |
IsActive | If disabled the component won't add any functionalities to the application |
IsSkeleton | If enabled the component was created by an decorator and will be created setup again with the latest definitions |
Defaults | See defaults |
Instance | See instance |
Component Pipelines
interface IExpressMediatorPipelines {
pre: IExpressMediatorPipeline<IPreHandlerPipeline>[];
post: IExpressMediatorPipeline<IPostHandlerPipeline>[];
}
Component Pre Pipelines
interface IPreHandlerPipeline extends IPipeline {
handle<T extends IRequest>(
context: MediatorContext,
type: { new (): T },
value: T | undefined
): Promise<PrePipelineResponse<T>>;
}
Component Post Pipelines
interface IPostHandlerPipeline extends IPipeline {
handle<T extends IRequest>(context: MediatorContext, type: { new (): T }, value: T): Promise<void>;
}
Component Pipelines Dependency Injection
All previous pipeline types (pre and post) extend the base interface IPipeline
which again extend the interface IDependencyInjection
.
interface IPipeline extends IDependencyInjection {}
interface IDependencyInjection {
dependencyInjection(resolver: IDependencyChainResolver): Promise<void>;
}
Component Defaults
The defaults of the components contain informations which can be overridden or extended over the lifetime of the app only. A constructor is required and the class must have an implementation of IExpressMediatorComponentDefaults
.
Component Defaults Interface
interface IExpressMediatorComponentDefaults {
[key: string | symbol | number]: AllowedDefaultTypes | Map<string, any> | AllowedDefaultTypes[];
}
Component Defaults Interface Example
interface IMediatorDefaults extends IExpressMediatorComponentDefaults {
useAbsolutePath: boolean;
locations: Map<string, Function>;
middlewares: Map<string, RequestHandler<any, any, any, any, Record<string, any>>>;
}
Component Defaults Abstract Class
abstract class ExpressMediatorComponentDefaults implements IExpressMediatorComponentDefaults {
[key: number | string | symbol]: AllowedDefaultTypes | AllowedDefaultTypes[] | Map<string, any>;
}
Component Defaults Abstract Class Example
class MediatorDefaults extends ExpressMediatorComponentDefaults implements IMediatorDefaults {
useAbsolutePath = false;
locations = new Map();
middlewares = new Map([
["CORS", cors()],
["JSON format", express.json()],
["OK response", Ok],
["BadRequest response", BadRequest],
["Pipeline error response", PipelineError],
]);
}
Component Defaults Allowed Types
type AllowedDefaultTypes = string | boolean | number;
Component Instance
A instance of an component must contain a constructor to invoke. All static informations of the component should be saved into the defaults because those will be invoked only once.
2 months ago
3 months ago
4 months ago
4 months ago
4 months ago
5 months ago
5 months ago
5 months ago
5 months ago
9 months ago
9 months ago
9 months ago