So let’s go back to where we were last time, where we said:

  • Perform process analysis
  • Directory structure analysis
  • File loading sequence
  • The life cycle
  • Frame built-in base object *
    • Application
    • Context
    • Request & Response
    • Controller
    • Service
    • Helper
    • Config
    • Logger

Runtime environment

A Web application itself should be stateless and have the ability to set itself according to the environment in which it runs.

Specify the operating environment

  • throughconfig/envFile specifies that the contents of this file are the runtime environment, such asprod
  • throughEGG_SERVER_ENVEnvironment variable designation

Method 2 is common because it is easier to specify the runtime environment with the EGG_SERVER_ENV environment variable, such as starting the application in a production environment:

EGG_SERVER_ENV=prod npm start
Copy the code

Get the running environment in the application

Get it using app.config.env

Difference from NODE_ENV

Runtime environment and mapping supported by the framework by default (if EGG_SERVER_ENV is not specified, NODE_ENV will be matched)

NODE_ENV EGG_SERVER_ENV instructions
local Local development environment
test unittest Unit testing
production prod The production environment

When NODE_ENV is production and EGG_SERVER_ENV is not specified, the framework sets EGG_SERVER_ENV to prod.

Custom Environment

While there may be more to a normal development process than the above, Egg supports custom environments to suit your own development process.

For example, add an integration test environment, SIT, to the development process. Set EGG_SERVER_ENV to sit (and NODE_ENV = production is recommended). Config /config.sit.js will be loaded on startup and the run environment variable app.config.env will be set to sit.

Differences with Koa

In Koa we use app.env to make environment judgments. App. env defaults to process.env.node_env.

In Egg (and egg-based frameworks), configuration unity is placed on app.config, so we need to distinguish environments by app.config.env, which is no longer used.

The Config configuration

The framework provides powerful and extensible configuration capabilities that automatically merge application, plug-in, and framework configurations, overwrite them sequentially, and maintain different configurations based on the environment. The combined configuration can be obtained directly from app.config.

Egg selects configuration as code, and configuration changes should be reviewed before being published. The application package itself can be deployed in multiple environments by specifying the runtime environment.

Multi-environment Configuration

The framework supports loading configuration by environment, defining configuration files for multiple environments

config
|- config.default.js
|- config.prod.js
|- config.unittest.js
|- config.local.js
Copy the code

Config.default. js is the default configuration file. This configuration file is loaded by all environments and is generally used as the default configuration file for development environments.

When env is specified, the corresponding configuration file is loaded and the configuration of the same name in the default configuration file is overwritten. If the prod environment loads config.prod.js and config.default.js files, config.prod.js overwrites the config.default.js configuration of the same name.

Configuration of writing

The configuration file returns an Object object, which can cover some configurations of the framework. Applications can also store their own service configurations here for easy management, and obtain them directly from app.config.

Export object notation

// Configure the directory for logger files. Logger is provided by the framework by default
module.exports = {
  logger: {
    dir: '/home/admin/logs/demoapp',}};Copy the code

The configuration file can also return a function, which is acceptableappInfoparameter

// Put the logger directory in the code directory
const path = require('path');
module.exports = appInfo= > {
  return {
    logger: {
      dir: path.join(appInfo.baseDir, 'logs'),}}; };Copy the code

The built-in appInfo is:

appInfo instructions
pkg package.json
name Application name, same as pkg.name
baseDir Directory of application code
HOME User directory, for example, the admin account is /home/admin
root The application root directory is baseDir only in local and Unittest environments, and HOME only in other environments.

Appinfo.root is an elegant adaptation, such as in the server environment we would use /home/admin/logs as the log directory, but local development does not want to pollute the user directory, such an adaptation is a good solution.

Merging rules

Merge of configurations using the Extend2 module for deep copy

const a = {
  arr: [ 1.2]};const b = {
  arr: [ 3]}; extend(true, a, b);
// => { arr: [ 3 ] }
// The framework overwrites arrays directly instead of merging them.
Copy the code

Configure the results

Json (worker process) and run/agent_config.json (agent process), which can be used to analyze problems.

Middleware

Egg is implemented based on Koa, so the middleware form of Egg is the same as the MIDDLEWARE form of Koa, based on the Onion ring model. Every time we write a piece of middleware, we put a layer on the onion.

Writing middleware

writing

How middleware content is written

