protontype
Version:
A simple REST framework make in TypeScript
316 lines (287 loc) • 12.1 kB
text/typescript
import 'reflect-metadata';
import Express from 'express';
import fs from 'fs';
import https from 'https';
import winston from 'winston';
import { DBConnector, ProtonDB } from '../database/DBConnector';
import { DefaultDBConnector } from '../database/DefaultDBConnector';
import { RouteConfig } from '../decorators/RouteConfig';
import { DefaultMiddleware } from '../middlewares/DefaultMiddleware';
import { BaseMiddleware } from '../middlewares/BaseMiddleware';
import { BaseRouter } from '../router/BaseRouter';
import { Method } from '../router/Method';
import { MiddlewareFunctionParams } from './../decorators/MiddlewareConfig';
import { RouterFunctionParams } from './../decorators/RouteConfig';
import { Logger } from './Logger';
import { DEFAULT_CONFIG, GlobalConfig, ProtonConfigLoader, ServerConfig } from './ProtonConfigLoader';
/**
* @author Humberto Machado
* Protontype main class. Configure and start Routers, Middlewares and bootstrap application
*/
export class ProtonApplication{
private express: Express.Application;
private middlewares: BaseMiddleware[] = [];
private routers: BaseRouter[] = [];
private config: GlobalConfig;
private logger: winston.LoggerInstance;
private dbConnector: DBConnector<any, any>;
/**
* Create Protontype aplication
*/
constructor(config?: GlobalConfig) {
this.config = this.loadConfig(config);
this.logger = Logger.createLogger(this.config.logger);
this.express = Express();
}
/**
* Start up Protontype application.
* @return express instance
*/
public start(): Promise<ProtonApplication> {
return new Promise<ProtonApplication>((resolve, reject) => {
this.connectDB().then(connection => {
ProtonDB.dbConnection = connection;
this.configMiddlewares();
this.configureRoutes();
this.startServers();
resolve(this);
}).catch(error => reject(error));
});
}
public connectDB(): Promise<any> {
if (this.dbConnector) {
return this.dbConnector.createConnection(this.config.database);
} else {
return new DefaultDBConnector().createConnection(this.config.database);
}
}
private startServers(): void {
let servers: ServerConfig[] = this.config.servers;
if (servers && servers.length > 0) {
servers.forEach(server => {
this.startServer(server.port, server.useHttps);
});
} else {
this.logger.error('No server found');
}
}
private startServer(port: number, useHttps: boolean) {
if (this.config.https && useHttps) {
const credentials = {
key: fs.readFileSync(this.config.https.key),
cert: fs.readFileSync(this.config.https.cert)
};
https.createServer(credentials, this.express)
.listen(port, () => this.logger.info(`Application listen port ${port} (HTTPS)`));
}
else {
this.express.listen(port, () => this.logger.info(`Application listen port ${port} (HTTP)`));
}
}
private loadConfig(config?: GlobalConfig): GlobalConfig {
if (config) {
return config;
} else {
config = ProtonConfigLoader.loadConfig();
if (!config) {
config = DEFAULT_CONFIG;
}
return config;
}
}
/**
* Configure global Middlewares. Application scope
*/
private configMiddlewares(): void {
new DefaultMiddleware().init(this).configMiddlewares();
this.middlewares.forEach(middleware => {
middleware.init(this);
middleware.configMiddlewares();
this.express.use((req, res, next) => {
middleware.middlewareFuntion.call(middleware, this.createMiddlewareFunctionParams(req, res, next, this))
if (middleware.autoNext) {
next();
}
});
});
}
/**
* Initialize all configured routes annotated with @Route
*/
private configureRoutes(): void {
this.routers.forEach(router => {
router.init(this);
let configs: RouteConfig[] = router.getRouteConfigs();
if (configs != null) {
let routerMiddewares = this.configRouterMiddlewares(router);
configs.forEach(config => {
if (config.method != null && config.endpoint != null) {
this.createRoutesByMethod(config, router, routerMiddewares);
} else {
config.routeFunction.call(router);
}
});
}
});
}
private createRoutesByMethod(routeConfig: RouteConfig, router: BaseRouter, routerMiddlewares: Express.Handler[]): void {
switch (routeConfig.method) {
case Method.GET:
this.express.get(router.getBaseUrl() + routeConfig.endpoint, routerMiddlewares, this.configRouteMiddlewares(routeConfig), (req, res) => {
routeConfig.routeFunction.call(router, this.createRouterFunctionParams(req, res, this));
});
break;
case Method.POST:
this.express.post(router.getBaseUrl() + routeConfig.endpoint, routerMiddlewares, this.configRouteMiddlewares(routeConfig), (req, res) => {
routeConfig.routeFunction.call(router, this.createRouterFunctionParams(req, res, this));
});
break;
case Method.PUT:
this.express.put(router.getBaseUrl() + routeConfig.endpoint, routerMiddlewares, this.configRouteMiddlewares(routeConfig), (req, res) => {
routeConfig.routeFunction.call(router, this.createRouterFunctionParams(req, res, this));
});
break;
case Method.DELETE:
this.express.delete(router.getBaseUrl() + routeConfig.endpoint, routerMiddlewares, this.configRouteMiddlewares(routeConfig), (req, res) => {
routeConfig.routeFunction.call(router, this.createRouterFunctionParams(req, res, this));
});
break;
case Method.PATCH:
this.express.patch(router.getBaseUrl() + routeConfig.endpoint, routerMiddlewares, this.configRouteMiddlewares(routeConfig), (req, res) => {
routeConfig.routeFunction.call(router, this.createRouterFunctionParams(req, res, this));
});
break;
case Method.OPTIONS:
this.express.options(router.getBaseUrl() + routeConfig.endpoint, routerMiddlewares, this.configRouteMiddlewares(routeConfig), (req, res) => {
routeConfig.routeFunction.call(router, this.createRouterFunctionParams(req, res, this));
});
break;
case Method.HEAD:
this.express.head(router.getBaseUrl() + routeConfig.endpoint, routerMiddlewares, this.configRouteMiddlewares(routeConfig), (req, res) => {
routeConfig.routeFunction.call(router, this.createRouterFunctionParams(req, res, this));
});
break;
}
}
private createRouterFunctionParams(req: Express.Request, res: Express.Response, app: ProtonApplication): RouterFunctionParams {
return { req: req, res: res, app: app }
}
private createMiddlewareFunctionParams(req: Express.Request, res: Express.Response,
next: Express.NextFunction, app: ProtonApplication): MiddlewareFunctionParams {
return { req: req, res: res, next: next, app: app }
}
public withDBConnector(dbConnector: DBConnector<any, any>): this {
this.dbConnector = dbConnector;
return this;
}
public withDBConnectorAs(dbConnector: { new(...args: any[]) }): this {
this.withDBConnector(new dbConnector());
return this;
}
public withConfig(config: any) : this {
this.config = config;
return this;
}
/**
* Configures the Route Scope Middlewares
*/
private configRouteMiddlewares(config: RouteConfig): Express.Handler[] {
return this.getExpressMiddlewaresList(config.middlewares);
}
/**
* Configures the Router Scope Middlewares
*/
private configRouterMiddlewares(router: BaseRouter): Express.Handler[] {
return this.getExpressMiddlewaresList(router.getRouterMiddlewares());
}
private getExpressMiddlewaresList(BaseMiddlewares: BaseMiddleware[]): Express.Handler[] {
let middlewares: Express.Handler[] = [];
if (BaseMiddlewares) {
BaseMiddlewares.forEach(middleware => {
if (middleware && middleware.middlewareFuntion) {
middleware.init(this);
middleware.configMiddlewares();
middlewares.push((req, res, next) => {
middleware.middlewareFuntion.call(middleware, this.createMiddlewareFunctionParams(req, res, next, this));
if (middleware.autoNext) {
next();
}
});
} else {
middlewares.push((req, res, next) => { next() });
}
})
} else {
middlewares.push((req, res, next) => { next() });
}
return middlewares;
}
/**
* Add Router to application
* @param router Router implementation
*/
public addRouter(router: BaseRouter): this {
this.routers.push(router);
return this;
}
public addRouterAs(router: { new(...args: any[]) }): this {
this.addRouter(new router());
return this;
}
/**
* Add Global Middleware. A middleware added here, will act for all routers of the application
* @param middleware Middleware implementation
*/
public addMiddleware(middleware: BaseMiddleware): this {
this.middlewares.push(middleware);
return this;
}
public addMiddlewareAs(middleware: { new(...args: any[]) }): this {
this.addMiddleware(new middleware());
return this;
}
/**
* Return a express instance
* @see {@link http://expressjs.com/en/4x/api.html}
*/
public getExpress(): Express.Application {
return this.express;
}
/**
* Return a list of Confugured routers
*/
public getRouters(): BaseRouter[] {
return this.routers;
}
/**
* Return {@link GlobalConfig} object. Content of proton.json file
*/
public getConfig(): GlobalConfig {
return this.config;
}
/**
* @return list of all endpoints loaded in ProtonApplication
*/
public getRoutesList(): { method: string, path: string }[] {
let routeList: any[] = [];
this.express._router.stack.forEach(r => {
if (r.route && r.route.path) {
routeList.push({
method: r.route.stack[0].method.toUpperCase(),
path: r.route.path
});
}
});
this.routers.forEach(router => {
router.getRouter().stack.forEach(r => {
if (r.route && r.route.path) {
routeList.push({
method: r.route.stack[0].method.toUpperCase(),
path: router.getBaseUrl() + r.route.path
});
}
});
});
return routeList;
}
}