ExpressJS:在运行时动态添加路由
ExpressJS: Adding routes dynamically at runtime
我希望能够在运行时添加新路由,而无需使用 NodeJS 和 ExpressJS 重新启动服务器。我在这篇文章中采用了类似的方法:https://alexanderzeitler.com/articles/expressjs-dynamic-runtime-routing/
从技术上讲,我能够在文章中同样在运行时添加新文件和逻辑,但问题是当没有 api路由匹配我将发送 404 JSON 响应(因为它应该是)。
我认为我遇到的问题是我动态创建的路由是从未到达,因为静态路由优先于动态创建的路由。这意味着创建的路由将在错误处理后挂载,因此永远不会到达。我的代码在 app.js
...
// Routes
app.use('/api/products', productRoutes);
app.use('/api/users', userRoutes);
...
/* This is where the dynamically created routes should be mounted */
// Error handling
app.use((req, res, next) => {
const err = new Error('Not found');
err.status = 404;
next(err);
});
app.use((err, req, res, next) => {
res.status(err.status || 500).json({error: {message: err.message}});
});
/* This is where the dynamic routes are mounted */
module.exports = app;
当我注释掉错误处理时,我能够到达我在运行时创建的路由,而通过错误处理,我只能在服务器重启后到达我想避免的动态创建的路由。
使用查询参数无法解决该问题,因为动态添加的路由在逻辑、模型属性、http methods/verbs 和 API 端点方面有所不同。例如
GET/POST/api/{端点}
GET/POST/api/foo/{端点}
GET/PUT/DELETE /api/foo/bar/{端点}/:id
我想我基本上需要:
1) 在错误处理之前找到一种方法来挂载动态创建的路由 - 我目前停留在或
2) 修改路由堆栈 - 这我读过的是不切实际、缓慢、糟糕的做法并且容易出错
3) 寻找替代解决方案
我希望有人能帮助我。
提前致谢
EDIT
这里是创建新路由的代码。 POST方法
中的相关端点是/api/databases/
const Database = require('../models/database');
const controller = require('./template/controller');
const creation = require('../Creation');
...
exports.createOne = (req, res, next) => {
if (!creation.findFileInDirectory(`./backend/api/models/${req.body.name.singular}.js`) ||
!creation.findFileInDirectory(`./backend/api/controllers/${req.body.name.singular}.js`) ||
!creation.findFileInDirectory(`./backend/api/routes/${req.body.name.singular}.js`)) {
controller.createOne(req, res, next, Database, {
modelName: 'database',
}, () => {
//creation.createEndpoint(req.body.name, req.body.data, req.body.auth);
creation.createEndpoint(req.body.name, req.body, req.body.auth);
});
} else {
res.status(422).json({message: 'Endpoint exists already'});
}
}
...
代码段中的控制器只是一个模块化控制器文件,它处理我对不同模型的所有端点的所有 CRUD 操作。每条路由都分为模型、控制器和路由,以分离并更好地维护它们的逻辑。
在 POST 方法中,我首先检查要创建的端点是否已经存在。如果确实如此,我会以 422 响应表示端点已经存在。如果它不存在,我会在数据库端点中使用我的模块化控制器创建一个条目,并为应该创建的端点创建一个模型、控制器和路由。
创建逻辑如下:
const createEndpoint = (name, data, auth) => {
createFile(`./backend/api/models/${name.singular}.js`, model.createModel(capitalize(name.singular), data), () => {
createFile(`./backend/api/controllers/${name.singular}.js`, controller.createController({singular: capitalize(name.singular), plural: name.plural}, data.data), () => {
createFile(`./backend/api/routes/${name.singular}.js`, route.createRoute({singular: capitalize(name.singular), plural: name.plural}, auth), () => {
const app = require('../../app');
mountEndpoints(name.singular, app);
});
});
});
};
这里我基本上将数据从 POST 方法传递给异步创建的模型、控制器和路由文件。创建所有文件后,我将端点路由挂载到应用程序。挂载路由的逻辑是:
const mountEndpoints = (path, app) => {
const module = require(`../routes/${path}`);
app.use(`/api/${module.plural ? `${module.plural}` : `${path}s`}`, module);
}
创建的路线可能如下所示:
const express = require('express');
const router = express.Router();
const checkAuth = require('../middleware/check-auth');
const ProductController = require('../controllers/product');
router.route('/')
.get(ProductController.getAll)
.post(checkAuth, ProductController.createOne);
router.route('/:id')
.get(ProductController.getOne)
.patch(checkAuth, ProductController.patchOne)
.delete(checkAuth, ProductController.deleteOne);
module.exports = router;
module.exports.plural = 'products';
checkAuth 包括 authorization/authentication 的一些逻辑。
除了我不知道如何处理错误处理前的路由。
快递路线将按创建顺序处理。
要在 app
定义之后的特定位置添加路由,您可以创建一个占位符路由器并将路由附加到那里,而不是 app
本身。
Express 不支持在定义后删除路由,但您可以replace an entire router。
创建一个快速路由器实例(如果需要,甚至可以创建另一个 app
)来安装动态端点。每当你想改变路由时重新定义路由器(除了添加到路由器堆栈的末尾,这是由 express 支持的)。
// Routes
app.use('/api/products', productRoutes);
app.use('/api/users', userRoutes);
let dynamicApiRouter = null
export function setupDynamicRouter(config) {
dynamicApiRouter = new express.Router()
// Add routes to dynamicApiRouter from `config`
dynamicApiRouter[config.method](config.path, config.handler)
}
app.use('/api', (req, res, next) => dynamicApiRouter(req, res, next))
// Error handling
app.use((req, res, next) => {
const err = new Error('Not found');
err.status = 404;
next(err);
});
app.use((err, req, res, next) => {
res.status(err.status || 500).json({error: {message: err.message}});
});
然后当你挂载一个新端点时,将路由器传入并删除 /api
路径前缀,因为它现在在父 app
.
的路由器外部处理
const mountEndpoints = (path, router) => {
const module = require(`../routes/${path}`);
router.use(`/${module.plural ? `${module.plural}` : `${path}s`}`, module);
}
只是添加到上面的答案,这是正确的,你基本上需要一个 express
应用程序或一个 Router
实例来附加你的路由。
在我的例子中,我有一个异步初始化的遗留 Node.js 应用程序,这意味着底层 express
中间件在内部初始化数据库连接后返回。
此处 myApp
是具有 init
方法的遗留应用程序,returns 快速中间件 instance
一段时间后的回调:
var express = require('express')
var myApp = require('my-app')
module.exports = (config) => {
// create an app handle
var handle = express()
myApp.init(config, (err, instance) => {
if (err) {}
// mount lazily
handle.use(instance)
})
// return immediately
return handle
}
我调用了那个包装器模块 lazy-load.js
然后我像这样使用它:
var express = require('express')
var lazyLoad = require('lazy-load')
express()
.use('/a', lazyLoad(require('./config-a.json')))
.use('/b', lazyLoad(require('./config-b.json')))
.listen(3000)
我想为同一应用程序的多个实例提供服务,但针对不同的用户使用不同的配置。
关于最初的问题,同样,只要您继续引用最初附加到您的服务器的 handle
中间件实例,您就可以在运行时继续向其添加新路由。
我希望能够在运行时添加新路由,而无需使用 NodeJS 和 ExpressJS 重新启动服务器。我在这篇文章中采用了类似的方法:https://alexanderzeitler.com/articles/expressjs-dynamic-runtime-routing/
从技术上讲,我能够在文章中同样在运行时添加新文件和逻辑,但问题是当没有 api路由匹配我将发送 404 JSON 响应(因为它应该是)。
我认为我遇到的问题是我动态创建的路由是从未到达,因为静态路由优先于动态创建的路由。这意味着创建的路由将在错误处理后挂载,因此永远不会到达。我的代码在 app.js
...
// Routes
app.use('/api/products', productRoutes);
app.use('/api/users', userRoutes);
...
/* This is where the dynamically created routes should be mounted */
// Error handling
app.use((req, res, next) => {
const err = new Error('Not found');
err.status = 404;
next(err);
});
app.use((err, req, res, next) => {
res.status(err.status || 500).json({error: {message: err.message}});
});
/* This is where the dynamic routes are mounted */
module.exports = app;
当我注释掉错误处理时,我能够到达我在运行时创建的路由,而通过错误处理,我只能在服务器重启后到达我想避免的动态创建的路由。
使用查询参数无法解决该问题,因为动态添加的路由在逻辑、模型属性、http methods/verbs 和 API 端点方面有所不同。例如
GET/POST/api/{端点}
GET/POST/api/foo/{端点}
GET/PUT/DELETE /api/foo/bar/{端点}/:id
我想我基本上需要:
1) 在错误处理之前找到一种方法来挂载动态创建的路由 - 我目前停留在或
2) 修改路由堆栈 - 这我读过的是不切实际、缓慢、糟糕的做法并且容易出错
3) 寻找替代解决方案
我希望有人能帮助我。
提前致谢
EDIT
这里是创建新路由的代码。 POST方法
const Database = require('../models/database');
const controller = require('./template/controller');
const creation = require('../Creation');
...
exports.createOne = (req, res, next) => {
if (!creation.findFileInDirectory(`./backend/api/models/${req.body.name.singular}.js`) ||
!creation.findFileInDirectory(`./backend/api/controllers/${req.body.name.singular}.js`) ||
!creation.findFileInDirectory(`./backend/api/routes/${req.body.name.singular}.js`)) {
controller.createOne(req, res, next, Database, {
modelName: 'database',
}, () => {
//creation.createEndpoint(req.body.name, req.body.data, req.body.auth);
creation.createEndpoint(req.body.name, req.body, req.body.auth);
});
} else {
res.status(422).json({message: 'Endpoint exists already'});
}
}
...
代码段中的控制器只是一个模块化控制器文件,它处理我对不同模型的所有端点的所有 CRUD 操作。每条路由都分为模型、控制器和路由,以分离并更好地维护它们的逻辑。
在 POST 方法中,我首先检查要创建的端点是否已经存在。如果确实如此,我会以 422 响应表示端点已经存在。如果它不存在,我会在数据库端点中使用我的模块化控制器创建一个条目,并为应该创建的端点创建一个模型、控制器和路由。
创建逻辑如下:
const createEndpoint = (name, data, auth) => {
createFile(`./backend/api/models/${name.singular}.js`, model.createModel(capitalize(name.singular), data), () => {
createFile(`./backend/api/controllers/${name.singular}.js`, controller.createController({singular: capitalize(name.singular), plural: name.plural}, data.data), () => {
createFile(`./backend/api/routes/${name.singular}.js`, route.createRoute({singular: capitalize(name.singular), plural: name.plural}, auth), () => {
const app = require('../../app');
mountEndpoints(name.singular, app);
});
});
});
};
这里我基本上将数据从 POST 方法传递给异步创建的模型、控制器和路由文件。创建所有文件后,我将端点路由挂载到应用程序。挂载路由的逻辑是:
const mountEndpoints = (path, app) => {
const module = require(`../routes/${path}`);
app.use(`/api/${module.plural ? `${module.plural}` : `${path}s`}`, module);
}
创建的路线可能如下所示:
const express = require('express');
const router = express.Router();
const checkAuth = require('../middleware/check-auth');
const ProductController = require('../controllers/product');
router.route('/')
.get(ProductController.getAll)
.post(checkAuth, ProductController.createOne);
router.route('/:id')
.get(ProductController.getOne)
.patch(checkAuth, ProductController.patchOne)
.delete(checkAuth, ProductController.deleteOne);
module.exports = router;
module.exports.plural = 'products';
checkAuth 包括 authorization/authentication 的一些逻辑。
除了我不知道如何处理错误处理前的路由。
快递路线将按创建顺序处理。
要在 app
定义之后的特定位置添加路由,您可以创建一个占位符路由器并将路由附加到那里,而不是 app
本身。
Express 不支持在定义后删除路由,但您可以replace an entire router。
创建一个快速路由器实例(如果需要,甚至可以创建另一个 app
)来安装动态端点。每当你想改变路由时重新定义路由器(除了添加到路由器堆栈的末尾,这是由 express 支持的)。
// Routes
app.use('/api/products', productRoutes);
app.use('/api/users', userRoutes);
let dynamicApiRouter = null
export function setupDynamicRouter(config) {
dynamicApiRouter = new express.Router()
// Add routes to dynamicApiRouter from `config`
dynamicApiRouter[config.method](config.path, config.handler)
}
app.use('/api', (req, res, next) => dynamicApiRouter(req, res, next))
// Error handling
app.use((req, res, next) => {
const err = new Error('Not found');
err.status = 404;
next(err);
});
app.use((err, req, res, next) => {
res.status(err.status || 500).json({error: {message: err.message}});
});
然后当你挂载一个新端点时,将路由器传入并删除 /api
路径前缀,因为它现在在父 app
.
const mountEndpoints = (path, router) => {
const module = require(`../routes/${path}`);
router.use(`/${module.plural ? `${module.plural}` : `${path}s`}`, module);
}
只是添加到上面的答案,这是正确的,你基本上需要一个 express
应用程序或一个 Router
实例来附加你的路由。
在我的例子中,我有一个异步初始化的遗留 Node.js 应用程序,这意味着底层 express
中间件在内部初始化数据库连接后返回。
此处 myApp
是具有 init
方法的遗留应用程序,returns 快速中间件 instance
一段时间后的回调:
var express = require('express')
var myApp = require('my-app')
module.exports = (config) => {
// create an app handle
var handle = express()
myApp.init(config, (err, instance) => {
if (err) {}
// mount lazily
handle.use(instance)
})
// return immediately
return handle
}
我调用了那个包装器模块 lazy-load.js
然后我像这样使用它:
var express = require('express')
var lazyLoad = require('lazy-load')
express()
.use('/a', lazyLoad(require('./config-a.json')))
.use('/b', lazyLoad(require('./config-b.json')))
.listen(3000)
我想为同一应用程序的多个实例提供服务,但针对不同的用户使用不同的配置。
关于最初的问题,同样,只要您继续引用最初附加到您的服务器的 handle
中间件实例,您就可以在运行时继续向其添加新路由。