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 trigg


Keywords
backend, core, database, endpoint, service, express, model, trigger-events, validation, websocket
License
MIT
Install
npm install zodora-backend-core@0.0.64

Documentation

Logo

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

  1. Core
  2. Model
  3. Endpoint
  4. Service
  5. Database
  6. Trigger

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 at Permission. 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
    }
});