// app/middleware/gzip.js
const isJSON = require('koa-is-json');
const zlib = require('zlib');

async function gzip(ctx, next) {
  await next();

  // Convert the response body to GZIP after subsequent middleware execution is complete
  let body = ctx.body;
  if(! body)return;
  if (isJSON(body)) body = JSON.stringify(body);

  // Set gzip body to correct the response header
  const stream = zlib.createGzip();
  stream.end(body);
  ctx.body = stream;
  ctx.set('Content-Encoding'.'gzip');
}
Copy the code

The framework’s middleware is written exactly the same as Koa’s middleware, so any Koa middleware can be used directly by the framework.

configuration

Typically, middleware also has its own configuration.

We agreed that a middleware is a separate file placed in the app/middleware directory that requires exports a normal function that takes two arguments:

  • Options: middleware configuration items that the framework will useapp.config[${middlewareName}]So you can get the middleware configuration directly from the configuration file
  • App: instance of the current Application Application

To make the gzip middleware above a simple optimization to specify that gzip compression is performed only if the body is greater than the configured threshold, create a new file gzip.js in the app/ Middleware directory

// app/middleware/gzip.js
const isJSON = require('koa-is-json');
const zlib = require('zlib');

module.exports = options= > {
  return async function gzip(ctx, next) {
    await next();

    // Convert the response body to GZIP after subsequent middleware execution is complete
    let body = ctx.body;
    if(! body)return;

    / / support options. Threshold
    if (options.threshold && ctx.length < options.threshold) return;

    if (isJSON(body)) body = JSON.stringify(body);

    // Set gzip body to correct the response header
    const stream = zlib.createGzip();
    stream.end(body);
    ctx.body = stream;
    ctx.set('Content-Encoding'.'gzip');
  };
};
Copy the code

Using middleware

After the middleware is written, we also need to manually mount it

Use middleware in your application

Add the following configuration in config.default.js to enable and configure the middleware

module.exports = {
  // Configure the required middleware. The array order is the loading order of the middleware
  middleware: [ 'gzip'].// Configure the gzip middleware configuration
  gzip: {
    threshold: 1024.// Response body less than 1k does not compress
  },
  // options.gzip.threshold
};
Copy the code

This configuration will eventually be merged into app.config.AppMiddleware at startup

Use middleware in frameworks and plug-ins

Frameworks and plugins do not support matching middleware in config.default.js.

// app.js
module.exports = app= > {
  // Count the request time at the front of the middleware
  app.config.coreMiddleware.unshift('report');
};

// app/middleware/report.js
module.exports = (a)= > {
  return async function (ctx, next) {
    const startTime = Date.now();
    await next();
    // Report the request time
    reportTime(Date.now() - startTime); }};Copy the code

Middleware defined by the application layer (app.config.appMiddleware) and the framework default middleware (app.config.coreMiddleware) are loaded by the loader and mounted toapp.middlewareOn.

The router uses middleware

The middleware configured in these two ways is global and handles every request

If you only want to apply to a single route, you can instantiate and mount it directly in app/router.js as follows:

module.exports = app= > {
 const gzip = app.middleware.gzip({ threshold: 1024 });
 app.router.get('/needgzip', gzip, app.controller.handler);
};
Copy the code

Framework Default middleware

In addition to the application layer loading middleware, the framework itself and other plug-ins also load a lot of middleware.

All the built-in middleware configuration items can be modified by modifying the middleware configuration items of the same name in the configuration.

The bodyParser middleware comes with the framework (the loader of the framework changes the various separators in the file name to the variable name in the form of a camel). If we want to modify the bodyParser configuration, we just need to write it in config/config.default.js

module.exports = {
  bodyParser: {
    jsonLimit: '10mb',}};Copy the code

Use Koa’s middleware

It is very easy to introduce the Koa middleware ecosystem within the framework.

Using koA-COMPRESS as an example, when used in KOA:

const koa = require('koa');
const compress = require('koa-compress');

const app = koa();

const options = { threshold: 2048 };
app.use(compress(options));
Copy the code

Egg

// app/middleware/compress.js
// KoA-COMPRESS exposes an interface (' (options) => middleware ') consistent with the middleware requirements of the framework
module.exports = require('koa-compress');
Copy the code

Configuring middleware

// config/config.default.js
module.exports = {
  middleware: [ 'compress'].compress: {
    threshold: 2048,}}Copy the code

