preface

This state is a bit like writing a composition when going to school, the beginning is always “pull” not to come out, suffocating uncomfortable.

The original address

The source address

From the background

After the separation of the front and back ends, the front end will need to deal with some node layer work, such as template rendering, interface forwarding, part of the business logic, etc. The commonly used frameworks are KOA and KOA-Router, etc.

Now we need to implement a requirement:

  1. The user to access/feWhen pages are displayedhello fe
  2. The user to access/backendWhen pages are displayedhello backend

Are you thinking, this requires me to use koA, KOA-Router, native Node module can do it.

const http = require('http')
const url = require('url')
const PORT = 3000

http.createServer((req, res) = > {
  let { pathname } = url.parse(req.url)
  let str = 'hello'

  if (pathname === '/fe') {
    str += ' fe'
  } else if (pathname === '/backend') {
    str += ' backend'
  }

  res.end(str)
}).listen(PORT, () => {
  console.log(`app start at: ${PORT}`)})Copy the code

It is true that using a framework may seem wasteful for very simple requirements, but there are drawbacks to the above implementation, such as

  1. We need to resolve the path ourselves.
  2. Path parsing and logic writing are coupled together. If more and more complex requirements need to be implemented in the future, that’s fine.

So let’s try using KOA and KoA-Router

app.js

const Koa = require('koa')
const KoaRouter = require('koa-router')

const app = new Koa()
const router = new KoaRouter()
const PORT = 3000

router.get('/fe', (ctx) => {
  ctx.body = 'hello fe'
})

router.get('/backend', (ctx) => {
  ctx.body = 'hello backend'
})

app.use(router.routes())
app.use(router.allowedMethods())

app.listen(PORT, () => {
  console.log(`app start at: ${PORT}`)})Copy the code

The path resolution is handled by the KOA-router, but the overall writing is still a bit problematic.

  1. There is no way to reuse anonymous functions
  2. Routing configuration and logical processing in one file, no separation, big project, is also a hassle.

Let’s optimize it a little bit by looking at the overall directory structure

├─ App.js // App.js // App.js // App.js // App.js // App.js // App.js // App.js // App.js // App.js // App.js // App.js // App.js // App.js // App.js // App.js // App.js // App.js ├── Routes // │ ├─ index. Js ├─ views // Template ├─ indexCopy the code

Preview the logic of each file

The intersection of app.js apps

const Koa = require('koa')
const middleware = require('./middleware')
const app = new Koa()
const PORT = 3000

middleware(app)

app.listen(PORT, () => {
  console.log(`app start at: ${PORT}`)})Copy the code

Js Routes /index.js Route configuration center

const KoaRouter = require('koa-router')
const router = new KoaRouter()
const koaCompose = require('koa-compose')
const hello = require('.. /controller/hello')

module.exports = (a)= > {
  router.get('/fe', hello.fe)
  router.get('/backend', hello.backend)

  return koaCompose([ router.routes(), router.allowedMethods() ])
}

Copy the code

Controller /hello.js Logic of the Hello module

module.exports = {
  fe (ctx) {
    ctx.body = 'hello fe'
  },
  backend (ctx) {
    ctx.body = 'hello backend'}}Copy the code

Middleware /index.js Middleware unified registry

const routes = require('.. /routes')

module.exports = (app) = > {
  app.use(routes())
}
Copy the code

You may be wondering at this point, right?

A simple requirement looks too complicated by this. Is it necessary?

The answer is: yes, this directory structure may not make the most sense, but the routing, controller, view layer and so on all have their place. Great help for future extensions.

I don’t know if you noticed the routing configuration

Js Routes /index.js Route configuration center

const KoaRouter = require('koa-router')
const router = new KoaRouter()
const koaCompose = require('koa-compose')
const hello = require('.. /controller/hello')

module.exports = (a)= > {
  router.get('/fe', hello.fe)
  router.get('/backend', hello.backend)

  return koaCompose([ router.routes(), router.allowedMethods() ])
}

Copy the code

Each route corresponds to a controller to process, very separate, very common ah!! This seems to be a common configuration pattern for vue-router or react-router on the front end.

But when there are more modules, this folder will become

const KoaRouter = require('koa-router')
const router = new KoaRouter()
const koaCompose = require('koa-compose')
// Now you need to require the files of each module
const hello = require('.. /controller/hello')
const a = require('.. /controller/a')
const c = require('.. /controller/c')

module.exports = (a)= > {
  router.get('/fe', hello.fe)
  router.get('/backend', hello.backend)
  // Configure the routing and controller for each module
  router.get('/a/a', a.a)
  router.post('/a/b', a.b)
  router.get('/a/c', a.c)
  router.get('/a/d', a.d)

  router.get('/c/a', c.c)
  router.post('/c/b', c.b)
  router.get('/c/c', c.c)
  router.get('/c/d', c.d)

  / /... , etc.
  return koaCompose([ router.routes(), router.allowedMethods() ])
}

Copy the code

Is there any way to register a koA-router without manually introducing one controller after another and then manually calling the get/POST method of the KOA-Router?

For example, we only need to do the following configuration to complete the above manual configuration function.

