koa-route-decors
The directory structure
└ ─ SRC │ ├─ Constants. Ts/ / constant│ ├ ─ ─ controller. Ts// @controller decorator│ ├ ─ ─ index. Ts// Import file│ ├ ─ ─ injectable. Ts// the @Injectable decorator declares classes that can be injected│ ├ ─ ─ injector. Ts/ / syringe│ ├─ ├─ Request// HTTP request method decorator, @get @post│ ├ ─ ─ the router. Ts// Load the route│ └ ─ ─ utils. Ts ├ ─ ─ package - lock. Json ├ ─ ─ package. The json ├ ─ ─ tsconfig. Json └ ─ ─ tslint. JsonCopy the code
The body of the
1. Define the decorator for HTTP request methods
// request.ts
import 'reflect-metadata';
import {RequestMethod} from './interface';
import {METHOD_METADATA, PATH_METADATA} from './constants';
export const Get = createDecorator('get');
export const Post = createDecorator('post');
export const Put = createDecorator('put');
export const Delete = createDecorator('delete');
export const Options = createDecorator('options');
export const Patch = createDecorator('patch');
// Decorator factory constructor
function createDecorator(method: RequestMethod) {
return function (path = ' ') {
return function (target: {[key: string]: any}, key: string, descriptor: PropertyDescriptor) {
// Append metadata
Reflect.defineMetadata(PATH_METADATA, path, descriptor.value); // Append the routing path
Reflect.defineMetadata(METHOD_METADATA, method, descriptor.value); // Attach the request method
};
};
}
Copy the code
2. Define the Controller decorator and declare that this class is a Controller that will be used to load routes later
/// controller.ts
import 'reflect-metadata';
import {Constructor} from './interface';
import {PATH_METADATA} from './constants';
export function Controller(path = ' ') {
return function (target: Constructor) {
Reflect.defineMetadata(PATH_METADATA, path, target); // Append route prefix path
};
}
Copy the code
3. Define a syringe, dependency injection container
// injector.ts
import 'reflect-metadata';
import {Constructor} from './interface';
// A single syringe class
class Injector {
static injector: Injector;
private readonly providerMap = new Map(a);// Store dependencies that can be injected
private constructor() {}
// Get an example of a syringe
static getInstance() {
if(! Injector.injector) { Injector.injector =new Injector();
}
return Injector.injector;
}
// Add dependencies to the injection container
inject(target: Constructor) {
// Get the constructor parameter type by reflection
const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
if (this.providerMap.has(target)) return; / / registered
for (const p of paramTypes) {
if (p === target) {
throw new Error('can not depend self');
} else if (!this.providerMap.has(p)) {
throw new Error('dependency is not register'); }}this.providerMap.set(target, target); // The dependency is put into the container
}
// Create an instance factory to actually inject dependencies into a given class
factory(target: Constructor) {
const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
// Get the dependencies to inject
const dependencies = paramTypes.map((item: Constructor) = > {
if (!this.providerMap.has(item)) {
throw new Error('dependency is not register');
} else if (item.length) {
return this.factory(item); // If there are dependencies, call recursively
} else {
return new item(); // There is no dependency}});return newtarget(... dependencies);// Inject all dependencies into the given class}}// Get the syringe instance and export it
const rootInjector = Injector.getInstance();
export {rootInjector};
Copy the code
4. Define decorators for classes that can be injected
// injectable.ts
import {rootInjector} from './injector';
export function Injectable() {
return function (target: any) {
rootInjector.inject(target); // Call the inject method of the syringe to put the class into the injection container
};
}
Copy the code
5. Construct routes and instantiate controllers
// router.ts
import 'reflect-metadata';
import {METHOD_METADATA, PATH_METADATA} from './constants';
import * as Router from 'koa-router';
import {Constructor} from './interface';
import {Utils} from './utils';
import {rootInjector} from './injector';
// Initialize the route
export function initRouter(controller: Constructor) {
const router = new Router();
const routes = mapRoute(controller); // Map all routes
// Bind the route to the koa-router
for (const item of routes) {
const {url, method, handle} = item;
switch (method) {
case 'get':
router.get(url, handle);
break;
case 'post':
router.post(url, handle);
break;
case 'put':
router.put(url, handle);
break;
case 'delete':
router.delete(url, handle);
break;
case 'options':
router.options(url, handle);
break;
case 'patch':
router.patch(url, handle);
break; }}return router;
}
// Automatically load the controllers in the *.controller.ts file under the given path, bind the route
export async function autoRouter(rootDir: string) {
const router = new Router();
const reg = /.+controller.ts$/;
const files = await Utils.getFile(rootDir); // Get all file paths under the given path
const controllers = files.filter(item= > reg.test(item)).map(item= > require(item)); // Import all controllers
for (const controller of controllers) {
const keys = Object.keys(controller);
// Get all the controllers that meet the requirements
const controllerClass = keys.map(item= > {
if (
Utils.isFunction(controller[item])
&& controller[item] === controller[item].prototype.constructor
&& typeof Reflect.getMetadata(PATH_METADATA, controller[item]) === 'string'
) {
return controller[item];
} else {
return false;
}
}).filter(item= > item);
// Traverse the controller and bind the merged route
controllerClass.forEach(item= > {
const subRouter = initRouter(item);
router.use(subRouter.routes());
});
}
return router;
}
// Get the route mapping of the controller
function mapRoute(controller: Constructor) {
const instance = rootInjector.factory(controller); // Instantiate the controller class
const prototype = Object.getPrototypeOf(instance); // Get the controller prototype object in which the class instance methods are all defined
// Get all method names of the class, excluding all method names that are not functions and constructors
const methodNames = Object.getOwnPropertyNames(prototype).filter(item= >! (prototype[item] === prototype.constructor) && Utils.isFunction(prototype[item]));return methodNames.map(methodName= > {
const handle = prototype[methodName]; // The route is actually executed
const prefix = Reflect.getMetadata(PATH_METADATA, controller); // Get the route prefix defined by the @Controller decorator
const method = Reflect.getMetadata(METHOD_METADATA, handle); // HTTP request methods @post, @get, etc
let url = Reflect.getMetadata(PATH_METADATA, handle); // Get the path of the routing method
url = url ? url : ` /${methodName}`; // If no path is passed, the method name is used by default
url = prefix + url; // Merge routes
return {url, method, handle: handle.bind(instance), methodName}; // Return a route map. Bind rebinds this
});
}
Copy the code
At this point, all the logic has been implemented.
Use pseudocode to simulate usage
├ ─ ─ the SRC │ ├ ─ ─ the user. The controller. The ts │ ├ ─ ─ the user. The service. The ts │ ├ ─ ─ the user. The model. The ts │ └ ─ ─ utils. Ts ├ ─ ─ package - lock. Json ├ ─ ─ Package. json ├─ tsconfig.json ├─ tslint.json// user.controller.ts
@Contriller()
export class UserController {
constructor(private userService: UserService) {} // Inject the service
@Get()
async userInfo(ctx: Context, next: () => Promise<any>) {
const {id} = ctx.request.body
const info = await this.userServuce.getUserInfoById(id); ctx.body = info; }}// user.service.ts
@Injectable()
export class UserService {
constructor(private userModel: UserModel) {}
async getUserInfoById(id: number) {
const info = await this.userModel.findById(id);
returninfo; }}// user.model.ts
@Injectable()
export class UserModel {
private repository: Repository<User>;
private select: (keyof User)[] = ['id'.'username'.'nickname'];
constructor() {
this.repository = getRepository(User);
}
async findById(id: number) {
const user = await this.repository.findOne(id, {select: this.select});
returnuser; }}Copy the code
The code has been uploaded to Github koa-Route-Decors, a complete koA project implementation example, Huzz-Koa-template, or see my other article on how to open nodeJS projects correctly, typescript + KOA