Front-end development-Ali's web back-end development framework egg.js

What is Egg.js?

Egg.js is a nodejs-based development, born for enterprise-level frameworks and applications. We hope that Egg.js will nurture more upper-level frameworks to help development teams and developers reduce development and maintenance costs.

github address:

https://github.com/eggjs/egg

characteristic

  • Provide the ability to customize the upper framework based on Egg
  • Highly extensible plug-in mechanism
  • Built-in multi-process management
  • Based on Koa development, excellent performance
  • Stable framework and high test coverage
  • Progressive development

Quick start

This article will build an Egg.js application step by step from the perspective of examples, so that you can quickly get started with Egg.js.

Environmental preparation

  • Operating system: support macOS, Linux, Windows
  • Operating environment: It is recommended to select the LTS version, and the minimum requirement is 8.x.

Quick initialization

We recommend using the scaffolding directly, you can quickly generate the project with just a few simple instructions (npm >=6.1.0):

$ mkdir egg-example && cd egg-example
$ npm init egg --type=simple
$ npm i

Startup project:

$ npm run dev
$ open http://localhost:7001

Build step by step

Usually you can use the npm init egg to quickly select the scaffolding suitable for the corresponding business model and quickly start the development of the Egg.js project using the method in the previous section.

But in order to let everyone better understand Egg.js, next, we will skip the scaffolding and manually build a Hacker News step by step.

Note: In actual projects, we recommend using the scaffolding in the previous section for direct initialization.

Front-end development-Ali's web back-end development framework egg.js

 

Initialize the project

First, initialize the directory structure:

$ mkdir egg-example
$ cd egg-example
$ npm init
$ npm i egg --save
$ npm i egg-bin --save-dev

Add npm scripts to package.json:

{
  "name": "egg-example",
  "scripts": {
    "dev": "egg-bin dev"
  }
}

Write Controller

If you are familiar with Web development or MVC, you must guess that the first step we need to write is Controller and Router.

// app/controller/home.js
const Controller = require('egg').Controller;

class HomeController extends Controller {
  async index() {
    this.ctx.body = 'Hello world';
  }
}

module.exports = HomeController;

Configure route mapping:

// app/router.js
module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
};

Add a configuration file:

// config/config.default.js
exports.keys = <此处改为你自己的 Cookie 安全字符串>;

The directory structure at this time is as follows:

egg-example
├── app
│   ├── controller
│   │   └── home.js
│   └── router.js
├── config
│   └── config.default.js
└── package.json

OK, now you can start the app to experience

$ npm run dev
$ open http://localhost:7001

note:

Controller has two writing methods, class and exports. This article demonstrates the former. You may need to refer to the Controller documentation. Config also has the wording of module.exports and exports, please refer to the Node.js modules documentation for details.

Static resources

Egg has a built-in static plug-in, and it is recommended to deploy to CDN in the online environment. This plug-in is not required.

