Monday, May 20, 2024
 Popular · Latest · Hot · Upcoming
115
rated 0 times [  116] [ 1]  / answers: 1 / hits: 6082  / 4 Years ago, fri, july 31, 2020, 12:00:00

I want to be able to add new routes at runtime without restarting the server with NodeJS & ExpressJS. I made a similiar approach like in this article: https://alexanderzeitler.com/articles/expressjs-dynamic-runtime-routing/
Technically I'm able to add new files and logic at runtime likewise in the article, but the problem is that when no api route was matched I'll send a 404 JSON respond (as it is supposed to be).

I think the problem that I'm having is that my dynamically created routes are never reached, because static routes have priority over dynamically created routes. This means that the created routes will be mounted after error handling and therefore will never be reached. My Code in 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;




When I comment out the error handling I'm able to reach the routes which I created during runtime whereas with error handling I can only reach dynamically created routes after server restart which I want to avoid.
The problem is not solved with query params, because the dynamically added routes differ in logic, model properties, http methods/verbs and API endpoints. e.g.
GET/POST /api/{endpoint}
GET/POST /api/foo/{endpoint}
GET/PUT/DELETE /api/foo/bar/{endpoint}/:id


I think I basically need to either:
1) find a way to mount the dynamically created routes before the error handling - which I'm currently stuck at or
2) modify the route stack - which I have read is impractical, slow, bad practice and error prone
3) find an alternative solution

I hope someone can help me.
Thanks in advance

EDIT
Here is the code for the creation of new routes. The relevant endpoint is /api/databases/ in the POST method




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'});
}
}

...




The controller in the snippet is just a modular controller file, which handles all of my CRUD Operations of all the endpoints of different models. Each route is split into models, controllers and routes to seperate and better maintain their logic.

In the POST method I first check whether the endpoint to be created already exists. If it does I respond with a 422 respond that the endpoint already exists. If it does not exist I create an entry mith my modular controller in the databases endpoint and create a model, controller & route for the endpoint which should be created.

The creation logic is the following:




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);
});
});
});
};




Here I basically pass along the data from the POST method to the model, controller & route file which are created asynchronously. When all files are created I mount the endpoint route to the app. The logic to mount the route is:




const mountEndpoints = (path, app) => {
const module = require(`../routes/${path}`);
app.use(`/api/${module.plural ? `${module.plural}` : `${path}s`}`, module);
}




A created route might look like the following:




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 includes some logic for authorization/authentication.

The code does pretty much what I want it to do except that I don't know how to handle the positioning of the route before the error handling.


More From » node.js

 Answers
12

Express routes will be handled in creation order.


To add routes in specific locations after the app definition you can create a placeholder router and attach routes to that instead of modifying the app itself.


Express doesn't support deleting routes once they are defined, but you can replace an entire router.


Create an express router instance (or even another app if needed) to mount the dynamic endpoints on. Redefine the router whenever you want to change the routes (apart from additions to the end of the routers stack, which is supported by express).


// Routes
app.use('/api/products', productRoutes);
app.use('/api/users', userRoutes);

let dynamicApiRouter = null

export function setupDynamicRouter(route_configs) {
dynamicApiRouter = new express.Router()
// Add routes to dynamicApiRouter from `route_configs`
for (const config of route_configs) {
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}});
});

setupDynamicRouter() can be called at any time with one or a list of routes and handlers to setup:


const route_config = [
{
method: 'get',
path: '/sales',
handler: (req, res, next) => res.json({ ok: true }),
},
{
method: 'post',
path: '/sales',
handler: (req, res, next) => res.json({ post: true }),
},
])
setupDynamicRouter(route_config)

For the questions example "routes" setup, the /api path prefix now lives on the router mount in the parent app so can be removed from each router.use


const mountEndpoints = (path, router) => {
const module = require(`../routes/${path}`);
router.use(`/${module.plural ? `${module.plural}` : `${path}s`}`, module);
}

[#3009] Wednesday, July 29, 2020, 4 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
kristineterrak

Total Points: 74
Total Questions: 109
Total Answers: 115

Location: Anguilla
Member since Sun, Jan 29, 2023
1 Year ago
kristineterrak questions
Sun, Jul 25, 21, 00:00, 3 Years ago
Wed, Mar 4, 20, 00:00, 4 Years ago
Thu, Nov 14, 19, 00:00, 5 Years ago
Sun, Apr 28, 19, 00:00, 5 Years ago
Mon, Mar 4, 19, 00:00, 5 Years ago
;