The cause of

The Router describes the mapping between the request URL and the Controller. The Eggjs convention requires that all routes be declared in app/router.js. The directory structure is as follows:

┌ app ├ ─ ─ the router. Js │ ├ ─ ─ controller │ │ ├ ─ ─ home. Js │ │ ├ ─ ─...Copy the code

Routing and corresponding processing methods are maintained in two separate places, and often need to switch back and forth between router.js and Controller during development.

When the front and back end collaborate, the back end needs to generate a corresponding Api document for each Api to the front end.

A more elegant implementation

Thanks to JavaScript’s added decorator feature, we can do faceted programming as intuitively and naturally as Java/C# :

@route('/intro') async intro() {} @route('/intro') {Method: 'post'}) async intro() {} @route('/intro', {method: 'post', role: XxxRole}) Async intro() {} // Controller level middleware @route('/intro', {method: 'post', role: xxxRole, beforeMiddleware: xxMiddleware }) async intro() { }Copy the code

Why is that

Why design such complex functionality, and is it abusing decorators?

Let’s take a look at route’s functionality:

  • Route definition
  • Parameter calibration
  • permissions
  • ControllerLevel middleware

Router functionality included in the official complete definition of router: route definition, middleware, permissions, and “permissions” not directly written in the documentation:

router.verb('router-name'.'path-match', middleware1, ... , middlewareN, app.controller.action);Copy the code

Comparison will find, just more “parameter verification” function.

Parameter calibration

Official practice for parameter validation in Eggjs:

class PostController extends Controller {
    async create() { const ctx = this.ctx; Request. Body 'CTX. Validate (createRule); } catch (err) { ctx.logger.warn(err.errors); ctx.body = { success:false };
            return; }}};Copy the code

In our business practice, there are two problems with this scheme:

  • Parameter missing check

    For example, if the data submitted by the user is {a: ‘a’, ‘b’: ‘b’, c: ‘c’}, if only a is defined in the verification rule, b and C are omitted, and the two values may be used in subsequent services.

  • Eggjs User data can be obtained through CTx. request anytime and anywhere during the life cycle of a request

    The existence of “parameter leakage verification” leads to the instability of subsequent services, which may crash services or cause security problems due to abnormal data of users at any time.

The solution

In order to solve the problem of “parameter leakage check”, we made the following conventions:

  • Controller also needs to declare input parameters

    class UserController extends Controller {
        @route('/api/user', { method: 'post'}) async updateUser(username) { // ... }}Copy the code

    In the example above, even if the user submits a lot of data, the business code only gets username

  • Businesses other than Controller should not directly access the data on ctx.request

    That is, when a Service method depends on user data, it should get it through an input parameter rather than directly accessing ctx.request

Based on these conventions, let’s look at how we solve parameter validation problems in JS and TypeScript:

  • JS

    @route('/api/user', {
        method: 'post',
        rule: {
            username: { type: 'string', max: 20 },
        }   
    })
    async updateUser(username) {
        // ...
    }
    Copy the code

    The parameter of egg-validate’s underlying dependency is used as the verification library

  • TypeScript

    @route('/api/user', {
        method: 'post'
    })
    async updateUser(username: R<{ type: string, max: 20 }>) {
        // ...
    }
    Copy the code

Yes, the logic to manually call ctx.validate(createRule) and catch the exception was omitted. Laziness is the number one ingredient in productivity. When you already have parameters and rules, why do you need to code yourself?

New front – and back-end collaboration practices

In a traditional front – and back-end development collaboration, the back – end provides apis for the front – end to call, with code like this:

function updateUser() {
    request
        .post(`/api/user`, { username })
        .then(ret => {
            
        });
}
Copy the code

The front end students need to pay attention to routes, parameters, and return values. It is more convenient to generate the foreground service directly.

  • The Controller code:

    export class UserController {
    
      @route({ url: '/api/user' })
      async getUserInfo(id: number) {
        return{... }; }}Copy the code
  • Generated service:

    exportClass UserService extends Base {/** home */ async getUserInfo(id: number) {const __data = {id};returnawait this.request({ method: `get`, url: `/api/user`, data: __data, }); }}export const metaService = new UserService();
    export default new UserService();
    Copy the code
  • The front desk to use

    import { userService } from 'service/user';
    
    const userInfo = await userService.getUserInfo(id);
    Copy the code

    Compare the original writing:

    function updateUser() {
        return new Promise((resolve, reject) => {
            request
            .post(`/api/user`, { username })
            .then(ret => {
                resolve(ret);
            }); 
        });
    }
    Copy the code

    Userservice. getUserInfo encapsulates the request logic, so the front end does not need to care about the call process.

How can you use it in your own projects

We have abstracted the best practices as egg-Controller plug-ins, which can be installed and used as follows:

  1. To install an egg – controller

    tnpm i -S egg-controller
    Copy the code
  2. To enable the plugin

    Open config/plugin.js and add the following configuration

    aop: {
        enable: true,
        package: 'egg-aop',
    },
    controller: {
        enable: true,
        package: 'egg-controller',},Copy the code
  3. The use of plug-in

    Refer to the egg-Controller documentation for detailed usage