Static plugin default mapping /public/* -> app/public/* directory

Here, we can put all the static resources in the app/public directory:

app/public
├── css
│   └── news.css
└── js
    ├── lib.js
    └── news.js

Template rendering

In most cases, we need to render the template after reading the data, and then present it to the user. Therefore, we need to introduce the corresponding template engine.

The framework does not force you to use a certain template engine, it just agrees on the View plug-in development specification, and developers can introduce different plug-ins to achieve differentiated customization.

For more usage, see View.

In this example, we use Nunjucks to render, first install the corresponding plug-in egg-view-nunjucks:

$ npm i egg-view-nunjucks --save

Open the plug-in:

// config/plugin.js
exports.nunjucks = {
  enable: true,
  package: 'egg-view-nunjucks'
};
// config/config.default.js
exports.keys = <此处改为你自己的 Cookie 安全字符串>;
// 添加 view 配置
exports.view = {
  defaultViewEngine: 'nunjucks',
  mapping: {
    '.tpl': 'nunjucks',
  },
};

Note: It is the config directory, not app/config!

Write a template file for the list page, usually placed in the app/view directory

<!-- app/view/news/list.tpl -->
<html>
  <head>
    <title>Hacker News</title>
    <link rel="stylesheet" href="/public/css/news.css" />
  </head>
  <body>
    <ul class="news-view view">
      {% for item in list %}
        <li class="item">
          <a href="{
   
   { item.url }}">{
   
   { item.title }}</a>
        </li>
      {% endfor %}
    </ul>
  </body>
</html>

Add Controller and Router

// app/controller/news.js
const Controller = require('egg').Controller;

class NewsController extends Controller {
  async list() {
    const dataList = {
      list: [
        { id: 1, title: 'this is news 1', url: '/news/1' },
        { id: 2, title: 'this is news 2', url: '/news/2' }
      ]
    };
    await this.ctx.render('news/list.tpl', dataList);
  }
}

module.exports = NewsController;

// app/router.js
module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
  router.get('/news', controller.news.list);
};

Start the browser and visit http://localhost:7001/news to see the rendered page.

Tip: The development plug-in is enabled by default during the development period. After modifying the back-end code, the Worker process will be restarted automatically.

Write service

In practical applications, Controller generally does not produce data by itself, nor does it contain complex logic. The complex process should be abstracted as a business logic layer Service.

Let's add a Service to fetch Hacker News data, as follows:

The framework provides a built-in HttpClient to facilitate developers to use HTTP requests.

// app/service/news.js
const Service = require('egg').Service;

class NewsService extends Service {
  async list(page = 1) {
    // read config
    const { serverUrl, pageSize } = this.config.news;

    // use build-in http client to GET hacker-news api
    const { data: idList } = await this.ctx.curl(`${serverUrl}/topstories.json`, {
      data: {
        orderBy: '"$key"',
        startAt: `"${pageSize * (page - 1)}"`,
        endAt: `"${pageSize * page - 1}"`,
      },
      dataType: 'json',
    });

    // parallel GET detail
    const newsList = await Promise.all(
      Object.keys(idList).map(key => {
        const url = `${serverUrl}/item/${idList[key]}.json`;
        return this.ctx.curl(url, { dataType: 'json' });
      })
    );
    return newsList.map(res => res.data);
  }
}

module.exports = NewsService;

Then slightly modify the previous Controller:

// app/controller/news.js
const Controller = require('egg').Controller;

class NewsController extends Controller {
  async list() {
    const ctx = this.ctx;
    const page = ctx.query.page || 1;
    const newsList = await ctx.service.news.list(page);
    await ctx.render('news/list.tpl', { list: newsList });
  }
}

module.exports = NewsController;

Also need to add the configuration read in app/service/news.js:

// config/config.default.js
// 添加 news 的配置项
exports.news = {
  pageSize: 5,
  serverUrl: 'https://hacker-news.firebaseio.com/v0',
};

Write an extension

Encountered a small problem, our information time data is in UnixTime format, and we hope to display it in a format that is easy to read.

The framework provides a way to quickly expand, just provide the extension script in the app/extend directory, see Extensions for details.

Here, we can use the Helper supported by the View plug-in to achieve:

$ npm i moment --save
// app/extend/helper.js
const moment = require('moment');
exports.relativeTime = time => moment(new Date(time * 1000)).fromNow();

Use in the template:

<!-- app/view/news/list.tpl -->
{
   
   { helper.relativeTime(item.time) }}

Write Middleware

Suppose there is a demand: our news site is forbidden to visit by Baidu crawlers.

Smart students must soon think that User-Agent can be judged by Middleware, as follows:

// app/middleware/robot.js
// options === app.config.robot
module.exports = (options, app) => {
  return async function robotMiddleware(ctx, next) {
    const source = ctx.get('user-agent') || '';
    const match = options.ua.some(ua => ua.test(source));
    if (match) {
      ctx.status = 403;
      ctx.message = 'Go away, robot.';
    } else {
      await next();
    }
  }
};

// config/config.default.js
// add middleware robot
exports.middleware = [
  'robot'
];
// robot's configurations
exports.robot = {
  ua: [
    /Baiduspider/i,
  ]
};

Now you can use curl http://localhost:7001/news -A "Baiduspider" to see the effect.

See the middleware documentation for more.

Configuration file

When writing a business, it is inevitable to have a configuration file. The framework provides powerful configuration merge management functions:

  • Support loading different configuration files according to environment variables, such as config.local.js, config.prod.js and so on.
  • Applications/plugins/frames can configure their own configuration files, and the frameworks will be merged and loaded in order.
  • The specific merge logic can be found in the configuration file.
// config/config.default.js
exports.robot = {
  ua: [
    /curl/i,
    /Baiduspider/i,
  ],
};

// config/config.local.js
// only read at development mode, will override default
exports.robot = {
  ua: [
    /Baiduspider/i,
  ],
};

// app/service/some.js
const Service = require('egg').Service;

class SomeService extends Service {
  async list() {
    const rule = this.config.robot.ua;
  }
}

module.exports = SomeService;

Directory Structure

In the quick start, everyone should have a preliminary impression of the framework, and then we briefly understand the following catalog convention specifications.

egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app
|   ├── router.js
│   ├── controller
│   |   └── home.js
│   ├── service (可选)
│   |   └── user.js
│   ├── middleware (可选)
│   |   └── response_time.js
│   ├── schedule (可选)
│   |   └── my_task.js
│   ├── public (可选)
│   |   └── reset.css
│   ├── view (可选)
│   |   └── home.tpl
│   └── extend (可选)
│       ├── helper.js (可选)
│       ├── request.js (可选)
│       ├── response.js (可选)
│       ├── context.js (可选)
│       ├── application.js (可选)
│       └── agent.js (可选)
├── config
|   ├── plugin.js
|   ├── config.default.js
│   ├── config.prod.js
|   ├── config.test.js (可选)
|   ├── config.local.js (可选)
|   └── config.unittest.js (可选)
└── test
    ├── middleware
    |   └── response_time.test.js
    └── controller
        └── home.test.js

As above, the directory agreed by the framework:

  • app/router.js is used to configure URL routing rules, see Router for details.
  • app/controller/** is used to parse the user's input and return the corresponding result after processing, see Controller for details.
  • app/service/** is used to write the business logic layer. It is optional and recommended. For details, see Service.
  • app/middleware/** is used to write middleware, optional, see Middleware for details.
  • app/public/** is used to place static resources, optional, see the built-in plug-in egg-static for details.
  • app/extend/** is used to extend the framework, optional, please refer to the framework extension for details.
  • config/config.{env}.js is used to write configuration files, see configuration for details.
  • config/plugin.js is used to configure the plug-ins that need to be loaded, see plug-ins for details.
  • test/** is used for unit testing, see unit testing for details.
  • app.js and agent.js are used to customize the initialization work at startup. They are optional. For details, see Start Customization. For the role of agent.js, see Agent mechanism.

The directory agreed by the built-in plugin:

  • app/public/** is used to place static resources, optional, see the built-in plug-in egg-static for details.
  • app/schedule/** is used for scheduled tasks, optional, see Scheduled Tasks for details.

If you need to customize your own catalog specification, see Loader API

  • app/view/** is used to place template files. It is optional and agreed by the template plugin. For details, see Template Rendering.
  • app/model/** is used to place the domain model, optional, agreed by the domain-related plug-ins, such as egg-sequelize.

If you think the effect is good, please help add a focus and praise, and share the front-end practical development skills every day

Guess you like

Origin blog.csdn.net/wangxi06/article/details/115001118