For koA-GRACE V1.x: github.com/xiongwilee/…

Gracejs(also known as KOA-Grace V2) is a new MVC+RESTful architecture based on KOA V2.x.

A list,

Gracejs is an upgraded version of KOA-Grace, also known as KOA-Grace V2.

Key features include:

  1. Support MVC architecture, which can generate server-side routing more easily;
  2. Standard RESTful architecture, support back-end interface asynchronous concurrency, better page performance;
  3. A set of Node environment services multiple site applications, deployment is simpler;
  4. Elegant MOCK function, the development environment to simulate data more smoothly;
  5. Perfect support for async/await and Generator syntax.
  6. More flexible front-end build types, use whatever you want, whatever you want.

Compared to KOA-GRACE V1 (KOA-GRACE v1) : Gracejs perfectly supports KOA V2, and has improved the performance of virtual host matching and route matching, as well as some test cases. Of course, don’t worry if you are using KOA-Grace, we will port the performance and functionality of Gracejs in addition to koA2 support to the corresponding MIDDLEWARE of KOA-Grace.

Concepts such as “front-end and back-end separation”, “RESTful” and “MVC” will not be introduced here. If you are interested, you can refer to the paper on the practice of front-end and back-end separation of Qudian based on KOAJS.

Gracejs and front and rear end separation problem ac group:

Start fast

Note: Make sure you have Nodejs at least v7.6.0 in your environment (or consider koA-Grace V1.x with Nodejs V4.x + support)

run

Then, execute the command:

$ npm run devCopy the code

Then visit: http://127.0.0.1:3000 to see an example!

The directory structure

Gracejs has exactly the same directory structure as koA-Grace V1.x:

.├ ─ ─ controller │ ├ ─ ─ data. Js │ ├ ─ ─ defaultCtrl. Js │ └ ─ ─ home. Js ├ ─ ─ the static │ ├ ─ ─ CSS │ ├ ─ ─ image │ └ ─ ─ js └ ─ ─ views └ ─ ─ home.htmlCopy the code

Among them:

  • controllerUsed to store routing and controller files
  • staticUsed to store static files
  • viewsUsed to store template files

It is important to note that this directory structure is the standard directory structure for production code. You can adjust your directory structure as much as you like in the development environment, as long as you make sure that the compiled output files are printed in this path.

If you still have questions about this, check out: Front-end construction -Boilerplate

The proxy mechanism

Take the realization of an e-commerce application under the “personal center” page as an example. Assume that the first screen of this page includes: user basic information module, commodity and order module, message notification module.

Once the back-end architecture is servified, these three modules can be decoupled into three HTTP API interfaces. Gracejs’ this.proxy method is used to asynchronously and concurrently fetch data from the three interfaces.

The diagram below:

This has several benefits:

  1. In Nodejs layer (server) asynchronously and concurrently obtain data from the back end (server), so that HTTP can go through the Intranet and the performance is better.
  2. The back-end interface can be provided to the client at the same time, so that the interface can be reused to Web+APP, and the back-end development cost is lower.
  3. Once the data is fetched in the Nodejs layer and handed directly to the page, regardless of the technology stack used on the front end, the first-screen experience is much better.

So, is that perfect? Surely not:

  1. How to ensure the security of a back-end interface after it is opened to the Internet?
  2. What if the current page request is a GET method but I want to POST to the back end?
  3. What if I want to reset the POST parameter at the Controller layer?
  4. How does the back-end interface set cookies to the browser?
  5. How can SESSION state not be lost after passing through a layer of Nodejs proxies?
  6. What if the current request is a file file stream? .

The good news is that these issues have been addressed in proxy middleware. Koa-grace-proxy can be found at github.com/xiongwilee/… .

4. Detailed user manual

Before looking at the detailed user manual, it is recommended to take a look at the main Gracejs file source: github.com/xiongwilee/… .

I won’t waste space to post code here, but Gracejs is a collection of key middleware components.

All middleware is in the Middleware directory, and the configuration is managed by config/main.*.js.

About configuration files:

  1. Json merge field > config/main.*.js > config.js;
  2. Configure the global scope saved under Gracejs after generationglobal.config, easy to read.

Here are the roles and uses of several key middleware components.

Router — Routing and controller

The route generation method in Gracejs is very simple. Take the built-in Demo module as an example. Go to the Controller directory of the Demo module: APP/Demo/Controller.

The file directories are as follows:

├─ ├─ class.js ├─ Class.jsCopy the code

2. Instructions for using routing files

To extend home.js in the Demo module:

exports.index = async function () {
    .
}
exports.index.__method__ = 'get';
exports.index.__regular__ = null;Copy the code

In addition, the following points need to be noted:

Of course, if all the controller methods in the routing file are POST methods, you can add: module.exports.__method__ = ‘post’ at the bottom of the controller file, and the same for __regular__ configuration.

Note: In general, no additional configuration is required here, so don’t write it without special usage scenarios to keep the code beautiful__method__and__regular__Configuration.