General configuration

  • Enable: controls whether the middleware is enabled
  • Match: Sets that only requests that match certain rules will pass through this middleware
  • Ignore: Sets requests that comply with certain rules not to pass through this middleware

Match and ignore

Match and ignore support the same parameters, but have opposite effects. Match and ignore cannot be configured at the same time.

module.exports = {
  gzip: {
    match: '/static',}};module.exports = {
  gzip: {
    match(ctx) {
      // Enabled only on ios devices
      const reg = /iphone|ipad|ipod/i;
      return reg.test(ctx.get('user-agent')); ,}}};Copy the code

Matching rules, from egg-path-matching

const pathMatching = require('egg-path-matching');
const options = {
  ignore: '/api'.// string will use parsed by path-to-regexp
  // support regexp
  ignore: /^\/api/.// support function
  ignore: ctx= > ctx.path.startsWith('/api'),
  // support Array
  ignore: [ ctx= > ctx.path.startsWith('/api'), /^\/foo$/, '/bar'].// support match or ignore
  match: '/api'};Copy the code

routing

Router is mainly used to describe the mapping between the request URL and the Controller that performs the specific action. The framework defines the app/router.js file to unify all routing rules.

How to define Router

  • app/router.jsIt defines URL routing rules
// app/router.js
module.exports = app= > {
  const { router, controller } = app;
  router.get('/user/:id', controller.user.info);
};
Copy the code
  • app/controllerThe Controller is implemented below the directory
// app/controller/user.js
class UserController extends Controller {
  async info() {
    const { ctx } = this;
    ctx.body = {
      name: `hello ${ctx.params.id}`}; }}Copy the code

Router definition description

The following is the complete definition of the route. Parameters can be selected according to different scenarios:

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

The complete definition of routing consists of five main parts:

  • Verb – Indicates an action triggered by a user. It supports all HTTP methods, such as GET and POST.
    • router.head – HEAD
    • router.options – OPTIONS
    • router.get – GET
    • router.put – PUT
    • router.post – POST
    • router.patch – PATCH
    • router.delete – DELETE
    • router.del– Because delete is a reserved word, an alias for the delete method is provided.
    • router.redirect– You can redirect urls, such as the one we use most often to route the user’s access to the root directory to a home page.
  • Router-name Specifies an alias for a route. You can use the Helper functions pathFor and urlFor to generate urls. (optional)
  • Path-match-route URL path
  • middleware1… MiddlewareN – Can have multiple Middleware configurations in the Router. (Optional), specifying that the URL path goes through only these middleware processes
  • Controller – Specifies the specific controller to which routes are mapped. Controller can be written either way:
    • app.controller.user.fetch– Specify a specific controller directly
    • 'user.fetch'– Can be shortened to a string

Matters needing attention

  • In the Router definition, multiple Middleware implementations can be supported in tandem
  • Controller must be defined in the app/ Controller directory
  • A file can also contain multiple Controller definitions, which can be passed when defining routes${fileName}.${functionName}To specify the corresponding Controller.
  • The Controller supports subdirectories that can be passed when defining routes${directoryName}.${fileName}.${functionName}To specify the corresponding Controller.

demo

// app/router.js
module.exports = app= > {
  const { router, controller } = app;
  router.get('/home', controller.home);
  router.get('/user/:id', controller.user.page);
  router.post('/admin', isAdmin, controller.admin);
  router.post('/user', isLoginUser, hasAdminPermission, controller.user.create);
  router.post('/api/v1/comments', controller.v1.comments.create); // app/controller/v1/comments.js
};
Copy the code

RESTful URL definition

Provides app.resources(‘routerName’, ‘pathMatch’, Controller) to quickly generate CRUD routing structures on a path.

// app/router.js
module.exports = app= > {
  const { router, controller } = app;
  router.resources('posts'.'/api/posts', controller.posts);
  router.resources('users'.'/api/v1/users', controller.v1.users); // app/controller/v1/users.js
};
Copy the code

The code above deploys a set of CRUD path structures on the /posts path, corresponding to Controller app/ Controller /posts.js. Then, you just need to implement the corresponding function in posts.js.