routes/a.js

module.exports = [
  {
    path: '/a/a'.controller: 'a.a'
  },
  {
    path: '/a/b'.methods: 'post'.controller: 'a.b'
  },
  {
    path: '/a/c'.controller: 'a.c'
  },
  {
    path: '/a/d'.controller: 'a.d'}]Copy the code

routes/c.js

module.exports = [
  {
    path: '/c/a'.controller: 'c.a'
  },
  {
    path: '/c/b'.methods: 'post'.controller: 'c.b'
  },
  {
    path: '/c/c'.controller: 'c.c'
  },
  {
    path: '/c/d'.controller: 'c.d'}]Copy the code

Then use the pure-koa-Router module for a simple configuration

const pureKoaRouter = require('pure-koa-router')
const routes = path.join(__dirname, '.. /routes') // Specify a route
const controllerDir = path.join(__dirname, '.. /controller') // Specify the root directory of the controller

app.use(pureKoaRouter({
  routes,
  controllerDir
}))

Copy the code

This way we can focus on routing configuration instead of manually requiring a bunch of files.

Briefly introduce the above configuration

{
  path: '/c/b'.methods: 'post'.controller: 'c.b'
}

Copy the code

Path: Path configuration, can be string /c/b, array [‘/c/b’], of course, can be regular expression /\c\b/

Methods (string get, array [‘get’, ‘post’], default get,

Controller: logical processing method for matching routes. C.b indicates method b for exporting c files in controllerDir. A.B.C indicates method c for exporting b files in /a/b of controllerDir

The source code to achieve

Let’s step through the implementation logic

You can click on the source code

The overall structure

module.exports = ({ routes = [], controllerDir = ' ', routerOptions = {} }) = > {
  // xxx

  return koaCompose([ router.routes(), router.allowedMethods() ])
})

Copy the code

Pure – koa – the router receives

  1. routes
    1. Const routes = path.join(__dirname, ‘.. /routes’))
    2. Const routes = path.join(__dirname, ‘..) const routes = path.join(__dirname, ‘.. /routes/tasks.js’)
    3. Const routes = require(‘.. /routes/index’))
  2. controllerDir, root directory of the controller
  3. routerOptionsNew KoaRouter, you can see thatkoa-router

This package is then executed to return the koaCompose wrapped middleware for the KOA instance to add.

Parameter adaptation

assert(Array.isArray(routes) || typeof routes === 'string'.'routes must be an Array or a String')
assert(fs.existsSync(controllerDir), 'controllerDir must be a file directory')

if (typeof routes === 'string') {
  routes = routes.replace('.js'.' ')

  if (fs.existsSync(`${routes}.js`) || fs.existsSync(routes)) {
    // Handle the file passed in
    if (fs.existsSync(`${routes}.js`)) {
      routes = require(routes)
    // Process the incoming directory
    } else if (fs.existsSync(routes)) {
      // Read the files in the directory and merge them
      routes = fs.readdirSync(routes).reduce((result, fileName) = > {
        return result.concat(require(nodePath.join(routes, fileName)))
      }, [])
    }
  } else {
    // routes If it is a string it must be the path of a file or directory
    throw new Error('routes is not a file or a directory')}}Copy the code

Routing registered

Whether routes are passed in a file or a directory, or exported directly, the configuration is structured like this

Routes Content Preview

[
  // Basic configuration
  {
    path: '/test/a'.methods: 'post'.controller: 'test.index.a'
  },
  // Multiple routes to one controller
  {
    path: [ '/test/b'.'/test/c'].controller: 'test.index.a'
  },
  // Multiple routes to multiple controllers
  {
    path: [ '/test/d'.'/test/e'].controller: [ 'test.index.a'.'test.index.b']},// Single route pair controller
  {
    path: '/test/f'.controller: [ 'test.index.a'.'test.index.b']},/ / regular
  {
    path: /\/test\/\d/.controller: 'test.index.c'}]Copy the code

Take the initiative to register

let router = new KoaRouter(routerOptions)
let middleware

routes.forEach((routeConfig = {}) = > {
  let { path, methods = [ 'get' ], controller } = routeConfig
  // Routing method type parameter adaptation
  methods = (Array.isArray(methods) && methods) || [ methods ]
  // Controller parameter adaptation
  controller = (Array.isArray(controller) && controller) || [ controller ]

  middleware = controller.map((controller) = > {
    // 'test.index.c' => [ 'test', 'index', 'c' ]
    let controllerPath = controller.split('. ')
    // Method name c
    let controllerMethod = controllerPath.pop()

    try {
      // Read /test/index file c method
      controllerMethod = require(nodePath.join(controllerDir, controllerPath.join('/')))[ controllerMethod ]
    } catch (error) {
      throw error
    }
    // controllerMethod must be a method
    assert(typeof controllerMethod === 'function'.'koa middleware must be a function')

    return controllerMethod
  })
  // Finally use router.register to register
  router.register(path, methods, middleware)


Copy the code

The implementation process of the source code basically ends here.

At the end

Pure-koa-router separates route configuration from controller, allowing us to focus on route configuration and controller implementation. I hope I can help you a little.

The original address

The source address