Update the KOA development framework with typescript

The strongly typed TypeScript development experience has obvious advantages over JavaScript in terms of maintenance projects, so it is imperative to remodel the scaffolding that is commonly used.

Let’s start with the koA-based node backend scaffolding:

  1. The construction of project development environment and typescript compilation environment;
  2. Add typing support for Node, KOA, KOA middleware, and libraries used;
  3. A feature transformation project for typesript.

Set up project development environment

The development and compilation environment is built based on gulp. The gulp-typescript plug-in is used to compile typescript files. The gulp-Nodemon can monitor changes in file content and automatically compile and restart Node services to improve development efficiency.

npm install -D gulp gulp-nodemon gulp-typescript ts-node typescript
Copy the code

Gulp configuration

Gulpfile. The setting of js

const { src, dest, watch, series, task } = require('gulp');
const del = require('del');
const ts = require('gulp-typescript');
const nodemon = require('gulp-nodemon');
const tsProject = ts.createProject('tsconfig.json');

function clean(cb) {
  return del(['dist'], cb);
}

// Output js to dist directory
function toJs() {
  return src('src/**/*.ts')
    .pipe(tsProject())
    .pipe(dest('dist'));
}

// Nodemon monitors ts files
function runNodemon() {
  nodemon({
    inspect: true.script: 'src/app.ts'.watch: ['src'].ext: 'ts'.env: { NODE_ENV: 'development' },
    // tasks: ['build'],
  }).on('crash', () = > {console.error('Application has crashed! \n');
  });
}

const build = series(clean, toJs);
task('build', build);
exports.build = build;
exports.default = runNodemon;
Copy the code

The typescript configuration

Tsconfig. The setting of json

{"compilerOptions": {"baseUrl": ".", // import relative start path "outDir": "./dist", // build output directory "module": "commonjs", "target": "Esnext", / / the node environment support esnext "allowSyntheticDefaultImports" : true, "importHelpers" : true, "strict" : false, "moduleResolution": "node", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "noImplicitAny": true, "suppressImplicitAnyIndexErrors": true, "noUnusedParameters": true, "noUnusedLocals": true, "noImplicitReturns": True, "experimentalDecorators": true, // Open the decorator using "emitDecoratorMetadata": true, "allowJs": true, "sourceMap": true, "paths": { "@/*": [ "src/*" ] } }, "include": [ "src/**/*" ], "exclude": [ "node_modules", "dist" ] }Copy the code

Eslint configuration

Of course, ESLint also adds support for typescript

npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
Copy the code

. Eslintrc. Json Settings

{
  "env": {
    "es6": true."node": true
  },
  "extends": [
    "eslint:recommended"."plugin:@typescript-eslint/eslint-recommended"]."globals": {
    "Atomics": "readonly"."SharedArrayBuffer": "readonly"
  },
  "parser": "@typescript-eslint/parser"."parserOptions": {
    "ecmaVersion": 2018."sourceType": "module"
  },
  "plugins": [
    "@typescript-eslint"]."rules": {
    "indent": [ "warn".2]."no-unused-vars": 0}}Copy the code

Package. json runs the configuration

Finally, set the scripts of package.json

  "scripts": {
    "start": "gulp",// dev
    "build": "gulp build", // output
    "eslint": "eslint --fix --ext .js,.ts src/",
    "server": "export NODE_ENV=production && node dist/app" // production server
  },
Copy the code

Add typing support

The project uses the following components

  • jsonwebtoken
  • koa
  • koa-body
  • koa-compress
  • koa-favicon
  • koa-logger
  • koa-router
  • koa-static
  • koa2-cors
  • log4js

Install the corresponding type file, and don’t forget @types/node

npm install -D @types/jsonwebtoken @types/koa @types/koa-compress @types/koa-favicon @types/koa-logger @types/koa-router  @types/koa-static @types/koa2-cors @types/log4js @types/node
Copy the code

Transform projects using typescript decorators

The.NET MVC framework is handy for using decorators to configure controllers, and typescript decorators can now do the same. The reflect-metadata library is used here. If you’ve used Java or C#, you’ll be familiar with the principle of reflection.

Defines the decorator for the HTTP request

We no longer have to go back and forth between route configuration and controller methods to find and match

import 'reflect-metadata'
import { ROUTER_MAP } from '.. /constant'

/** * @desc generate HTTP method Decorator * @param {string} method - HTTP method, such as get, post, head * @return Decorator - Decorator */
function createMethodDecorator(method: string) {
  // The decorator accepts the route path as an argument
  return function httpMethodDecorator(path: string) {
    return (proto: any, name: string) = > {
      const target = proto.constructor;
      const routeMap = Reflect.getMetadata(ROUTER_MAP, target, 'method') | | []; routeMap.push({ name, method, path }); Reflect.defineMetadata(ROUTER_MAP, routeMap, target,'method');
    };
  };
}

Export the HTTP method decorator
export const post = createMethodDecorator('post');

export const get = createMethodDecorator('get');

export const del = createMethodDecorator('del');

export const put = createMethodDecorator('put');

export const patch = createMethodDecorator('patch');

export const options = createMethodDecorator('options');

export const head = createMethodDecorator('head');

export const all = createMethodDecorator('all');
Copy the code

Method of decorating the controller

export default class Sign {
    
  @post('/login')
  async login (ctx: Context) {
    const { email, password } = ctx.request.body;
    const users = await userDao.getUser({ email });
    // ...
    return ctx.body = {
      code: 0,
      message: 'Login successful',
      data
    };
  }

  @post('/register')
  async register (ctx: Context) {
    const { email, password } = ctx.request.body;
    const salt = makeSalt();
    // ...
    return ctx.body = {
      code: 0,
      message: 'Registration successful! ',
      data
    }
  }
  
}
Copy the code

Collect metadata and add routes

Now that we’ve added decorators to the methods of the corresponding controllers, how do we collect the metadata? This requires the fs file module provided by Node. When the Node service is started for the first time, scan the Controller folder, collect all controller modules, combine the metadata collected by the decorator, and add the corresponding method to the KOA-Router.

import 'reflect-metadata'
import fs from 'fs'
import path from 'path'
import { ROUTER_MAP } from './constant'
import { RouteMeta } from './type'
import Router from 'koa-router'

const addRouter = (router: Router) = > {
  const ctrPath = path.join(__dirname, 'controller');
  const modules: ObjectConstructor [] = [];
  // Scan the Controller folder to collect all controllers
  fs.readdirSync(ctrPath).forEach(name= > {
    if (/ ^ ^.] +? \.(t|j)s$/.test(name)) {
      modules.push(require(path.join(ctrPath, name)).default)
    }
  });
  // Add a route with meta data
  modules.forEach(m= > {
    const routerMap: RouteMeta[] = Reflect.getMetadata(ROUTER_MAP, m, 'method') | | [];if (routerMap.length) {
      const ctr = new m();
      routerMap.forEach(route= > {
        const{ name, method, path } = route; router[method](path, ctr[name]); })}})}export default addRouter
Copy the code

The last

So the koA project scaffolding transformation is basically completed, see the source code koA-Server