This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
1. The background
Network IO verification layer is necessary
Suppose we want to develop a service that does the following.
- Request and participate:
{list: [' xiao Ming ', 'Zhang SAN ']}
- Store the incoming list into the database
- The response:
{MSG: 'success '}
For service stability, we need pre-validation when the user passes a list that is not an array or the elements of a list that are not strings, for example:
if (Array.isArray(req.list)
&& req.list.every(item= >
typeof item === 'string')) {
// The input is valid
}
else {
// Input is abnormal
}
Copy the code
However, the interface input that is actually developed is usually more complex than the example above. Layering prevents normal processing logic from being mixed up with type validation and exception handling.
The NETWORK I/O verification layer verifies input and output by protocol
agreement
The validation layer requires us to provide expectations for IO, i.e., protocols. For the previous service, our expectations are as follows:
// Text description- request: list mandatory, which is a string. - Response: MSG Mandatory, which is a string// typescript type declarations
interface Request {
list: string[];
}
interface Response {
msg: string;
}
// koa-joi-router
{
validate: {
type: 'json'.body: {
list: Joi.array().items(Joi.string().required()).required(),
},
output: {
200: {
body: {
message: Joi.string().required()
}
}
}
}
}
Copy the code
For ts implemented services, the network IO verification layer + protocol design has additional benefits.
IO validation in front compensates for ts’s inability to run type checking.
2. Koa – joi – the introduction of the router
As the name indicates, koA-Joi-router is a combination of JOI and KOA-Router. It is a route based on THE I/O verification capability of KOA. Among them, JOI focuses on data structure validation of JS.
Example 3.
npm i koa-joi-router -D
Copy the code
const koa = require('koa');
const router = require('koa-joi-router');
const Joi = router.Joi;
const public = router();
public.route({
method: 'post'.path: '/signup'.validate: {
body: {
name: Joi.string().max(100),
email: Joi.string().lowercase().email(),
password: Joi.string().max(100),
_csrf: Joi.string().token()
},
type: 'form'.output: {
200: {
body: {
userId: Joi.string(),
name: Joi.string()
}
}
}
},
handler: async (ctx) => {
const user = await createUser(ctx.request.body);
ctx.status = 201; ctx.body = user; }});const app = new koa();
app.use(public.middleware());
app.listen(3000);
Copy the code
There are several ways to write new routes
// case 1
public.route({
method: 'post'.path: '/signup'.validate: {
body: {
name: Joi.string().max(100),
email: Joi.string().lowercase().email(),
password: Joi.string().max(100),
_csrf: Joi.string().token()
},
type: 'form'.output: {
200: {
body: {
userId: Joi.string(),
name: Joi.string()
}
}
}
},
handler: async (ctx) => {
const user = await createUser(ctx.request.body);
ctx.status = 201; ctx.body = user; }});// case 2
public.post('/signup', {
validate: {
body: {
name: Joi.string().max(100),
email: Joi.string().lowercase().email(),
password: Joi.string().max(100),
_csrf: Joi.string().token()
},
type: 'form'.output: {
200: {
body: {
userId: Joi.string(),
name: Joi.string()
}
}
}
},
handler: async (ctx) => {
const user = await createUser(ctx.request.body);
ctx.status = 201; ctx.body = user; }});// case 3
public.post('/signup', {
validate: {
body: {
name: Joi.string().max(100),
email: Joi.string().lowercase().email(),
password: Joi.string().max(100),
_csrf: Joi.string().token()
},
type: 'form'.output: {
200: {
body: {
userId: Joi.string(),
name: Joi.string()
}
}
}
}
}, async (ctx) => {
const user = await createUser(ctx.request.body);
ctx.status = 201;
ctx.body = user;
});
Copy the code
Typescript support
Just introduce the corresponding type dependency.
npm i @types/koa-joi-router -D
Copy the code
// ts.config.json
{
// ...
"compilerOptions": {
// ...
"types": [
// ...
"koa-joi-router",].}}Copy the code
Directory structure division
In practice, we divide the directory structure based on our understanding of koa-Joi-Router.
API | _ the run / / actual business module, named after the corresponding path | processing | _ _ handler / / normal business index / / protocol description, exception handling | _ schema / / ts attention, The type declaration of Req and Res is main // service start, each middleware mount router // route write, and call each service module in APICopy the code
3. Exception handling
public.post('/signup', {
validate: {
type: 'json'.body: {
list: Joi.array().items(Joi.string().required()).required(),
},
output: {
200: {
body: {
message: Joi.string().required()
}
}
}
},
async handler(ctx) {
// Normal business processing}});Copy the code
For example, if the request body is {} and the verification fails, the response “list” is required. {“message”: “\”list\” is required”}
public.post('/signup', {
validate: {
type: 'json'.body: {
list: Joi.array().items(Joi.string().required()).required(),
},
output: {
200: {
body: {
message: Joi.string().required()
}
}
},
continueOnError: true // The handler is still executed
},
async handler(ctx) {
if (ctx.invalid) {
ctx.body = {message: ctx.invalid? .body? .msg};// Wrap the error as a valid output structure
} else {
// Normal business processing}}});Copy the code
ContinueOnError enables handler handler functions to be executed even when exceptions occur. Wrap CTx. invalid as a valid output structure in handler.
4. Protocol description
The protocol description uses chained calls. See the JOI documentation for details, listing some common apis.
The name of the | describe |
---|---|
any.failover | Failed to validate alternative values |
any.default | Check withundefined Is the default value of |
any.required | The value is not acceptedundefined .All constructs are accepted by defaultundefined |
any.required | The value is not acceptedundefined .All constructs are accepted by defaultundefined |
any.allow | Some values that need to be exempted,Like the empty string of string |
any.valid | Restrict types to specified literals |
object.unknown | Accept other keys that are not verified,Extra keys are not accepted by default |
string.regex | Verifies whether a regular is matched |
Ps: Each type corresponds to the joi factory function. For example, string corresponds to joi.string (), and any refers to all factory functions.
5. Verify the relationship with TS assertions
The TS and validation layers come into play at compile and run time, respectively, and therefore cannot be correlated automatically. We can assume that what is going into the business logic through validation is what we expect, giving assertions.
interface Req {
}
if (ctx.invalid) {
// Check exception
}
else {
try {
ctx.body = await main(ctx.request.body as Req); // Type assertion
} catch (err) {
// The internal operation is abnormal}}Copy the code
You need to manually ensure that the verification protocol can meet the TS assertion, so as to avoid the “fish” that passes the verification and is not processed. For example, the following two are equivalent:
// joi
{
message: Joi.string().required().allow(' ')}// ts
interface Req {
message: string
}
Copy the code
Joi’s API is far richer than TS’s type system. It is also possible for the protocol to be stricter than the TS assertion, for example:
// joi
{
email: Joi.string().required().email()
}
// ts
interface Req {
email: string
}
Copy the code
The above situation requires the premise that the same standard is used for the front and back end verification.
Imagine a form that passes front-end validation, but is submitted with an interface error “input exception”. There are two general ideas:
- The front and back ends are implemented by JS and maintain a one-to-one relationship, which can rely on JOI verification.
- The front and back ends are decoupled, and the key data structures are verified by the back end.
Koa-joi-router is just one option to remedy the TS runtime problem. From the integration perspective of the framework, koA-Joi-Router is the right choice for KOA.