WEBAPI or ASP. NET MVC knows that Microsoft's routing is very good, convenient and flexible. Although the individual seems to be too flexible, the different development within the team is easy to use different routing methods and seems a bit confusing. But this is not the point, I do the node project when I feel that the use of the use(...)
specified routing path is very annoying, so Typescript
write this based on Koa
Koa-router
the routing plug-in, you can easily implement some similar webapi-like routing capabilities.
The goal is the same as Webapi:
- The controller added will automatically join the route.
- Routes can also be specified manually via path ().
- You can define HTTP method, such as or, and
GET
POST
so on.
- The parameters of the API can specify query param, path param, and body in the URL.
The package has been uploaded to NPM, npm install Webapi-router installation, you can first look at the effect:
The first step is to set a fixed prefix for the controllers directory and URL.
All controllers are in this directory, which automatically calculates the route based on the physical path. The fixed prefix of the URL is between the host and the route, for example localhost/api/v2/user/name
, api/v2
the fixed prefix.
import { WebApiRouter } from ‘webapi-router‘;app.use(new WebApiRouter().router(‘sample/controllers‘, ‘api‘));
The second step is that the controller BaseController
inherits from
export class TestController extends BaseController{}
The third step is to add a decorator to the controller's method.
@POST(‘/user/:name‘)postWithPathParam(@PathParam(‘name‘) name: string, @QueryParam(‘id‘) id: string, @BodyParam body: any) { console.info(`TestController - post with name: ${name}, body: ${JSON.stringify(body)}`); return ‘ok‘;}
@POST
parameter is optional, and the physical path of the controller is used as the routing address for Null.
:name
is the variable in the path, for example, it /user/brook
:name
can be brook
used in the parameters of the method. @PathParam
@QueryParam
You can url
get ?
the parameters in the back.
@BodyParam
Can get Post
up thebody
Is it a little webapi?
now, let's see how it all came true.
The implementation process is very simple, starting from the above goal, first get the physical path of the controllers, and then get the decoration of the method and its parameters.
The object of the adorner is whether to get it, to Get
Post
wait, to specify it Path
, and finally to assign the data in node request to the parameters of the method.
Core code:
get the physical path
initRouterForControllers() { //找出指定目录下的所有继承自BaseController的.js文件 let files = FileUtil.getFiles(this.controllerFolder); files.forEach(file => { let exportClass = require(file).default; if(this.isAvalidController(exportClass)){ this.setRouterForClass(exportClass, file); } });}
turn from physical path to Route
private buildControllerRouter(file: string){ let relativeFile = Path.relative(Path.join(FileUtil.getApiDir(), this.controllerFolder), file); let controllerPath = ‘/‘ + relativeFile.replace(/\\/g, ‘/‘).replace(‘.js‘,‘‘).toLowerCase(); if(controllerPath.endsWith(‘controller‘)) controllerPath = controllerPath.substring(0, controllerPath.length - 10); return controllerPath;}
the implementation of the adorner
Adorners need to be introduced into the reflect-metadata
library
First look at the method of the adorner, @GET
and the @POST
like, the implementation method is to decorate the method with a property Router
, Router
is a Symbol
, to ensure that the only. Then the function of the analysis decoration is stored in this attribute, for example Method
, Path
etc.
export function GET(path?: string) { return (target: BaseController, name: string) => setMethodDecorator(target, name, ‘GET‘, path);} function setMethodDecorator(target: BaseController, name: string, method: string, path?: string){ target[Router] = target[Router] || {}; target[Router][name] = target[Router][name] || {}; target[Router][name].method = method; target[Router][name].path = path;}
There are also parametric decorators, which are used to assign values to parameters request
, such as body
, and param
so on.
export function BodyParam(target: BaseController, name: string, index: number) { setParamDecorator(target, name, index, { name: "", type: ParamType.Body });}function setParamDecorator(target: BaseController, name: string, index: number, value: {name: string, type: ParamType}) { let paramTypes = Reflect.getMetadata("design:paramtypes", target, name); target[Router] = target[Router] || {}; target[Router][name] = target[Router][name] || {}; target[Router][name].params = target[Router][name].params || []; target[Router][name].params[index] = { type: paramTypes[index], name: value.name, paramType: value.type };}
This data is stored on the router property of the object, which can be used when building the route later.
The bindings are routed to Koa-router
the top
The above is routed from the physical path, but the parameter path in the decoration is preferred, so first look at the properties of the prototype in existence there is Router
no Path
, some words use this as a route, there is no Path
physical route.
private setRouterForClass(exportClass: any, file: string) { let controllerRouterPath = this.buildControllerRouter(file); let controller = new exportClass(); for(let funcName in exportClass.prototype[Router]){ let method = exportClass.prototype[Router][funcName].method.toLowerCase(); let path = exportClass.prototype[Router][funcName].path; this.setRouterForFunction(method, controller, funcName, path ? `/${this.urlPrefix}${path}` : `/${this.urlPrefix}${controllerRouterPath}/${funcName}`); }}
Assign a value to the method parameter in the controller and bind the route toKoaRouter
Private Setrouterforfunction (method:string, Controller:any, funcname:string, routerpath:string) {This.koaRouter[met Hod] (Routerpath, async (CTX, next) = {await This.execapi (CTX, Next, controller, FuncName)}); Private Async Execapi (Ctx:Koa.Context, Next:function, Controller:any, funcname:string): promise<void> {//this is The line Controller's API method is the try {ctx.body = await controller[funcname] (... this.buildfuncparams (CTX, Controller, CONTR Oller[funcname])); } catch (Err) {console.error (err); Next (); }}private buildfuncparams (Ctx:any, Controller:any, func:function) {//to collect the parameter specific values let Paramsinfo = Controller[router ][func.name].params; let params = []; if (Paramsinfo) {for (Let i = 0; i < paramsinfo.length; i++) {if (Paramsinfo[i]) {PA Rams.push (Paramsinfo[i].type (This.getparam (CTX, Paramsinfo[i].paramtype, Paramsinfo[i].name)); } else {Params.push (CTX); }}} return params;} Private GetParam (Ctx:any, Paramtype:paramtype, name:string) {//Take the required parameters out of CTX switch (paramtype) {case Paramt Ype. Query:return Ctx.query[name]; Case ParamType.Path:return Ctx.params[name]; Case ParamType.Body:return Ctx.request.body; Default:console.error (' does not-support this param type '); }}
This completes the simple version of similar WEBAPI routing, source code in Https://github.com/brookshi/webapi-router, welcome everyone fork/star, thank you.
"Open source" Nodejs imitation Webapi routing