# ⛅ Smart Routing with Cloudflare Workers and Webpack (bonus content)

Automatic Smart Routing for Cloudflare Workers simplifies routing and removes boilerplate. Currently each route is setup manually. This has been tedious and needs to change.

%[https://www.youtube.com/watch?v=q17r-ZLFMJA]

## Cloudflare Workers Course Outline

1. [Getting Started with Serverless Cloudflare Workers](https://joel.net/getting-started-with-serverless-cloudflare-workers-lesson-1)
2. [Cloudflare Workers as a Web Server (with Webpack)](https://joel.net/cloudflare-workers-as-a-web-server-with-webpack-lesson-2)
3. [Making API Calls From a Cloudflare Worker](https://joel.net/making-api-calls-from-a-cloudflare-worker-lesson-3)
4. [Key-Value Storage With Cloudflare Workers KV](https://joel.net/key-value-storage-with-cloudflare-workers-kv-lesson-4)
5. **[Bonus] Smart Routing with Cloudflare Workers**

## Before Smart Routing

This is what my routes look like before smart routing. Multiple imports as well as routes to manage inside of `src/index.js`.

```javascript
// ❌ Every page is imported at the top
import allPeople from './pages/allPeople'
import index from './pages/index'
import person from './pages/person'
import files from './pages/files'
import filesPost from './pages/files.post'
import filesList from './pages/files.list'

// ❌ Every route is manually added
router.get('/', () => index(request))
router.get('/people/?', () => allPeople(request))
router.get('/people/.+', () => person(request))
router.get('/files/.+', () => files(request))
router.get('/files/?', () => filesList(request))
router.post('/files/?', () => filesPost(request))
```

I don't like this, there's far too much boilerplate. Fortunately with some features of webpack, this can be improved.

Also, In the previous lessons, I used a very crude method to pull parameters off the routes. A route like `/people/123` would be parsed and `123` would become `id`.

```javascript
// ❌ Gross
const person = async request => {
  const url = new URL(request.url)
  const id = url.pathname.substring(8)
  const person = await fetchPerson(id)
  /* code */
}
```

## What I Want Instead

It would be nice to add files to the project and not have to worry about routing. I want the pages and routes to just be picked up automatically and modify less files.

Something like this (below), where the route is included in the page and not in `index.js`.

```javascript
/**
  * ./src/pages/status.js
  */ 
import { textResponse } from '../lib/responses'

// ✅ routing is automatic
export const route = '/status/?'

const status = () => textResponse('OK')

export default status
```

It would also be nice if the parameterization of routes be be more idiomatic. Something like this:

```javascript
// ✅ Route Parameters
const person = async ({ params }) => {
  const person = await fetchPerson(params.id)
  /* code */
}
```

## Webpack's require.cache

Webpack keeps a cache of all files added to the project inside of `require.cache`. This can be exploited to build the routes automatically.

Because webpack's `require.cache` contains all files and not just the pages, the non-pages need to be filtered out. So the test that seems to work for this is a page must be a `Module` and also export a `route`.

This is what those tests look like:

```javascript
const isModule = module => {
  if (!module || !module.exports) return false
  return Object.prototype.toString.call(module.exports) === '[object Module]'
}

const hasRoute = module => {
  if (!module || !module.exports) return false
  return !!module.exports.route
}
```

Next, export `getRoutes`. This function filters for the route pages and returns an Array of routes that include a `route`, `method` (default to `get`), and the `module`.

```javascript
export const getRoutes = () =>
  Object.values(require.cache)
    .filter(module => isModule(module) && isRoute(module))
    .map(module => ({
      route: module.exports.route,
      method: module.exports.method || 'get',
      module: module.exports.default,
    }))
```

## Auto-include Files With Webpack

Once the imports for the pages are removed from `index.js`, Webpack won't include them. So they have to be forced into the bundle.

This can be done by modifying the `webpack.config.js` and adding the files there.

The [glob](https://www.npmjs.com/package/glob) function returns a list of all the JavaScript files in the `./src/pages/` directory.

```javascript
const glob = require('glob')

const files = glob.sync('./src/pages/**/*.js')
```

Include the files in the webpack's entry.

```javascript
module.exports = {
  entry: files.concat(['./src/index.js']),
}
```

If there's a better way, let me know in the comments.

## Add The Auto-Routing to `src/index.js`

Add the auto-routing before any catch-all routes. In this case, the `notFound` route should be included last.

```javascript
import Router from './lib/router'
import notFound from './pages/404'
import { getRoutes } from './lib/auto-routes'

const routes = getRoutes()
const router = new Router()

routes.forEach(({ route, method, module }) => router[method](route, module))
router.all(notFound)
```

## Creating Route Parameters

In the previous lessons, I used a very crude method to pull parameters off the routes. A route like `/people/123` would be parsed and `123` would become `id`.

```javascript
// ❌ Gross
const person = async request => {
  const url = new URL(request.url)
  const id = url.pathname.substring(8)
  const person = await fetchPerson(id)
  /* code */
}
```

It would be nice if I could do something like this instead:

```javascript
// ✅ Route Parameters
const person = async ({ params }) => {
  const person = await fetchPerson(params.id)
  /* code */
}
```

This feature can be added by modifying the [`src/lib/router.js`](https://github.com/joelnet/cloudflare-worker-website/blob/1fcd52001bd8e3ca9a4810b138979af6a50f5a2a/src/lib/router.js#L22) file to assign a `params` object onto the `req`. Find `Path` and add the one-liner like below.

```javascript
const Path = regExp => req => {
  const url = new URL(req.url)
  const path = url.pathname
  const match = path.match(regExp) || []
  // 👇 Insert this line to create the `params` on `req`.
  req.params = match && match.groups ? match.groups : {}
  return match[0] === path
}
```

Since routes are already RegEx, we can use the Named Group feature for this.

```javascript
// ❌ No Named Group
export const route = '/people/.+'

// ✅ Group Named "id"
export const route = '/people/(?<id>.+)'
```

Now code like this is possible!

```javascript
// 🔥 Route Parameters
const person = async ({ params }) => {
  const person = await fetchPerson(params.id)
  /* code */
}
```

## Summary

These improvements will simplify the the way new routes are created. It also extracts parameters from the routes.

Browse [the repository at this point in history](https://github.com/joelnet/cloudflare-worker-website/tree/1fcd52001bd8e3ca9a4810b138979af6a50f5a2a).

Subscribe to my [Newsletter](https://joel.net/newsletter) to continue learning about Cloudflare Workers!

Cheers 🍻

- Join my 📰 [Newsletter](https://joel.net/newsletter)
- Subscribe to my 📺 YouTube, [JoelCodes](https://www.youtube.com/c/JoelCodes?sub_confirmation=1)
- Say hi to me on Twitter [@joelnet](https://twitter.com/joelnet)

