ZijianHe / koa-router

Router middleware for koa.
MIT License
4.85k stars 407 forks source link

Recommend organization for file-per-route structure #211

Closed ivan-kleshnin closed 8 years ago

ivan-kleshnin commented 8 years ago

I want to put every route in separate file. Because they are big and I want to be able to compare code between files via diff tool. Is there any way to organize this avoiding ugly boilerplate. I have the same problem with Express, just to note. See code examples to get what I mean:

Approach 1

Create new router instance in every file. Requires a lot of manual assembling in the app.

Files

pages/home.js
pages/contacts.js
app.js

pages/home.js

import KoaRouter from "koa-router";

let router = KoaRouter();
router.get("/home", ...);

export default router;

pages/contacts.js

import KoaRouter from "koa-router";

let router = KoaRouter();
router.get("/contacts", ...);

export default router;

app.js

import homeRouter from "pages/home";
import contactsRouter from "pages/contacts";

// ugly
app.use(homeRouter.routes()).use(homeRouter.allowedMethods());
app.use(contactsRouter.routes()).use(contactsRouter.allowedMethods());

Approach 2

Create shared router for every folder. Less boilerplate but requires additional imports to "trigger" route updates which I consider a light hacks.

Files

pages/router.js
pages/home.js
pages/contacts.js
app.js

pages/router.js

import KoaRouter from "koa-router";

let router = KoaRouter();

export default router;

pages/home.js

import router from "./router";

router.get("/home", ...);

pages/contacts.js

import router from "./router";

router.get("/contacts", ...);

app.js

import "pages/home";     // to trigger router update
import "pages/contacts"; // ...hacky
import router from "pages/router";

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

Approach 3

Same as Approach 2 but with circular dependencies between router object and it's routes. The worst IMO because relies on export order (module.exports = work, export default ... does not....)


Btw, this seems to be precisely that issue with Express which led to creation of Hapi framework. In the last one to create route you don't need to import anything because routes are represented as values:

{ method: 'GET', path: '/test', handler: handler }

Am I wrong and are there better approaches than those I described above? Again, this issue arises only when you want to put every route in separate file. That's why some people with different coding disciplines could probably never met it.

alexmingoia commented 8 years ago

If I understand correctly, you prefer the approach of exporting a map as the route file. Create a wrapper:

routes/test.js

module.exports = {
   methods: ['GET'],
   path: '/test',
   middleware: [handler]
};

helpers/route.js

module.exports = function (router, routes) {
  routes.forEach(function (path) {
    var def = require(path);
    router.register(def.path, def.methods, def.middleware);
  });
}

app.js

var app = require('koa')();
var router = require('koa-router')();
var route = require('./helpers/route');

route(router, [
  './routes/test'
]);

app.use(router.routes());
ivan-kleshnin commented 8 years ago

Thanks! I don't imply Hapi made this right – I don't have experience with it. I just wanted to verify my assumptions that routing API is not very friendly to my approach (single route / HTTP verb per file).

I also thought about the version you proposed and the problem with it is that you still need to access router instance inside route files because router exposes router.url and other possible helpers. So it leads to another cyclic import which is not good.

So let's close this issue for now and if someone (you, me, everyone) discovers both simple and conventinal way to overcome that difficulty – he may notify others here.