1.2.0 • Published 4 years ago

@o-galaxy/ether v1.2.0

Weekly downloads
-
License
ISC
Repository
github
Last release
4 years ago

Ether

Open Galaxy - Ether

REST API Framework, module based, each module constructed of -

  • controller(s)
  • provider(s)
  • guard(s)
  • middleware(s)

Those are the building blocks of an Ether API application.

API

ether/core

Controller

Controller(options: {path: string} = { path: '/'})

Decorator that marks a class as a Controller. a controller is a class where each class-method defines a route. (to define a route you must decorate the class-method with an Rest-Method decorator).

import { Request, Response, NextFunction } from "express";
import { UserHandler } from "./user.handler";
import { Controller, Get } from "@o-galaxy/ether/core";


@Controller({ path: '/user' })
export class UserController {


    constructor(private userHandler: UserHandler) {}
    
    @Get()
    async getUser(req: Request, res: Response, next: NextFunction) {
        const uid = res.locals.uid;
        try {
            let result = await this.userHandler.getUser(uid);

            return res.send(result);
        } catch (error) {
            next(error)
        }
    }
}

REST Methods

<METHOD>(route: string = '/', middlewares: (Array<RequestHandler> | RequestHandler) = [])

  • Get Get(route: string = '/', middlewares: (Array<RequestHandler> | RequestHandler) = [])

    Decorator that marks a class-method as a Get method for the provided `route`, where the provided middlewares precede the current handler method.
  • Post Post( : string = '/', middlewares: (Array<RequestHandler> | RequestHandler) = [])

    Decorator that marks a class-method as a Post method for the provided `route`, where the provided middlewares precede the current handler method.
  • Put Put(route: string = '/', middlewares: (Array<RequestHandler> | RequestHandler) = [])

    Decorator that marks a class-method as a Put method for the provided `route`, where the provided middlewares precede the current handler method.
  • Delete Delete(route: string = '/', middlewares: (Array<RequestHandler> | RequestHandler) = [])

    defines a Delete method for the provided `route`, where the provided middlewares precede the current handler method.
  • All All(route: string = '/', middlewares: (Array<RequestHandler> | RequestHandler) = [])

    Decorator that marks a class-method as a route handler for all api methods, for the provided `route`, where the provided middlewares precede the current handler method.

Guard

Guard()

interface IGuard {
    guard(req:Request, res:Response): (boolean | Promise<boolean>);
} 

Decorator that marks a class as a Guard. a guard is a middleware on a module level. It's basically a class implementing the IGuard interface. The guard method implements the logic of the guard middleware, returning false value of throwing an error will lead to an error handler.

import { Guard } from "@o-galaxy/ether/core";
import { IGuard } from "@o-galaxy/ether/models";

@Guard() 
export class AuthUserGuard implements IGuard {
    async guard(req, res): Promise<boolean> {
        try {
            let { authorization } = req.headers;
            authorization = this.parseAuthHeader(authorization);
            if(!authorization) {
                return false;   
            }
            // ...

            return true;
        } catch (error) {
            throw error;
        } 
    }

    parseAuthHeader(header: string): string {
        if(header.indexOf('Bearer') != 0) {
            throw 'bad auth header';
        }
        return header.slice('Bearer '.length);
    }
}

Provider

Decorator that marks a class as a Provider. To inject a provider class in another controller / provider / guard class constructor the class must be decorated with @Provider().

a provider is a class that ideally do one of :

  • holds the api call's business logic.
  • function as a separation layer between the controller and the db layer.
  • function as a generic (or not) utility
@Provider()
export class UserProvider {

	public async findAndUpdateUser(uid: string, email: string, payload: any ) {
		try {
			const user = await UserModel.findOneAndUpdate(
				// ...
			);
            return user;
			
		} catch (error) {
			throw error;
		}
	}

}

Module

Module(params: Partial<ModuleParameters>)

interface ModuleParameters {
    path: string;
    controllers: Array<any>;
    providers: Array<any>;
    modules: Array<any>;
    guards: Array<any>;
}

Modules are a way to group together set of code; controllers, providers, middlewares, that have related functionalities and workflow. Modules can be plugged into other modules, by doing so, any routes defined in the sub-module, prefixed by the path of the module is plugged into.

import { Module } from "@o-galaxy/ether/core";
import { LogisterController } from './logister/logister.controller';
import { UserController } from './user/user.controller';
import { AuthUserGuard } from '../../guards/auth-user.guard';


@Module({
    path: '/v1/',
    guards: [
        AuthUserGuard
    ],
    controllers: [
        UserController, 
        LogisterController
    ],
})
export class AuthUserModule { }

ether/common

build

build(module: any): express.Router

function used to build a router from a Module decorated class.

// -- file:  app.module.ts

import { Module } from "@o-galaxy/ether/core";
import { PublicModule } from "../v1/modules/public/public.module";
import { AdminModule } from "../v1/modules/admin/admin.module";
import { AuthUserModule } from "../v1/modules/auth-user/user.module";

@Module({
    modules: [
        AdminModule,
        PublicModule,
        AuthUserModule,
    ]
})
export class AppModule { }
// -- file: index.ts

import { build } from 'ether/common'
import { AppModule } from './app.module';

export const apiRouter = build(AppModule);
// -- file: server.ts
import { apiRouter } from './api/router'

const app = express();
app.use('/api/', apiRouter);
app.listen(3000);

middlewareFactory

middlewareFactory(handler: RequestHandler, context?: any): () => any

A function used to create a class-method middleware decorator function, from the provided handler function, if a context object was provided the handler function will be bound to it.

On the following example, using middlewareFactory, we're creating a Log middleware decorator function, by decorating postSubject route with Log decorator, the Log middleware will precede the postSubject route handler, and write the request url and body to the console.

Note : The middleware decorator function must code before the Rest-Method decorator.

import { middlewareFactory } from 'ether/core';

export const Log = middlewareFactory((req, res, next) => {
    console.log('request url: ' + req.originalUrl);
    console.log('request body: ' + req.body);
    next();
})
import { Request, Response, NextFunction } from "express";
import { Controller, Post } from "@o-galaxy/ether/core";
import { SubjectController } from "../../public/subject/subject.controller";

import { Log } from "../../..//middlewares";

@Controller({ path: '/subject' })
export class AdminSubjectController extends SubjectController {

    @Log()
    @Post()
    public async postSubject(req: Request, res: Response, next: NextFunction) {
        try {
            const { subject } = req.body;
            const result = await this.adminSubjectService.postSubject(subject);
            return res.send(result);
        } catch(error) {
            return next(error);
        }
    }

}

TODO

  • system error table - describable errors.
  • add better errors for injecting non provider classes.
  • support passing router options to controller.
  • support using guard as decorator on module level.
  • defaultize body / params - spec solution or use middlewares (with example).
  • abstract / hide the (req, res, next) signature, spec for inject body, params, query, etc..