Method Path Route Name Controller.Action
GET /posts posts app.controllers.posts.index
GET /posts/new new_post app.controllers.posts.new
GET /posts/:id post app.controllers.posts.show
GET /posts/:id/edit edit_post app.controllers.posts.edit
POST /posts posts app.controllers.posts.create
PUT /posts/:id post app.controllers.posts.update
DELETE /posts/:id post app.controllers.posts.destroy
// app/controller/posts.js
exports.index = async() = > {}; exports.new =async() = > {}; exports.create =async() = > {}; exports.show =async() = > {}; exports.edit =async() = > {}; exports.update =async() = > {}; exports.destroy =async() = > {};Copy the code

If we don’t need any of these methods, we don’t need to implement them in posts.js, so that the URL path is not registered with the Router.

The router of actual combat

Parameters to obtain

The Query String way

ctx.query.xxx

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

// app/controller/search.js
exports.index = async ctx => {
  ctx.body = `search: ${ctx.query.name}`;
};

/ / curl http://127.0.0.1:7001/search? name=egg
Copy the code

Parameter naming

/user/:id/:name => ctx.params.xxx

// app/router.js
module.exports = app= > {
  app.router.get('/user/:id/:name', app.controller.user.info);
};

// app/controller/user.js
exports.info = async ctx => {
  ctx.body = `user: ${ctx.params.id}.${ctx.params.name}`;
};

/ / curl http://127.0.0.1:7001/user/123/xiaoming
Copy the code

Complex parameter acquisition

Routing also supports the definition of re, which can be more flexible to obtain parameters:

// app/router.js
module.exports = app= > {
  app.router.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, app.controller.package.detail);
};

// app/controller/package.js
exports.detail = async ctx => {
  // If the request URL is matched by the re, it can be retrieved from ctx.params in the order of the capture groups.
  // The content of 'ctx.params[0]' is' egg/1.0.0 'as requested below
  ctx.body = `package:${ctx.params[0]}`;
};

/ / curl http://127.0.0.1:7001/package/egg/1.0.0
Copy the code

Fetching form content

// app/router.js
module.exports = app= > {
  app.router.post('/form', app.controller.form.post);
};

// app/controller/form.js
exports.post = async ctx => {
  ctx.body = `body: The ${JSON.stringify(ctx.request.body)}`;
};
Copy the code

Here a direct POST request will result in an error.

The above verification is due to the built-in egg-Security plug-in in the framework, which provides some default security practices. Security plug-ins of the framework are enabled by default. If you want to disable some security defenses, you can directly set the enable attribute of this item to false.

Shut down CSRF temporarily

exports.security = {
  csrf: false
};
Copy the code

Form validation

npm i -S egg-validate
Copy the code
// config/plugin.js
module.exports = {
  // had enabled by egg
  // static: {
  // enable: true,
  // }
  validate: {
    enable: true.package: 'egg-validate',}};Copy the code
// app/router.js
module.exports = app= > {
  app.router.resources('/user', app.controller.user);
};

// app/controller/user.js
const createRule = {
  username: {
    type: 'email',},password: {
    type: 'password'.compare: 're-password',}}; exports.create =async ctx => {
  // If the verification fails, an exception will be thrown
  ctx.validate(createRule);
  ctx.body = ctx.request.body;
};
Copy the code

redirect

Internal redirection

// app/router.js
module.exports = app= > {
  app.router.get('index'.'/home/index', app.controller.home.index);
  app.router.redirect('/'.'/home/index'.303); // Access/automatically redirects to /home/index
};

// app/controller/home.js
exports.index = async ctx => {
  ctx.body = 'hello controller';
};
Copy the code

External redirection

exports.index = async ctx => {
  const type = ctx.query.type;
  const q = ctx.query.q || 'nodejs';

  if (type === 'bing') {
    ctx.redirect(`http://cn.bing.com/search?q=${q}`);
  } else {
    ctx.redirect(`https://www.google.co.kr/search?q=${q}`); }};Copy the code

Controller

The Controller is responsible for parsing the user’s input, processing it and returning the result.

The framework recommends that the Controller layer mainly processes the user’s request parameters (verification and conversion), then invokes the corresponding Service method to process the business, and encapsulates and returns the business results after obtaining them:

  • Gets the request parameters passed by the user over HTTP
  • Calibration and assembly parameters
  • The Service is called for business processing and, if necessary, the return result of the transformed Service is processed to suit the needs of the user
  • The result is returned to the user via HTTP

How to write Controller

