@o-galaxy/ether
Version:
Rest Api Framework
339 lines (246 loc) • 8.34 kB
Markdown
# 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.
<br>
<br>
<br>
## API
<br>
### **ether/core**
<br>
#### Controller
`Controller(options: {path: string} = { path: '/'})`
Decorator that marks a class as a Controller. <br>
a controller is a class where each class-method defines a route. <br>
(to define a route you must decorate the class-method with an [Rest-Method decorator](REST-Method)).
```ts
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)
}
}
}
```
<br>
<hr>
<br>
#### REST Methods
`<METHOD>(route: string = '/', middlewares: (Array<RequestHandler> | RequestHandler) = [])`
<br>
* **Get** <br>
`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.
<br>
* **Post** <br>
`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.
<br>
* **Put** <br>
`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.
<br>
* **Delete** <br>
`Delete(route: string = '/', middlewares: (Array<RequestHandler> | RequestHandler) = [])`
defines a Delete method for the provided `route`, where the provided middlewares precede the current handler method.
<br>
* **All** <br>
`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.
<br>
<hr>
<br>
#### Guard
`Guard()`
```ts
interface IGuard {
guard(req:Request, res:Response): (boolean | Promise<boolean>);
}
```
<br>
Decorator that marks a class as a Guard. <br>
a guard is a middleware on a module level. <br>
It's basically a class implementing the `IGuard` interface. <br>
The `guard` method implements the logic of the guard middleware, returning `false` value of throwing an error will lead to an error handler. <br>
```ts
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);
}
}
```
<br>
<hr>
<br>
#### Provider
Decorator that marks a class as a Provider. <br>
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<br>
```ts
@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;
}
}
}
```
<br>
<hr>
<br>
#### Module
`Module(params: Partial<ModuleParameters>)` <br>
```ts
interface ModuleParameters {
path: string;
controllers: Array<any>;
providers: Array<any>;
modules: Array<any>;
guards: Array<any>;
}
```
<br>
Modules are a way to group together set of code; controllers, providers, middlewares, that have related functionalities and workflow. <br>
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. <br>
```ts
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 { }
```
<br>
<hr>
<br>
<br>
### **ether/common**
<br>
#### build
`build(module: any): express.Router` <br>
function used to build a router from a `Module` decorated class. <br>
```ts
// -- 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 { }
```
```ts
// -- file: index.ts
import { build } from 'ether/common'
import { AppModule } from './app.module';
export const apiRouter = build(AppModule);
```
```ts
// -- file: server.ts
import { apiRouter } from './api/router'
const app = express();
app.use('/api/', apiRouter);
app.listen(3000);
```
<br>
<hr>
<br>
#### 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. <br>
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. <br>
*Note* : <br>
The middleware decorator function must code before the Rest-Method decorator.
<br>
```ts
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();
})
```
```ts
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..