Aller au contenu principal
Version: 3.0.0

Express

This chapter mainly introduces how to use Express as the upper-level framework in Midway and use its own capabilities.

Description
Contains independent main framework
Contains independent logs

Installation dependency

$ npm i @midwayjs/express@3 --save
$ npm i @types/body-parser @types/express @types/express-session --save-dev

Or reinstall the following dependencies in package.json.

{
"dependencies": {
"@midwayjs/express": "^3.0.0",
// ...
},
"devDependencies": {
"@types/body-parser": "^1.19.2",
"@types/express": "^4.17.13",
"@types/express-session": "^1.17.4",
// ...
}
}

Examples can also be created directly using scaffolding.

# npm v6
$ npm init midway --type=express-v3 my_project

# npm v7
$ npm init midway -- --type=express-v3 my_project

For Express,Midway provides @midwayjs/express package for adaptation, in which Midway provides unique dependency injection, section and other capabilities.

info

The Express version we are using is v4.

Directory structure

.
├── src
│ ├── controller # controller cdoe
│ ├── service # service code
| └── configuration.ts # Entry, Lifecycle Configuration and Component Management
├── test
├── package.json
└── tsconfig.json

Open the component

import { Configuration, App } from '@midwayjs/decorator';
import * as express from '@midwayjs/express';
import { join } from 'path';

@Configuration({
imports: [express],
importConfigs: [join(__dirname, './config')],
})
export class MainConfiguration {
@App()
app: express.Application;

async onReady() {}
}

Controller

The writing of the entire request controller is similar to that of Midway adapts to other frameworks. In order to be consistent with the frame writing of other scenes, Midway maps the req of the Express to a ctx object when requesting.

import { Inject, Controller, Get, Provide, Query } from '@midwayjs/decorator';
import { Context, NextFunction } from '@midwayjs/express';

@Controller('/')
export class HomeController {

@Inject()
ctx: Context;

@Get('/')
async home(@Query() id) {
console.log(id); // req.query.id === id
return 'hello world'; // Simple return, equivalent to res.send('hello world');
}
}

You can also add req and res.

import { Inject, Controller, Get, Provide, Query } from '@midwayjs/decorator';
import { Context, Response, NextFunction } from '@midwayjs/express';

@Controller('/')
export class HomeController {

@Inject()
ctx: Context; // is req

@Inject()
req: Context;

@Inject()
res: Response;

@Get('/')
async home(@Query() id) {
// this.req.query.id === id
}
}

Web middleware

Express middleware is written in a special way, and its parameters are different.

import { Middleware } from '@midwayjs/decorator';
import { Context, Response, NextFunction } from '@midwayjs/express';

@Middleware()
export class ReportMiddleware implements IMiddleware<Context, Response, NextFunction> {

resolve() {
return async (
req: Context,
res: Response,
next: NextFunction
) => {
console.log('Request...');
next();
};
}

}

Note that we have exported a ReportMiddleware class here. In order to facilitate the docking of asynchronous processes, the resolve return can be an async function.

The next method in the Express is used to call the next middleware, which means that the Express middleware is not an onion model, but a one-way call.

Routing middleware

We can apply the middleware written above to a single Controller or to a single route.

import { Controller, Get, Provide } from '@midwayjs/decorator';

@Controller('/', { middleware: [ ReportMiddleware ]}) // controller-level middleware
export class HomeController {

@Get('/', { middleware: [ ReportMiddleware ]}) // routing-level middleware
async home() {
return 'hello world'
}
}

Global middleware

Directly use the app.generateMiddleware method provided by Midway to load global middleware at the entrance.

// src/configuration.ts
import { Configuration } from '@midwayjs/decorator';
import { ILifeCycle } from '@midwayjs/core';
import * as express from '@midwayjs/express';
import { ReportMiddleware } from './middleware/report.middleware.ts'

@Configuration({
imports: [express],
})
export class ContainerLifeCycle implements ILifeCycle {

@App()
app: express.Application;

async onReady() {
this.app.useMiddleware(ReportMiddleware);
}
}

In addition to loading middleware in the form of Class, it also supports loading traditional Express middleware.

// src/configuration.ts
import { Configuration, App } from '@midwayjs/decorator';
import { ILifeCycle } from '@midwayjs/core';
import * as express from '@midwayjs/express';
import { join } from 'path';

@Configuration({
imports: [express]
})
export class ContainerLifeCycle implements ILifeCycle {

@App()
app: express.Application;

async onReady() {
this.app.useMiddleware((req, res, next) => {
// xxx
});
}
}

You can call methods on all Express by injecting app objects.

Return to unified processing

Since the Express middleware is a one-way call and cannot be executed on return, we have designed an additional filter decorated by @Match to handle the behavior of the return value.

For example, we can define filters returned globally.

// src/filter/globalMatch.filter.ts
import { Match } from '@midwayjs/decorator';
import { Context, Response } from '@midwayjs/express';

@Match()
export class GlobalMatchFilter {
match(value, req, res) {
// ...
return {
status: 200
data: {
value
},
};
}
}

You can also match a specific route for return.

// src/filter/api.filter.ts
import { Match } from '@midwayjs/decorator';
import { Context, Response } from '@midwayjs/express';

@Match((ctx: Context, res: Response) => {
return ctx.path === '/api';
})
export class APIMatchFilter {
match(value, req: Context, res: Response) {
// ...
return {
data: {
message:
data: value
},
};
}
}