All Controller files must be stored in the app/ Controller directory. Multi-level directories can be supported. Access can be cascaded by directory name

Controller class (recommended)

// app/controller/post.js
const Controller = require('egg').Controller;
class PostController extends Controller {
  async create() {
    const { ctx } = this;
    
    ctx.body = 'PostController';
    ctx.status = 201; }}module.exports = PostController;
Copy the code

The method defined above can be located by filename and method name in the route via app.controller

// app/router.js
module.exports = app= > {
  const { router, controller } = app;
  router.post('createPost'.'/api/posts', controller.post.create);
}
Copy the code

Controller supports multiple directories, for example, if we put the Controller code in app/Controller/sub/post js, then

app.router.post('createPost'.'/api/posts', app.controller.sub.post.create);
Copy the code

The Controller class defined by the properties description instantiates a brand new object with each request to the server, while the Controller class in the project inherits from egg.controller and has the following properties attached to this.

  • this.ctx: An instance of the current request Context object, through which we can get convenience properties and methods wrapped in the framework to handle the current request.
  • this.app: An instance of the current Application object, from which we can get global objects and methods provided by the framework.
  • this.service: Apply the defined Service, through which we can access the abstract business layer, equivalent to this.ctx.service.
  • this.config: Indicates the configuration item of the application runtime.
  • this.logger: logger object, which has four methods (debug.info.warn.error) indicates that four levels of logs are printed. The method and effect are the same as those described in Context Logger, but logs recorded by this Logger object are preceded by the path of the log file to quickly locate the log location.

Customize the Controller base class

// app/core/base_controller.js
const { Controller } = require('egg');
class BaseController extends Controller {
  get user() {
    return this.ctx.session.user;
  }

  success(data) {
    this.ctx.body = {
      success: true,
      data,
    };
  }

  notFound(msg) {
    msg = msg || 'not found';
    this.ctx.throw(404, msg); }}module.exports = BaseController;
Copy the code

BaseController: BaseController: BaseController: BaseController: BaseController: BaseController