3. Controller

To extend the index method of home.js in the demo module:

exports.index = async function () {
  //Bind default controller methods
  await this.bindDefault(a);//To get the data
  await this.proxy(.)
  //Render target engine
  await this.render('home', {
    title: 'Hello , Grace!'
  });
}Copy the code

It’s a standard controller. The scope of this controller is the current KOA context, and you can use any method of the KOA context.

The instructions for using the key context attributes are as follows:

Koa comes with:

For more information about koA’s own context properties, see koajs’ official website: koajs.com/

The context properties type instructions
this.request.href String The full URL of the current page can also be shortened tothis.href
this.request.query object The get argument can also be abbreviated asthis.query
this.response.set function Set the response header, which can also be abbreviated asthis.set
this.cookies.set function To set cookies, see:cookies
this.cookies.get function To obtain cookies, refer to:cookies

Gracejs injection:

The context properties type The middleware instructions
this.bindDefault function router The common controller is equivalent torequire('app/*/controller/defaultCtrl.js')
this.request.body object body The post argument can be obtained directly from this.request.body
this.render function views For the Template engine rendering method, see Template engine – Template Engine
this.mongo function mongo For details about how to operate a Database, see Database-database
this.mongoMap function mongo For the parallel Database multiple operations, see Database-database
this.proxy function proxy For RESTful data request methods, see Data Broker
this.fetch function proxy For the method of exporting files from the server, see request proxy
this.backData Object proxy By default, THE JSON data returned by the this.proxy back end is stored in Obejct format
this.upload function xload For details about how to upload files, see File Upload and Download
this.download function xload For details about how to download files, see File Upload and Download

4. Write asynchronous functions in the controller

If there are other asynchronous methods in the controller, they can be implemented through promises. Such as:

exports.main = async function() {
  await ((test) = > {
    return new Promise((resolve.reject) = > {
      setTimeout(() = > { resolve(test) }, 3000)
    });
  })('test')}Copy the code

1. Data broker

Data proxies can use the this.proxy method in the controller:

this.proxy(object|string,[opt])Copy the code

Usage Scenario 1: Proxy for multiple data requests

It can be found that the above case is a proxy scheme for multiple data requests at the same time, which is the realization of asynchronous concurrent data acquisition. Implementing multiple asynchronous concurrent requests for data using the this.proxy method is very simple:

exports.demo = async function() {await this.proxy({
    userInfo:'github:post:user/login/oauth/access_token? client_id=****',
    otherInfo:'github:other/info? test=test'});console.log(this.backData);
  / * *
* {
* userInfo : {... },
* otherInfo : {... }
*}
   * /
}Copy the code

The result of the proxy is then injected into the context’s this.backData object by default.

instructions

github:post:user/login/oauth/access_token? Client_id =**** The description is as follows:

  • github: for in theconfig/main.*.jstheapiObject to configure;
  • post: is the request method requested by the data agent. This parameter can be omitted, and the default value is method requested by the current user.
  • pathThe query argument in the following request path overrides the request parameter of the current page (this.query), passing the query along with the request interface
  • You can also write the full path:{userInfo:'https://api.github.com/user/login?test=test'}

In addition, the parameter description for this.proxy is as follows:

Parameter names type The default instructions
dest Object this.backData Specifies the object to receive data from. Default isthis.backData
conf Obejct {} Enclosing the proxy is usedrequestjsThis is the reset configuration passed to request (you can set the interface timeout here:conf: { timeout: 25000 })
json Object {} To specify data in JSON format, see:Requestjs json configuration
form Object {} Specify application/ X-www-form-urlencoded data,Requestjs form configuration
body Buffer|String|ReadStream There is no Reference:Requestjs body configuration
headers Object {} Specifies the headers for the current request
onBeforeRequest Function There is no The callback event before each request is sent, scoped to the current context, has two parameters ‘proxy name’,’requestOpt’, can be operatedrequestOptTo modify requestJS parameters
onAfterRequest Function There is no The callback event after each request gets the result, scoped to the current context, has two parameters ‘proxy name’,’response’, can be operatedresponseTo modifythis.proxyReturn result of

If the json, form, and body parameters are not sent, the data body requested by the current client is sent to the back-end interface by default. The default proxy data is recommended. Of course, if there are special circumstances, it is ok to configure your own data.

In addition to the this.proxy parameter, you can also write the configuration for multiple concurrent requests:

