Skip to content

Instantly share code, notes, and snippets.

@beardedtim
Last active December 22, 2019 16:09
Show Gist options
  • Save beardedtim/01a7cc97e95f26a2ffa746820373b7e1 to your computer and use it in GitHub Desktop.
Save beardedtim/01a7cc97e95f26a2ffa746820373b7e1 to your computer and use it in GitHub Desktop.
Create Express Routes Based on Folder Structure

Usage

The above script will create an Express Router with routes that mirror the folder structure, with the possibleAPIMethods file names being the verb handlers.

Example:

Given the folder structure

-- A
    |
    | -- B
          |
          | -- C
                |
                | -- get.js

it would create an API handler for the following request

GET /A/B/C

and

-- A
    |
     | -- post.js
     | -- get.js

will handle

POST /A
GET /A

and finally

-- A
    |
    | -- :id
            |
            | -- get.js

will handle

GET /A/1234
GET /A/asdf324232980=3242
GET /A/<...pretty much anything>
/** some handler **/
module.exports = async (req, res, next) => {
// any valid express handler
}
/**
* If you need to run multiple middleware,
* you can export an array
*/
module.exports = [
require('body-parser').json(),
async (req, res, next) => { /* ... */ }
]
const { Router } = require('express')
const path = require('path')
const parseAPI = require('./parseAPI')
const API = Router()
module.exports = apiPrefix =>
parseAPI(path.resolve(__dirname, 'v1')).then(routes => {
const routeMap = {}
for (const path of Object.keys(routes)) {
for (const [method, handler] of Object.entries(routes[path])) {
// If they gave us an array, they have custom
// middleware they want to run
if (Array.isArray(handler)) {
API[method](path, ...handler)
} else {
// They gave us just a function
API[method](path, handler)
}
const fullPath =
apiPrefix[apiPrefix.length - 1] === '/'
? `${apiPrefix.slice(0, -1)}${path}`
: `${apiPrefix}${path}`
// If we've seen this path before
routeMap[fullPath]
? // we this method
(routeMap[fullPath][method] = true)
: // else we need to create the object
(routeMap[fullPath] = { [method]: true })
}
}
// We have an endpoint to see
// all of the available routes
// and their methods
API.use('/_available', (_, res) =>
res.json({
data: routeMap,
meta: {
description:
'These are the avaialable routes for the API to take. If you do not see the route that you are looking for, you either do not have authorization to view it or it does not exist.'
}
})
)
return API
})
const fs = require('fs')
const path = require('path')
const { promisify } = require('util')
const readDir = promisify(fs.readdir)
const possibleAPIMethods = new Set([
'post',
'put',
'get',
'patch',
'delete',
'head',
'options'
])
const parseAPIFolders = async (rootDir, context = {}) => {
const files = await readDir(rootDir, { withFileTypes: true })
for (const f of files) {
// This is a file
if (f.isFile()) {
// Let's see if it's a method we know how to parse
const possibleMethod = path.basename(path.resolve(rootDir, f.name), '.js')
// If it is a method we know how to parse,
// let's add it to context!
if (possibleAPIMethods.has(possibleMethod)) {
const methodURLPath = rootDir.replace(__dirname, '')
context[methodURLPath] = context[methodURLPath]
? {
...context[methodURLPath],
[possibleMethod]: require(path.resolve(rootDir, f.name))
}
: {
[possibleMethod]: require(path.resolve(rootDir, f.name))
}
}
} else if (f.isDirectory()) {
await parseAPIFolders(path.resolve(rootDir, f.name), context)
}
}
return context
}
module.exports = parseAPIFolders
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment