Zodora-backend-core
The zodoro backend core facilitates the creation of a backend. It is a template for tables/models, validation, services and endpoints. The core manages the eccosystem and controls the state of the endpoint, services or database. Data changes execute triggers via the listener and allow a proper data flow in two directions.
Install
Install via npm:
$ npm install zodora-backend-core
Install via yarn:
$ yarn add zodora-backend-core
Quick Start
// Create Database
const mongoDB = new MongoDB(27017, 'MyDatabase');
// Start the builing of core
const core: Core = new Core(mongoDB);
// Init model
core.addModel((core: Core): Model => {
return new Model('User', {
id: {
type: 'Id',
name: '_id'
},
firstName: {
type: 'String',
},
lastName: {
type: 'String',
},
age:{
type: 'Number',
min: 0,
max: 150
}
});
// Here init models, endpoints or services
const restApi: RestApi = new RestApi();
enpoint.generateApiRequest({
core,
paths:[{
path:"api",
middleware: new RequestCounter(core),
children:[{
path:"users",
async (core: Core, args: ApiRequestArgument): Promise<Result> => {
// TODO Something
return {}
}
}]
}]
});
core.addEndpoint("RestAPI", restApi);
// Create and start core
core.createAndStart();
Table of Content
Core
The core managed all adding components and connect the complete system of parts.
The core must be created with a database, because the model gives them the task of what they do.
const core: Core = new Core(database);
The second step is to add of models which require a function of returning a model.
core.addModel((core: Core): Model => {
return new Model('User', {
id: {
type: 'Id',
name: '_id'
}
});
After that can be add an endpoint. There are two implementations of endpoints (Restfull and WebSocket)
core.addEndpoint("RestAPI", new RestApi());
If you need a service, you can now add a service. As Example a cleanup service or a auto start service.
core.addService(new StopService(24 * 60 * 60 * 1000));
If you need a trigger of changing data, you can now add a trigger. As Example a logger trigger.
core.addTrigger(TriggerLogger);
It a ready to start than create and start the core. Now managed the core all things and controller the state.
core.createAndStart();
Should the application be stopped, only the core needs to be stopped.
core.stopAndDestroy();
Model
The model has an exact scheme of this and works with their rules.
Create a new Model with that name and the schema
const userModel = new Model('User',schema);
The schema is a object of SchemaType and it has many of entry.When you add a new entry, the name of the child object is the name you see. Then the type of entry must be set.
{
firstName: {
type: 'String'
}
}
Types:
String
Number
-
Id
Id of the own object -
Refs
A reference to a other model
General Options
Name | Type | Description |
---|---|---|
require |
boolean |
Must be a always a value |
name |
string |
That is the name for the database |
export |
boolean |
If export this entry or make it invisible |
exportName |
string |
Set the export name |
set |
(data : number/string/boolean) => number/string/boolean |
Change the value before it save in the database |
get |
(data : number/string/boolean) => number/string/boolean |
Change the value after read from the database |
protection |
boolean |
The value is write protection for the default way |
String Option
Name | Type | Description |
---|---|---|
min |
number |
Min length of string |
max |
number |
Max length of string |
validate |
(data:string) => boolean |
A function of own validation |
default |
string or ()= > string
|
If the value not defined than set this value by the inserting |
Number Option
Name | Type | Description |
---|---|---|
min |
number |
Min value |
max |
number |
Max value |
validate |
(data:number) => boolean |
A function of own validation |
default |
number or ()= > number
|
If the value not defined than set this value by the inserting |
Refs Option
Name | Type | Description |
---|---|---|
model |
string |
Model to the depend reference |
idName |
string |
Name of entry which are the reference |
depend |
boolean |
Delete the complete entry when don't a reference (true) or set only this null (false) |
Boolean Option
Name | Type | Description |
---|---|---|
default |
boolean |
If the value not defined than set this value by the inserting |
Endpoint
The endpoints are a interface to outside of system and can be control the data flow. The main function are the manage api request. For the normal request can be generate this functions which have some settings or you write simple a own method. Permission or other middleware can be set between many layers or request.
Schema of Api-Request
Arguments
Name | Type | Description |
---|---|---|
meta |
object |
Values of id or other meta / header information |
data |
object |
Values of insert, update, delete or reading data |
Result
Name | Type | Description |
---|---|---|
data |
object |
Result of data |
error |
Error[] |
List of errors |
Exist endpoints
WsServer
Name | Type | Description |
---|---|---|
https |
boolean |
Use https |
port |
number |
Port number |
key |
string or Buffer
|
Key of certificate |
cert |
string or Buffer
|
Key of certificate |
Connection with JSON
Name | Type | Description |
---|---|---|
id |
number |
Id of request and answer id |
path |
string |
Execute function path. |
args |
object |
Argument for the request |
RestApi
Name | Type | Description |
---|---|---|
https |
boolean |
Use https |
port |
number |
Port number |
key |
string or Buffer
|
Key of certificate |
cert |
string or Buffer
|
Key of certificate |
Own endpoint
class MyEnpoint extends Endpoint {
async create(): Promise<void> {
// Create the endpoint
}
async start(): Promise<void> {
// Start the endpoint
}
async stop(): Promise<void> {
// Stop the endpoint
}
async destroy(): Promise<void> {
// Destroy the endpoint
}
protected useRequest(request: ApiRequest): void {
// Implemention of api request
}
}
Api-Request Generator
enpoint.generateApiRequest({
core,
paths:[{
path:"api",
middleware: new RequestCounter(core),
descriptor: [
param('userId', 'string', true, false),
result(true,"Return the user values with the 'userId'.")
],
children:[{
path:"users",
async (core: Core, args: ApiRequestArgument): Promise<Result> => {
// TODO Something
return {}
}
}]
}]
});
Path Option
Name | Type | Description |
---|---|---|
path |
string |
Currently path |
middleware |
Middleware or Middleware[]
|
Changing/ checking or modification of arguments or throw errors |
descriptor |
Array of param(name: string, type: string, metaData?: boolean , require?: boolean, description?: string ) or result(success: boolean, message: string, details?: any)
|
Generate for a request all used params at path, permission and middleware and the result. Generate docs via endpoint.generateApiRequest(): ApiRequest[] |
children |
Path[] |
Sub paths of this path |
execute |
(core: Core, args: ApiRequestArgument) => Promise<ApiRequestResult> |
Execute function on this path |
Middleware
Middleware is an interface between a default request and special modification layer. As an example can you create a middleware for the api key management.
class RequestCounter extends Middleware{
private counter = 0;
async pre(args: ApiRequestArgument): Promise<RequestError[]> {
counter++;
// Exist error
return [];
}
async post(args: ApiRequestArgument): Promise<RequestError[]> {
counter--;
// Exist error
return [];
}
}
Api-Doc Generator
Generate a Api documentation via PathConfig with easy entries. Use the Description-Object in the
Path
,Middleware
or atPermission
. It exits simple function for entries (param(...)
,result(...)
,resultAsError(...)
,meta(...)
,group(...)
).
Adding Option
path = {
descriptor: [
...
]
}
class RequestCounter extends Middleware{
description(): Descriptor {
return [
...
];
}
}
permission = {
descriptor: [
...
]
}
Description function
- Add all inside request into this group:
group(name: string, details: string)
- Create Api-Entry:
request(name: string, details: string)
- Add Param:
param(name: string, type: string, metaData: boolean = true, require: boolean = true, description = '')
- Add Error:
resultAsError(error: Error)
- Add default result:
result(success: boolean, message: string, details?: any)
Generate Doc
- Read only the Api-Tree
DocBuilder(name: string, url: string, option: PathConfig)
- Build to a README text
MDGenerator(name: string, url: string, option: PathConfig)
Api-Request Tests
This eccosystem have an own integration test system of testing api request. An TestEndpoint simulate a normal endpoints and extends with test function.
Quick Using
const endpoint: Endpoint = new TestEndpoint();
// Do something ...
// Load tests object
const test = {
path: "/login",
arguments: async (core: Core, store: ArgumentStore): Promise<ApiRequestArgument> => {
// Load and init Arguments
return {
meta: {},
data: {
email: "abc@def.com",
password: "HelloWorld"
}
};
},
result: async (core: Core, store: ArgumentStore, result: Result): Promise<boolean> => {
// Check the result
return result.error && result.error.length > 0;
}
}
// Added
endpoint.addTest(test);
// Execute
endpoint.executeTests();
TestGroup
- The ArgumentStore is a cache inside a test group.
Name | Type | Description |
---|---|---|
name |
string |
Name of the testing group |
before |
(core: Core, store: ArgumentStore) => Promise<void> |
Execute before the test at this group starts |
after |
(core: Core, store: ArgumentStore) => Promise<void> |
Execute before the test at this group starts |
tests |
(TestGroup / TestRequest)[] |
The tests at this group. |
TestRequest
- The ArgumentStore is a cache inside a test group.
Name | Type | Description |
---|---|---|
path |
string |
Request path |
name |
string |
Name of Test |
arguments |
(core: Core, store: ArgumentStore) => Promise<ApiRequestArgument> |
Init the arguments for the request |
result |
(core: Core, store: ArgumentStore, result: Result) => Promise<boolean> |
Checking the result of request |
Service
A service is a external process and work in an interval of time. A own process may be an auto stop service of the core.
class StopService extends Service {
constructor(lifeTime: number) {
super("StopService", lifeTime);
}
async onEvent(core: Core): Promise<void> {
return core.stopAndDestroy();
}
}
Database
The core hasn't only one database but have a default construct of a database. Have you a new used database than can you add this into the core.
Implements databases
- MongoDB
- VirtualDB (for simple and fast testing)
class MyDataBase extends DataBase {
async connect(): Promise<void> {
// Connect with the database
}
async disconnect(): Promise<void> {
// Disconnect with the database
}
async createTable(model: Model): Promise<void> {
// Create a new table of model
}
async deleteByModel(model: Model, filter: DBFilter): Promise<void> {
// Delete data
}
async findByModel(model: Model, filter: DBFilter, option: FindOption = {}): Promise<DataValue[]> {
// Find many data
}
async insertByModel(data: DataValue, model: Model): Promise<DataValue> {
// Insert a new data
}
async updateByModel(data: DataValue, model: Model, filter: DBFilter): Promise<void> {
// Update the data
}
}
Trigger
A trigger call if insert, update or delete a data. A core can be have many trigger listener.
core.addTrigger({
async insert(model, values): Promise<void> {
// A data value was insert
},
async update(model, data, updates): Promise<void> {
// A data value was update
},
async delete(model, data): Promise<void> {
// A data value was delete
}
});