It needs to be applied to app.

import { Configuration, App } from '@midwayjs/decorator';
import * as express from '@midwayjs/express';
import { join } from 'path';
import { APIMatchFilter } from './filter/api.filter';
import { GlobalMatchFilter } from 'filter/globalMatch.filter';

@Configuration({
imports: [express]
importConfigs: [join(__dirname, './config')]
})
export class ContainerLifeCycle {
@App()
app: express.Application;

async onReady() {
// ...
this.app.useFilter([APIMatchFilter, GlobalMatchFilter]);
}
}

Note that such filters are matched and executed in the order in which they are added.

Error handling

Same as ordinary items, using error filters, but the parameters are slightly different.

import { Catch } from '@midwayjs/decorator';
import { Context, Response } from '@midwayjs/express';

@Catch()
export class GlobalError {
catch(err: Error, req: Context, res: Response) {
if (err) {
return {
status: err.status ?? 500,
message: err.message
}
}
}
}

It needs to be applied to app.

import { Configuration, App } from '@midwayjs/decorator';
import * as express from '@midwayjs/express';
import { join } from 'path';
import { GlobalError } from './filter/global.filter';

@Configuration({
imports: [express]
importConfigs: [join(__dirname, './config')]
})
export class ContainerLifeCycle {
@App()
app: express.Application;

async onReady() {
this.app.useMiddleware((req, res, next) => {
next();
});

this.app.useFilter([GlobalError]);
}
}

Note that both @Match and @Catch are filters that are automatically executed internally. .

@midwayjs/express comes with the cookie parser function and uses the cookie-parser module.

Use keys as the key for cookies.

// src/config/config.default
export default {
keys: ['key1', 'key2']
}

Get Cookie.

const cookieValue = req.cookies['cookie-key'];

Set Cookie.

res.cookie (
'cookie-key',
'cookie-value',
cookieOptions
);

Session

@midwayjs/express has built-in Session components, providing us with ctx.session to access or modify the current user Session.

By default, cookie-session is used. The default configuration is as follows.

// src/config/config.default
export default {
session: {
name: 'MW_SESS',
resave: true
saveUninitialized: true
cookie: {
maxAge: 24*3600 * 1000, // ms
httpOnly: true
// sameSite: null
},
}
}

We can set the session through a simple API.

@Controller('/')
export class HomeController {

@Inject()
req;

@Get('/')
async get() {
// set all
this.req.session = req.query;

// set value
this.req.session.key = 'abc';

// get
const key = this.req.session.key;

// remove
this.req.session = null;

// set max age
this.req.session.maxAge = Number(req.query.maxAge);

// ...
}
}

BodyParser

@midwayjs/express has its own bodyParser function. By default, it parses Post requests and automatically identifies json, text, and urlencoded types.

The default size is limited to 1mb. You can set the size of each item separately.

// src/config/config.default
export default {
// ...
bodyParser: {
json: {
enable: true
limit: '1mb',
strict: true
},
raw: {
enable: false
limit: '1mb',
},
text: {
enable: true
limit: '1mb',
},
urlencoded: {
enable: true
extended: false
limit: '1mb',
parameterLimit: 1000
},
},
}

Configuration

Default configuration

The configuration sample of @midwayjs/express is as follows:

// src/config/config.default
export default {
// ...
express: {
port: 7001
},
}

All attributes are described as follows:

PropertyTypeDescription
portnumberOptional, port to start
globalPrefixstringoptional. the global http prefix
keysstring []Optional, Cookies signature, if the upper layer does not write keys, you can also set it here
hostnamestringOptional. The hostname to listen to. Default 127.1
keystring | Buffer | Array<Buffer|Object>Optional, Https key, server private key
certstring | Buffer | Array<Buffer|Object>Optional, Https cert, server certificate
castring | Buffer | Array<Buffer|Object>Optional, Https ca
http2booleanOptional, supported by http2, default false

Modify port

By default, we provide the 7001 default port parameter in config.default. by modifying it, we can modify the default port of Express http service.

For example, we changed it to 6001:

// src/config/config.default
export default {
// ...
express: {
port: 6001
},
}

By default, our port configuration is null because the single-test environment requires supertest to start the port.

// src/config/config.unittest
export default {
// ...
express: {
port: null
},
}

In addition, you can also temporarily modify the port by midway-bin dev-ts-port = 6001, which overwrites the configured port.

Global prefix

For more information about this feature, see [Global Prefixes](../controller# Global Routing Prefix).

Https configuration

In most cases, please use external agents as much as possible to complete the implementation of Https, such as Nginx.

In some special scenarios, you can directly turn on Https by configuring SSL certificates (TLS certificates).

First, you must prepare certificate files in advance, such as ssl.key and ssl.pem. The key is the private key of the server and the pem is the corresponding certificate.

Then configure it.

// src/config/config.default
import { readFileSync } from 'fs';
import { join } from 'path';

export default {
// ...
express: {
key: join(__dirname, '../ssl/ssl.key')
cert: join(__dirname, '../ssl/ssl.pem')
},
}

Modify context log

The context log of the express framework can be modified separately.

export default {
express: {
contextLoggerFormat: info => {
// equivalent req
const req = info.ctx;
const userId = req?.['session']?.['userId'] || '-';
return '${info.timestamp} ${info.LEVEL} ${info.pid} [${userId} - ${Date.now() - req.startTime}ms ${req.method}] ${info.message}';
}
// ...
},
};