DEV Community

Cover image for Creating your own ExpressJS from scratch (Part 4) - Modular router and global middlewares
Wesley Miranda
Wesley Miranda

Posted on

Creating your own ExpressJS from scratch (Part 4) - Modular router and global middlewares

It's time to deal deeper with middleware and create a way to allow the users from our framework to create their routes separately from the main app object.

Let's get started!


Global Middlewares

src/app.js

// (1)
const middlewaresForAll = []

// (2)
const use = (path, ...middlewares) => {
    const possiblePaths = [path + '/GET', path + '/POST', path + '/PUT', path + '/PATCH', path + '/DELETE']
    possiblePaths.forEach(route => {
        const middlewaresAndControllers = routes.get(route) || []

        if (middlewaresAndControllers.length) {
            routes.set(route, [...middlewares, ...middlewaresAndControllers])
        }
    })
}

// (3)
const useAll = (...middlewares) => {
    middlewaresForAll.push(...middlewares)
}

const serverHandler = async (request, response) => {
    const sanitizedUrl = sanitizeUrl(request.url, request.method)

    const match = matchUrl(sanitizedUrl)

    if (match) {
        const middlewaresAndControllers = routes.get(match)
// (4)
        await dispatchChain(request, response,
            [requestDecorator.bind(null, routes.keys()), responseDecorator, ...middlewaresForAll, ...middlewaresAndControllers])
    } else {
        response.statusCode = 404
        response.end('Not found')
    }
}
Enter fullscreen mode Exit fullscreen mode

From the code sessions above

1 - We need an array to store the middlewares that should be applied for all routes.

2 - The use function is responsible to add middleware for a specified path independently of the used method.

3 - The useAll is a way to add middleware to the middleware store.

4 - Now we can modify our serverHandler function to apply the array of middlewares for all routes.


Modular Router

It is a mini-app that we can include in our main application.

src/router.js

const Router = () => {
  const routes = new Map()
  const middlewaresForAll = []

  const getRoutes = () => {
    return routes
  }

  const getMiddlewaresForAll = () => {
    return middlewaresForAll
  }

  const useAll = (...middlewares) => {
    middlewaresForAll.push(...middlewares)
  }

  const use = (path, ...middlewares) => {
    const possiblePaths = [path + '/GET', path + '/POST', path + '/PUT', path + '/PATCH', path + '/DELETE']
    possiblePaths.forEach(route => {
      const middlewaresAndControllers = routes.get(route) || []

      if (middlewaresAndControllers.length) {
        routes.set(route, [...middlewares, ...middlewaresAndControllers])
      }
    })
  }

  const get = (path, ...handlers) => {
    const middlewaresAndControllers = routes.get(`${path}/GET`) || []
    routes.set(`${path}/GET`, [...middlewaresAndControllers, ...handlers])
  }

  const post = (path, ...handlers) => {
    const middlewaresAndControllers = routes.get(`${path}/POST`) || []
    routes.set(`${path}/POST`, [...middlewaresAndControllers, ...handlers])
  }

  const put = (path, ...handlers) => {
    const middlewaresAndControllers = routes.get(`${path}/PUT`) || []
    routes.set(`${path}/PUT`, [...middlewaresAndControllers, ...handlers])
  }

  const patch = (path, ...handlers) => {
    const middlewaresAndControllers = routes.get(`${path}/PATCH`) || []
    routes.set(`${path}/PATCH`, [...middlewaresAndControllers, ...handlers])
  }

  const del = (path, ...handlers) => {
    const middlewaresAndControllers = routes.get(`${path}/DELETE`) || []
    routes.set(`${path}/DELETE`, [...middlewaresAndControllers, ...handlers])
  }

  return {
    get,
    post,
    put,
    patch,
    del,
    use,
    useAll,
    getRoutes,
    getMiddlewaresForAll
  }

}

module.exports = Router
Enter fullscreen mode Exit fullscreen mode

That is all we have in the main app, the only difference we need to return routes and middlewaresForAll too.

To use our modular router we need to get the router handlers and combine them with existent routes.

src/app.js

const useRouter = (path, router) => {
        const routerRoutes = router.getRoutes()
        const middlewaresFromRouter = router.getMiddlewaresForAll()
        const existentHandlers = routes.get(path) || []
        routerRoutes.forEach((middlewares, key) => {
            routes.set(`${path + key}`, [...existentHandlers, ...middlewaresFromRouter, ...middlewares])
        })
    }

Enter fullscreen mode Exit fullscreen mode

Testing

router.get('/users', (req, res) => res.end('User route from router instance'))
router.get('/admins', (req, res) => res.end('Admins route'))

router.useAll((req, res, next) => {
    console.log('middleware for router instance /admins and /users')
    next()
})

router.use('/users', (req, res, next) => {
    console.log('middleware for /users')
    next()
})

router.use('/admins', (req, res, next) => {
    console.log('middleware for /admins')
    next()
})


app.useRouter('', router)

app.use('/admins', (req, res, next) => {
    console.log('middleware for all admins routes')
    next()
})

app.useAll((req, res, next) => {
    console.log('middleware for all routes')
    next()
})
Enter fullscreen mode Exit fullscreen mode

If I try to access admins route running in a terminal this command:

curl --location 'localhost:3000/admins'
Enter fullscreen mode Exit fullscreen mode

You must see in the console something like that:

middleware for all routes
middleware for all admins routes
middleware for router instance /admins and /users
middleware for /admins
Enter fullscreen mode Exit fullscreen mode

That's all for now. You can see the entire code here

In the next tutorial, I will show how to serve static files.

See you soon!

Top comments (0)