NodeJS Imitation WebApi Routing Example
- 2021-07-24 09:59:27
- OfStack
Anyone who has used WebApi or Asp. net MVC knows that Microsoft's routing design is very good, with 10 points convenient and 10 points flexible. Although it seems that some individuals are too flexible, it is easy for different developers in team to use different routing methods, which is a bit confusing. But this is not the point. When I was doing Node project, I felt that I kept using it
use(...)
To specify the routing path is annoying, so use the
Typescript
Wrote this based on
Koa
And
Koa-router
Routing plug-in, you can simply achieve a number of similar WebApi routing functions.
The target is like WebApi1:
1. Added controller will automatically join the route.
2. You can also specify the route manually through path ().
3. http method can be defined as
GET
Or
POST
Wait.
4. The parameters of Api can specify query param, path param and body in url.
Package has been uploaded to npm, npm install webapi-router installation, you can see the effect first:
Step 1: Set the directory of controllers and the fixed prefix of url
All controller are in this directory, which will automatically calculate the route according to the physical path. The fixed prefix of url is between host and the route, such as
localhost/api/v2/user/name
,
api/v2
This is the fixed prefix.
import { WebApiRouter } from 'webapi-router';
app.use(new WebApiRouter().router('sample/controllers', 'api'));
Step 2 is that controller inherits from
BaseController
export class TestController extends BaseController
{
}
Step 3 Add decorators to the controller 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
The parameter in is optional. If it is empty, the physical path of this controller will be used as the routing address.
Typescript
0
Is a variable in the path, such as
/user/brook
,
Typescript
0
Is
brook
Which can be obtained with @ PathParam in the parameters of the method
@QueryParam
Available
url
Li
?
Parameters after
@BodyParam
Available
Post
Coming up
body
Isn't it a bit of WebApi?
Now let's take a concrete look at how it is realized
The implementation process is actually very simple. Starting from the above goal, we first get the physical path of controllers, and then get the method decorated by the decorator and its parameters.
The purpose of the decorator is to get is
Get
Or
Post
Wait, and it is specified
Path
Finally, the data in node request is assigned to the parameters of the method.
Core code:
Get the physical path
initRouterForControllers() {
// Find out all inherited from the specified directory BaseController Adj. .js Documents
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;
}
Realization of decorator
Decorators need to be introduced
reflect-metadata库
Look at the decorator of the method first.
@GET
,
@POST
And so on, the implementation method is to add 1 attribute to the decorated method
Router
,
Router
It's a
Symbol
, ensure only 1. Then the function of analyzing decoration is stored in this attribute, such as
Method
,
Path
Wait.
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 is also a parameter decorator, which is used to assign parameters to
Koa-router
1
Values in, such as
body
,
param
Wait.
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 decorated data is stored on the Router property of the object, which can be used later when building the route.
Bind routing to
Koa-router
Upper
The route is obtained from the physical path above, but the parameter path in the decoration takes precedence, so look at the one just existing in the prototype first
Router
Is there any in the attribute
Path
If there is, use this as a route, and there is no
Path
Use physical routing.
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 values to method parameters in controller and bind routes to
KoaRouter
private setRouterForFunction(method: string, controller: any, funcName: string, routerPath: string){
this.koaRouter[method](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 execution controller Adj. api Method
try
{
ctx.body = await controller[funcName](...this.buildFuncParams(ctx, controller, controller[funcName]));
}
catch(err)
{
console.error(err);
next();
}
}
private buildFuncParams(ctx: any, controller: any, func: Function) { // Collect the specific values of the parameters
let paramsInfo = controller[Router][func.name].params;
let params = [];
if(paramsInfo)
{
for(let i = 0; i < paramsInfo.length; i++) {
if(paramsInfo[i]){
params.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){ // From ctx Take out the required parameters in
switch(paramType){
case ParamType.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 a simple version of WebApi-like routing.
Source download: webapi-router_jb51. rar