Braxo
Braxo

Reputation: 789

ExpressJS, route handling with two routers that also use each other?

I want to create route handling for these example routes:

GET /users
GET /users/:userid
GET /users/:userid/groups
GET /users/:userid/groups/:groupid

GET /groups
GET /groups/:groupid
GET /groups/:groupid/users
GET /groups/:groupid/users/:userid

Contrived code example of this setup. Imagine a directory structure as:

# index.js
# routes/users.js
# routes/groups.js
# lib/users.js
# lib/groups.js

And contents of each being:

index.js

express = require 'express'
UsersRouter = require './routes/users'
GroupsRouter = require './routes/groups'

app = express()

app.use '/users', UsersRouter
app.use '/groups', GroupsRouter

app.use (err, req, res, next) -> res.sendStatus(404)

app.listen '3000', () ->
    console.log "Listening on port #{3000}"

module.exports = app

routes/users.js

express = require 'express'
Users = require '../lib/users'
GroupsRouter = require './groups'

router = express.Router()

router.param 'userid', (req, res, next, userid) ->
    req.userid = userid
    next()

router.get '/', (req, res) ->
    Users.list req, (err, users) ->
        return next err if err
        res.status(200).send(users)

router.get '/:userid', (req, res, next) ->
    Users.find req, (err, user) ->
        return next err if err
        res.status(200).send(user)

router.use '/:userid/groups', GroupsRouter

module.exports = router

routes/groups.js

express = require 'express'
Groups = require '../lib/groups'
UsersRouter = require './users'

router = express.Router()

router.param 'groupid', (req, res, next, groupid) ->
    req.groupid = groupid
    next()

router.get '/', (req, res, next) ->
    Groups.list req, (err, groups) ->
        return next err if err
        res.status(200).send(groups)

router.get '/:groupid', (req, res, next) ->
    Groups.find req, (err, group) ->
        return next err if err
        res.status(200).send(group)

router.use '/:groupid/users', UsersRouter

module.exports = router

lib/users.js

module.exports =
    list: (req, cb) ->
        if req.groupid
            return cb null, "List of all users in group #{req.groupid}"
        else
            return cb null, "List of all users"

    find: (req, cb) ->
        if req.groupid and req.userid
            return cb null, "User #{req.userid} if in #{req.groupid}"
        else
            return cb null, "The user #{req.userid}"

lib/groups.js

module.exports =
    list: (req, cb) ->
        if req.userid
            return cb null, "List of all groups for #{req.userid}"
        else
            return cb null, "List of all groups"

    find: (req, cb) ->
        if req.userid and req.groupid
            return cb null, "Group #{req.groupid} if it has member #{req.userid}"
        else
            return cb null, "The group #{req.groupid}"

Problem is, I am getting an espress.js error for doing that cyclical require of the routers. Is it possible to do that?

If I instead just include one router in the other, and not vice versa, it get the expected response from my request.

An example of a longer route use case, say I want to see if a group has a user in it and if so return all the other groups that user belongs too:

GET /groups/:groupid/users/:userid/groups

Error I've received:

TypeError: Router.use() requires middleware function but got a Object

Upvotes: 1

Views: 1947

Answers (3)

Braxo
Braxo

Reputation: 789

I was able to achieve the routing I wanted by exporting a middleware route from one router that I could assign to the route I want to match in another router.

routes/users.js

express = require 'express'
Users = require '../lib/users'

router = express.Router()

router.param 'userid', (req, res, next, userid) ->
    req.userid = userid
    next()

router.pseudo = router.use (req, res, next) -> next()

router.get '/', (req, res) ->
    Users.list req, (err, users) ->
        return next err if err
        res.status(200).send(users)

router.get '/:userid', (req, res, next) ->
    Users.find req, (err, user) ->
        return next err if err
        res.status(200).send(user)

module.exports = router

router.use '/:userid/groups', (require '../groups').pseudo

routes/groups.js

express = require 'express'
Groups = require '../lib/groups'

router = express.Router()

router.param 'groupid', (req, res, next, groupid) ->
    req.groupid = groupid
    next()

router.pseudo = router.use (req, res, next) -> next()

router.get '/', (req, res, next) ->
    Groups.list req, (err, groups) ->
        return next err if err
        res.status(200).send(groups)

router.get '/:groupid', (req, res, next) ->
    Groups.find req, (err, group) ->
        return next err if err
        res.status(200).send(group)

module.exports = router

router.use '/:groupid/users', (require '../users).pseudo

This routing setup allows for requests like:

GET /groups/:groupid/users

The route will set the req.groupid value from the group route middleware and then be routed to the users / route to list the users.

Same if you keep on daisy chaining them like:

GET /groups/:groupid/users/:userid/groups

The route will first hit groups, then users, then back to groups and giving me the set values for req.groupid and req.userid.

Upvotes: 0

code-jaff
code-jaff

Reputation: 9330

This is the possible way to tackle this cyclic situation

keep the handler code in a separate module, and re-use in the route modules. (This is the point what jfriend00 was also trying to make in comments)

for eg.

// userHandler.js

module.exports = {
  browse: (req, res) => {
    // return user list
  },
  find: (req, res) => {
    // return user
  }
}


// userRouter
// ... 
const handler = require('userHandler');
router.get('/', handler.browse);
// ...

// groupsRouter
// ... 
const uHandler = require('userHandler');
router.get('/:groupid/users', uHandler.browse);
router.get('/:groupid/users/:userid', uHandler.find);
// ...

Upvotes: 0

Ethan
Ethan

Reputation: 3798

users.js relies on groups.js, but then groups.js requres users.js while it is being executed. This means that it will go in a loop:

users.js calls groups.js

groups.js calls users.js which calls groups.js etc.

Upvotes: 1

Related Questions