Task: Add reflection metadata for decorators and add basic implements for controller and express application and constants and app
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Sambo Chea 2021-09-14 09:06:03 +07:00
parent a6546b7676
commit 67c94211e7
13 changed files with 148 additions and 46 deletions

View File

@ -24,6 +24,7 @@
"license": "ISC",
"devDependencies": {
"@types/express": "^4.17.13",
"@types/node": "^16.9.1",
"@typescript-eslint/eslint-plugin": "^4.30.0",
"@typescript-eslint/parser": "^4.30.0",
"dotenv": "^10.0.0",
@ -36,12 +37,12 @@
"lint-staged": "^11.1.2",
"prettier": "2.3.2",
"ts-node-dev": "^1.1.8",
"typescript": "^4.4.2",
"@types/node": "^16.9.1"
"typescript": "^4.4.2"
},
"dependencies": {
"@cubetiq/ts-common": "1.0.0",
"express": "^4.17.1",
"@cubetiq/ts-common": "1.0.0"
"reflect-metadata": "^0.1.13"
},
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"

View File

@ -1,5 +1,8 @@
// app config
import "./dotenv"
// reflection metadata for decorator
import "reflect-metadata"
// core app
import "./server"
import "./server"

View File

@ -1,23 +0,0 @@
import express, { Application as ExpressApp } from "express"
class Application {
private readonly _instance: ExpressApp
get instance(): ExpressApp {
return this._instance
}
constructor() {
this._instance = express()
this._instance.use(express.json())
this.registerRoutes()
}
private registerRoutes(): void {
this._instance.get("/", (req, res) => {
res.send("Hello World!")
})
}
}
export default new Application()

View File

@ -1,3 +0,0 @@
const index = (req: any, res: any) => {
res.send("Hello World!")
}

52
src/application.ts Normal file
View File

@ -0,0 +1,52 @@
import express, { Application as ExpressApp, Handler } from "express"
import { controllers } from "./controller"
import { RouteHandler } from "./decorators/handlers.decorator"
import { MetadataKeys } from "./constants/metadata.keys"
class Application {
private readonly _instance: ExpressApp
get instance(): ExpressApp {
return this._instance
}
constructor() {
this._instance = express()
this.registerRoutes()
}
private registerRoutes(): void {
const info: Array<{ api: string; handler: string }> = []
controllers.forEach((controller) => {
const controllerInstance: { [handlerName: string]: Handler } =
new controller() as any
const basePath: string = Reflect.getMetadata(
MetadataKeys.BASE_PATH,
controller
)
const routers: RouteHandler[] = Reflect.getMetadata(
MetadataKeys.ROUTERS,
controller
)
const exRouter = express.Router()
routers.forEach(({ method, path, handlerName }) => {
exRouter[method](
path,
controllerInstance[String(handlerName)]
).bind(controllerInstance)
info.push({
api: `${method.toLocaleUpperCase()} ${basePath + path}`,
handler: `${controller.name}.${String(handlerName)}`,
})
})
this._instance.use(basePath, exRouter)
})
console.table(info)
}
}
export default new Application()

View File

@ -0,0 +1,4 @@
export enum MetadataKeys {
BASE_PATH = "base_path",
ROUTERS = "routers",
}

View File

@ -0,0 +1,13 @@
import { Request, Response } from "express"
import Controller from "../decorators/controller.decorator"
import { Get } from "../decorators/handlers.decorator"
@Controller("/hello")
export default class IndexController {
constructor() {}
@Get("")
public index(req: Request, res: Response) {
res.send(`Hello Index`)
}
}

3
src/controller/index.ts Normal file
View File

@ -0,0 +1,3 @@
import IndexController from "./index.controller"
export const controllers = [IndexController]

View File

@ -0,0 +1,9 @@
import { MetadataKeys } from "../constants/metadata.keys"
const Controller = (basePath: string): ClassDecorator => {
return (target: any) => {
Reflect.defineMetadata(MetadataKeys.BASE_PATH, basePath, target)
}
}
export default Controller

View File

@ -0,0 +1,41 @@
import { MetadataKeys } from "../constants/metadata.keys"
export enum Method {
GET = "get",
POST = "post",
}
export interface RouteHandler {
method: Method
path: string
handlerName: string | symbol
}
const methodDecoratorFactory = (method: Method) => {
return (path: string): MethodDecorator => {
return (target: any, propertyKey: string | symbol): void => {
const controllerClass = target.constructor
const routers: RouteHandler[] = Reflect.hasMetadata(
MetadataKeys.ROUTERS,
controllerClass
)
? Reflect.getMetadata(MetadataKeys.ROUTERS, controllerClass)
: []
routers.push({
method,
path,
handlerName: propertyKey,
})
Reflect.defineMetadata(
MetadataKeys.ROUTERS,
routers,
controllerClass
)
}
}
}
export const Get = methodDecoratorFactory(Method.GET)
export const Post = methodDecoratorFactory(Method.POST)

View File

@ -1,7 +1,7 @@
import { createServer } from "http"
import { SERVER_PORT } from "./app.config"
import { info } from "@cubetiq/ts-common/dist/log"
import application from "./app/application"
import application from "./application"
// get current host id
const hostId = `${require("os").hostname()}#${process.pid}`
@ -11,7 +11,7 @@ const app = application.instance
const httpServer = createServer(app)
info(
`Application server running on: ${hostId} at: http://0.0.0.0:${SERVER_PORT} and started at: ${startedAt}`
`Application server running on: ${hostId} at port: ${SERVER_PORT} and started at: ${startedAt}`
)
httpServer.listen(SERVER_PORT)

1
src/utils/utils.ts Normal file
View File

@ -0,0 +1 @@
// nothing here for now

View File

@ -1,15 +1,16 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"lib": ["ES2015"],
"skipLibCheck": true
},
"exclude": ["node_modules"]
}
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"lib": ["ES2015"],
"skipLibCheck": true,
"experimentalDecorators": true
},
"exclude": ["node_modules"]
}