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..