this.proxy({ testInfo: { uri: 'github:other/info? test=test', form: { // formData }, headers: {}, // ... OtherInfo: {// same as above custom configuration}})Copy the code

This gives you the flexibility to customize configurations at the interface level.

There are a lot of interesting details about the this.proxy method, which are recommended for interested students: github.com/xiongwilee/…

2. File proxy

The file agent can use the this.fetch method in the controller:

this.fetch(string)Copy the code

File request proxy is also very simple. For example, if you need to proxy an image request from Github and return it to the browser, see feclub.cn/user/avatar… Or to use the function to export files:

exports.avatar = async function() {await this.fetch(imgUrl);
}Copy the code

The important thing to note here is that response is terminated directly after this.fetch and is not executed on any other middleware.

Static — Static file service

The use of a static file is very simple, will/static / / or / * / static / * * * static file request agent to the module/static directory under the path:

//Configure static file routing
app.use(Middles.static(['/static/**/*'.'/*/static/**/*'], {
  dir: config_path_project,
  maxage: config_site.env = = 'production' && 60 * 60 * 1000
}));Copy the code

Take the static file of the blog in this example. The path of the static file under the blog project is: App/blog/static/image/bg. JPG, Access path as http://127.0.0.1/blog/static/image/bg.jpg or http://127.0.0.1/static/blog/image/bg.jpg

Two points to note:

  1. The static file port matches the current route port, so/static/**/or/*/static/*Formal routing would be invalid;
  2. In the production environment, it is recommended to use Nginx for static file services and purchase CDN to host static files.

Secure — Security module

Considering that user routing is hosted entirely by Nodejs, CSRF issues have to be protected at the Nodejs layer as well. I have written an article before: CSRF defense mechanism in front and back end separation architecture. Here I will only write the use method, not detail the principle.

In Gracejs you can configure:

//CSRF configuration
csrf: {
  //Name of the module for which XSRF protection is to be performed
  module:[]}Copy the code

Then, in the business code, get the cookie named: grace_token and send it back with either POST or GET parameters. Of course, if you don’t want to pollute the parameter objects in Ajax, you can also store the cookie value in the X-Grace-Token header.

Gracejs listens for POST requests and returns an error if token validation fails.

1. Connect to the database

In the config file config/main.*.js:

  //Mongo configuration
  mongo: {
    options:{
      //Mongoose configuration
    },
    api:{
      'blog': 'mongodb://localhost:27017/blog'}},Copy the code

Mongo. options configudes the mongo connection pool and other information, and mongo. API configudes the database connection path corresponding to the site.

It is worth noting that once the database is configured, mongoose starts the connection as soon as Gracejs Server starts until Gracejs Server shuts down

3. Call the database in the controller

It is very simple to use in the controller, mainly through the this.mongo,this.mongoMap two methods.

2)this.mongoMap(option)

Parallel multiple database operations

Parameters that

@param [array] option

@param [Object] option[]. Model Mongoose Entity Object, obtained by this.mongo(model)

@param [function] option[]. Fun Mongoose Entity method

@param [array] option[]. Arg Mongoose Entity Object method parameter

return

@return [array] Database operation result, which is returned in the form of the corresponding array

case

  let PostModel = this.mongo('Post');
  let mongoResult = await this.mongoMap([{
      model: PostModel,
      fun: PostModel.page,
      arg: [pageNum]
    },{
      model: PostModel,
      fun:PostModel.count,
      arg: [pageNum]
    }]);

  let posts = mongoResult[0];//Gets the result of the first query, postModel.page
  let page = mongoResult[1]; //Gets the result of the second query, postModel.count, executed concurrentlyCopy the code

1. File upload

Methods:

this.upload([opt])Copy the code

Example:

exports.aj_upload = async function() {
  await this.bindDefault(a);let files = await this.upload(a);let res = {};

  if (!files || files.length < 1) {
    res.code = 1;
    res.message = 'Failed to upload file!';
    return this.body = res; 
  }

  res.code = 0;
  res.message = '';
  res.data = {
    files: files
  }

  return this.body = res;
}Copy the code

other

Several core middleware in Gracejs are introduced. In addition, there are several middleware without detailed introduction, understanding can be:

  1. Gzip implementation: Use gzip to compress the body in response;
  2. HTTP Body content parsingParse the body of the request and save it tothis.request.bodyIn the field.
  3. Simple session implementation: Saving sessions in memory or Redis is not recommended in production environments. Session services in the production environment are performed by the back end.

Finally, the operation and maintenance deployment of Gracejs is not detailed here. Pm2 is recommended, and there is no need to worry about service unavailability during server restart.

Boilerplate

There are already boilerplates based on Vue and RequireJS.

Take a VUe-based build as an example.

The directory structure

A complete directory structure based on vue+Gracejs recommends using this pattern:

.├ ─ ─ app │ └ ─ ─ demo │ ├ ─ ─ build │ ├ ─ ─ controller │ ├ ─ ─ the mock │ ├ ─ ─ the static │ ├ ─ ─ views │ └ ─ ─ vues └ ─ ─ server ├ ─ ─ app │ └ ─ ─ Demo Exercises ── Middleware Exercises...Copy the code

Of course, Server (i.e. Gracejs) allows you to configure the app directory path, which you can place in any directory you want.

The demo module has two more directories than the demo module under the default server: Build and vues.

conclusion

Since then, Gracejs has finally been introduced. If you are interested, go to Github and give a star: github.com/xiongwilee/… .

Finally, welcome to issue, fork; If you have any questions, please contact xiongwilee[at]foxmail.com.