//app/controller/post.js
const Controller = require('.. /core/base_controller');
class PostController extends Controller {
  async list() {
    const posts = await this.service.listByUser(this.user); // Use base class methods
    this.success(posts); // Use base class methods}}Copy the code

The Controller method

Each Controller is an Async function whose input is an instance of the requested Context object, through which we can get various convenient properties and methods encapsulated by the framework.

// app/controller/post.js
exports.create = async ctx => {
  const createRule = {
    title: { type: 'string' },
    content: { type: 'string'}};// Check parameters
  ctx.validate(createRule);
  // Assemble parameters
  const author = ctx.session.userId;
  const req = Object.assign(ctx.request.body, { author });
  // Call service for business processing
  const res = await ctx.service.post.create(req);
  // Set the response content and response status code
  ctx.body = { id: res.id };
  ctx.status = 201;
};
Copy the code

Gets HTTP request parameters

query

class PostController extends Controller {
  async listPosts() {
    const query = this.ctx.query;
    / / {
    // category: 'egg',
    // language: 'node',
    // }}}Copy the code

When a key is duplicated in a Query String, ctx. Query takes only the value of the first occurrence of the key, and subsequent occurrences are ignored.

queries

Sometimes our system is designed to have users pass the same key, like GET /posts? Category = egg&id = 1 & id & id = 3 = 2.

The racks provide the CTX. queries object, which also parses the Query String, but does not discard any duplicate data. Instead, it puts them all in an array.

// GET /posts? category=egg&id=1&id=2&id=3
class PostController extends Controller {
  async listPosts() {
    console.log(this.ctx.queries);
    / / {
    // category: [ 'egg' ],
    // id: [ '1', '2', '3' ],
    // }}}Copy the code

Router params

The Router can also declare parameters, which can be obtained through ctx.params.

// app.get('/projects/:projectId/app/:appId', 'app.listApp');
// GET /projects/1/app/2
class AppController extends Controller {
  async listApp() {
    assert.equal(this.ctx.params.projectId, '1');
    assert.equal(this.ctx.params.appId, '2'); }}Copy the code

body

BodyParser middleware is built into the framework to parse the request body of these two formats into object and mount it to CTx.request.body.

In general, the most frequently adjusted configuration item is to change the maximum length allowed for parsing, which can be overridden by the framework’s default values in config/config.default.js.

module.exports = {
  bodyParser: {
    jsonLimit: '1mb'.formLimit: '1mb',}};Copy the code

If the user’s Request body exceeds our configured maximum parsing length, an exception with status code 413 (Request Entity Too Large) will be thrown. If the user’s Request body parsing fails (wrong JSON), Raises an exception with a status code of 400 (Bad Request).

A common mistake is to confuse ctx.request.body with ctx.body, which is short for ctx.Response. body.

Get the uploaded file

Typically, browsers send files in Multipart/form-data format, and the framework supports retrieving user-uploaded files through the built-in Multipart plugin.

The File mode:

Enable file mode in config file:

// config/config.default.js
exports.multipart = {
  mode: 'file'};Copy the code
Upload/Receive files:

Upload/receive a single file:

<form method="POST" action="/upload? _csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
  title: <input name="title" />
  file: <input name="file" type="file" />
  <button type="submit">Upload</button>
</form>
Copy the code

The corresponding back-end code is as follows:

// app/controller/upload.js
const Controller = require('egg').Controller;
const fs = require('mz/fs');

module.exports = class extends Controller {
  async upload() {
    const { ctx } = this;
    const file = ctx.request.files[0];
    const name = 'egg-multipart-test/' + path.basename(file.filename);
    let result;
    try {
      // Process files, such as uploading to the cloud
      result = await ctx.oss.put(name, file.filepath);
    } finally {
      // Temporary files need to be deleted
      await fs.unlink(file.filepath);
    }

    ctx.body = {
      url: result.url,
      // Get all field valuesrequestBody: ctx.request.body, }; }};Copy the code

Upload/receive multiple files:

For multiple files, we iterate with the ctx.request.files property and then process them separately:

<form method="POST" action="/upload? _csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
  title: <input name="title" />
  file1: <input name="file1" type="file" />
  file2: <input name="file2" type="file" />
  <button type="submit">Upload</button>
</form>
Copy the code

Corresponding back-end code:

// app/controller/upload.js
const Controller = require('egg').Controller;
const fs = require('mz/fs');

module.exports = class extends Controller {
  async upload() {
    const { ctx } = this;
    console.log(ctx.request.body);
    console.log('got %d files', ctx.request.files.length);
    for (const file of ctx.request.files) {
      console.log('field: ' + file.fieldname);
      console.log('filename: ' + file.filename);
      console.log('encoding: ' + file.encoding);
      console.log('mime: ' + file.mime);
      console.log('tmp filepath: ' + file.filepath);
      let result;
      try {
        // Process files, such as uploading to the cloud
        result = await ctx.oss.put('egg-multipart-test/' + file.filename, file.filepath);
      } finally {
        // Temporary files need to be deleted
        await fs.unlink(file.filepath);
      }
      console.log(result); }}};Copy the code

To ensure file upload security, the framework limits the supported file formats. By default, the framework supports the following whitelist:

// images
'.jpg'.'.jpeg'.// image/jpeg
'.png'.// image/png, image/x-png
'.gif'.// image/gif
'.bmp'.// image/bmp
'.wbmp'.// image/vnd.wap.wbmp
'.webp'.'.tif'.'.psd'.// text
'.svg'.'.js'.'.jsx'.'.json'.'.css'.'.less'.'.html'.'.htm'.'.xml'.// tar
'.zip'.'.gz'.'.tgz'.'.gzip'.// video
'.mp3'.'.mp4'.'.avi'.Copy the code

Users can add supported file extensions by configuring them in config/config.default.js, or rewrite the entire whitelist.

  • Added supported file name extensions
module.exports = {
  multipart: {
    fileExtensions: [ '.apk' ] // Add file support for the APk extension}};Copy the code
  • Overwrite the entire whitelist
module.exports = {
  multipart: {
    whitelist: [ '.png'].// Overwrite the entire whitelist, only upload '.png' format is allowed}};Copy the code

See the Egg – Multipart

header

In addition to getting parameters from the URL and request body, many parameters are passed through the request header.

  • ctx.headers.ctx.header.ctx.request.headers.ctx.request.header: These methods are equivalent, they are all fetchThe entire header object.
  • ctx.get(name).ctx.request.get(name): Gets the value of a field in the request header, and returns an empty string if the field does not exist.
  • We suggest usingctx.get(name)Rather thanctx.headers['name']Because the former automatically handles case.

Cookie

Through CTx. cookies, we can conveniently and securely set and read cookies in Controller.

class CookieController extends Controller {
  async add() {
    const ctx = this.ctx;
    const count = ctx.cookies.get('count');
    count = count ? Number(count) : 0;
    ctx.cookies.set('count', ++count);
    ctx.body = count;
  }

  async remove() {
    const ctx = this.ctx;
    const count = ctx.cookies.set('count'.null);
    ctx.status = 204; }}Copy the code

Session

Through cookies, we can set up a Session for each user, which is used to store user identity-related information. This information will be encrypted and stored in cookies to maintain user identity across requests.

The framework has a built-in Session plugin, which provides ctx. Session to access or modify the current user Session.

class PostController extends Controller {
  async fetchPosts() {
    const ctx = this.ctx;
    // Get the contents of Session
    const userId = ctx.session.userId;
    const posts = await ctx.service.post.fetch(userId);
    // Change the Session value
    ctx.session.visited = ctx.session.visited ? ++ctx.session.visited : 1;
    ctx.body = {
      success: true, posts, }; }}Copy the code

Session is a very straightforward way to use it, just read it or modify it, if you want to delete it, just assign it null:

class SessionController extends Controller {
  async deleteSession() {
    this.ctx.session = null; }};Copy the code

configuration

module.exports = {
  key: 'EGG_SESS'.// The name of the Cookie key-value pair hosting the Session
  maxAge: 86400000.// Maximum duration of Session
};
Copy the code

Parameter calibration

Validate plug-in provides a convenient parameter verification mechanism to help us complete various complex parameter verification.

Since it is a plug-in, we need to complete the registration of the plug-in:

// config/plugin.js
exports.validate = {
  enable: true.package: 'egg-validate'};Copy the code

Ctx.validate (rule, [body])

this.ctx.validate({
  title: { type: 'string' },
  content: { type: 'string'}});Copy the code

When verifying an exception, an exception is directly thrown. The status code of the exception is 422 (Unprocessable Entity). The errors field contains detailed verification failure information. If you want to handle checked exceptions yourself, you can catch them yourself with a try catch.

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

Validation rules

Parameter verification is completed through Parameter. The supported verification rules can be found in the documentation of the module.

You can add a custom rule by running app.validator.addRule(type, check).

// app.js
app.validator.addRule('json', (rule, value) => {
  try {
    JSON.parse(value);
  } catch (err) {
    return 'must be json string'; }});Copy the code

Call the Service

The Controller can call any method on any Service, and the Service is lazily loaded and instantiated only when it is accessed.

Sending an HTTP response

Set the status

ctx.status = 201;
Copy the code

Set up the body

The vast majority of data is sent to the requester through the body in the request. Just like the body in the request, the body sent in the response needs to have a matching Content-Type that tells the client how to parse the data.

Ctx. body is short for ctx.response.body, not to be confused with ctx.request.body.

class ViewController extends Controller {
  async show() {
    this.ctx.body = {
      name: 'egg'.category: 'framework'.language: 'Node.js'}; }async page() {
    this.ctx.body = '<html><h1>Hello</h1></html>'; }}Copy the code

Due to the streaming nature of Node.js, there are many scenarios that require a Stream response, such as returning a large file, the proxy server directly returning upstream content, and the framework also supports setting the body as a Stream and handling error events on the Stream.

class ProxyController extends Controller {
  async proxy() {
    const ctx = this.ctx;
    const result = await ctx.curl(url, {
      streaming: true}); ctx.set(result.header);// result.res is a streamctx.body = result.res; }};Copy the code

Set the Header

Ctx. set(key, value) allows you to set one response Header, and ctx.set(headers) allows you to set multiple headers.

redirect

The framework overrides KOA’s native CTx. redirect implementation with a Security plug-in to provide more secure redirection.

  • ctx.redirect(url)If the domain name is not in the configured whitelist, the hop is prohibited.
  • ctx.unsafeRedirect(url)Skip to another domain name without checking the domain name. Use the domain name after knowing the risks.

To use the CTx. redirect method, you need to configure the following parameters in the application configuration file:

// config/config.default.js
exports.security = {
  domainWhiteList: ['.domain.com'].// Security whitelist, starting with
};
Copy the code

If domainWhiteList is not configured or the array is empty, all forward requests will be allowed by default. This is the same as ctx.unsaferedirect (url).