Router instances in Node.js with express with a database - javascript

As far as I can gather from the Express documentation, when you declare an express.Router(), it creates a single instance of a router that you can then assign a routing path and execute logic with. The documentation says to think of a router like a mini-app for a specific route or routes, which makes sense.
I'm trying to strategize what to wrap my database connection around (using mongodb, let's say via mongoose, but it shouldn't matter). I of course don't want to open a new database connection on every route call, and I assume that if I wrap it around a router only one Router() instance will only be created. In other words, if I went to /routes/index.js, defined a Router() and then opened a database connection, then within it did router.get (or router.post, etc.), I would be opening one database connection when launching the app, not one per hit on that route.
Yet there might be other routes beyond index.js where I want access to the database. So alternatively, could I wrap the database connection around the app.use route handlers and other middleware within the main app.js file, then require('../app') in /routes files and add the database connection to module.exports in app.js, and finally define route logic in other files like /routes/index.js?
I'm a little confused on how to structure everything. Any guidance would be much appreciated.

If you are using mongoose, you can just connect once with some code like this:
mongoose.connect("mongodb://127.0.0.1:27017/test");
mongoose.connection.on('error', console.error.bind(console, 'Db connection error:'));
// Start the server once we have connected to the database.
mongoose.connection.once('open', () => {
console.log('Db connection open.');
app.listen(3000, function () {
console.log('Listening on port 3000');
});
});
And then, if you have a mongoose model named Foo set up like
const Foo = mongoose.model('Foo', fooSchema); // fooSchema is a mongoose.Schema
Then in your route you can use it like so:
const router = express.Router();
const Foo = require('./models/foo');
router
.route('/foos/:foo_id')
.get((req, res)=> {
Foo.findById(req.params.foo_id, (err, foo) => {
if (err) return res.sendStatus(500);
if (!foo) return res.status(404).send('Foo not found.');
res.status(200).json(foo);
});
});
This kind of setup lets mongoose handle connection pooling.

Related

Call a router WITHIN Nodejs after a route has been called

I am using Expressjs and the Auth0 API for authentication and ReactJs for client side.
Because of the limitations of the Auth0 API (spoke with their team) I am sending updated user details to my backend and then using app.set() to be able to use the req.body in another route.
I need to call the app.patch() route automatically after the app.post() route has been hit.
The end goal is that the users data will be updated and shown client side.
const express = require('express');
const cors = require('cors');
const path = require('path');
const app = express();
require('dotenv').config()
const { auth } = require("express-openid-connect");
app.use(express.json());
app.use(cors());
app.use(express.static(path.join(__dirname, 'build')));
app.use(
auth({
issuerBaseURL: process.env.AUTH0_ISSUER_BASE_URL,
baseURL: process.env.BASE_URL,
clientID: process.env.AUTH0_CLIENT_ID,
secret: process.env.SESSION_SECRET,
authRequired: false,
auth0Logout: true,
})
);
app.get('/', async (req, res) => {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.get('/api', async (req, res) => {
const stripe = require('stripe')(`${process.env.REACT_APP_Stripe_Live}`);
const invoice = await stripe.invoices.list({
limit: 3,
});
res.json(invoice);
});
app.post('/updateuser', (req, ) => {
app.set('data', req.body);
})
app.patch(`https://${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/users/:id`,(req,res) => {
let val = app.get('data');
req.params = {id: val.id};
console.log(req.params);
})
app.listen(process.env.PORT || 8080, () => {
console.log(`Server listening on 8080`);
});
I'd suggest you just take the code from inside of app.patch() and make it into a reusable function. Then it can be called from either the app.patch() route directly or from your other route that wants to do the same funtionality. Just decide what interface for that function will work for both, make it a separate function and then you can call it from both places.
For some reason (which I don't really understand, but seems to happen to lots of people), people forget that the code inside of routes can also be put into functions and shared just like any other Javascript code. I guess people seems to think of a route as a fixed unit by itself and forget that it can still be broken down into components and those components shared with other code.
Warning. On another point. This comment of yours sounds very wrong:
and then using app.set() to be able to use the req.body in another route
req.body belongs to one particular user. app.set() is global to your server (all user's requests access it). So, you're trying to store temporary state for one single user in essentially a global. That means that multiple user's request that happen to be in the process of doing something similar will trounce/overwrite each other's data. Or worse, one user's data will accidentally become some other user's data. You cannot program a multi-user server this way at all.
The usual way around this is to either 1) redesign the process so you don't have to save state on the server (stateless operations are generally better, if possible) or 2) Use a user-specific session (like with express-session) and save the temporary state in the user's session. Then, it is saved separately for each user and one user's state won't overwrite anothers.
If this usage of app.set() was to solve the original problem of executing a .patch() route, then the problem is solved by just calling a shared function and passing the req.body data directly to that shared function. Then, you don't have to stuff it away somewhere so a later route can use it. You just execute the functionality you want and pass it the desired data.

nodejs + express where load context configuration from database

my scenario id a nodejs + express app connected to a mysql database.
There are some context configurations, valid for all the website and all users, that I need to load from the database at every page request (not at app init because I don't want to reload the entire server when the configuration changes).
My idea was to use a simple middleware to inject the context into the request, like that:
const express = require('express');
const contextSetup = require('./contextSetup');
const app = express();
...
app.use(contextSetup.middleWare);
...
and in contextSetup.js:
const DB = require('./DB');
module.exports.middleWare = (req, res, next) => {
DB.query('select `code`, `data` from `setting` order by `sort`')
.then((rows) => {
const keyVals = {};
rows.forEach((row) => {
keyVals[row.code] = row.data;
});
req.appContext = keyVals;
next();
})
.catch((err) => next(err));
};
The only warning is to use the middleware after app.use(express.static()); otherwise my middleware would be called even for static files request (images, css, js, ...).
Is this approach correct?
Thank you
this works in my book! I might suggest that you put this on a router, so this works only for logged in users, for example. You might also further isolate your routers so that if there are any ajax routes, that these values are not requeried only to be ignored. So the code seems fine, and if performance becomes an issue, just implement a cache which, say, refreshes every half minute or ten requests. But its only one query, so that seems fine for now.

Express routing - prevent app.use('/') top-level path middleware from executing when visiting child/nested paths directly

So I'm not really sure if the title is descriptive enough, but here is a super simple example.
My site has a public area and a restricted admin area.
example.com/admin (admin home page)
example.com/admin/news (news page)
example.com/admin/posts (posts page)
And because I don't want people who aren't administrators or logged in to be able to access it, I have a simple middleware function to check for cookies.
app.js
const express = require('express');
const app = express();
const authMiddleWere = async (req, res, next) => {
// pseudo-code, do some cookie validity check here
console.log(`Route: ${req.url}`)
if (cookie) {
next();
}
};
const adminRouter = require('./routes/private/home');
const newsRouter = require('./routes/private/news');
const postsRouter = require('./routes/private/posts');
app.use('/admin/', authMiddleWere, adminRouter);
app.use('/admin/news', authMiddleWere, newsRouter);
app.use('/admin/posts', authMiddleWere, postsRouter);
/routes/private/home.js
const express = require('express');
const router = express.Router();
router.get('/', async (req, res, err) => {
res.render('private/home');
});
module.exports = router;
The problem here is that this authMiddleWere function gets called twice when I visit nested paths such as example.com/admin/news which shares the same pattern - it's starting with /admin/......
I can tell that for sure because we are logging the req.url in our middleware function so if I go to example.com/admin it will log out:
Route: /
But if I go to example.com/admin/news it will log out both:
Route: /
Route: /news
So what is causing this and how do I work my way around it? I'm assuming that what I described is the intended behavior of Express.js so I am looking for a way to get around this or (re)structure my code better.
Cheers!
You can use a regex for your route.
app.use(/\/admin$/, authMiddlewear, authRouter);
This will match only routes that end in admin. You may need to handle cases where the route is /admin/ instead of /admin, but iirc, express handles that intelligently.
Well one way you can fix this is by creating a separate route file and splitting everything into a MVC manner. For example:
Inside your main app.js just create a route pointing to the /admin like so:
app.use('/admin', authMiddleWere, require('./src/your-route-to-the-file/admin.route'));
Inside the admin.route file, call your controller like this:
const express = require("express");
const router = express.Router();
const mainAdminCtrl = require("../controllers/admin.controller");
router.get("/news", mainAdminCtrl.adminAuthDisplay);
module.exports = router;
Where the const mainAdminCtrl is your controller and the function adminAuthDisplay is your service.
Essentially, you are splitting your functionality in to a dedicated router, controller and service file. So when you try to access the route /admin, it will look for any suffix inside the router file.
In a case where you want to access the /news endpoint, your API will only make the call once.
If this helps, I can expand my explanation further.

Linking Socket IO to Express routes

I'm been looking around everywhere for the last few days for a way to access my socket IO instance running in Express from my routes and have not found a working solution.
The problem here is that I only run my socket IO instance at run-time and my routes and defined before that obviously.
The most promising solution I have found is one that wraps my routes in a function and requires it in my app.js file with whilst passing in the IO instance as an argument like so var routes = require('routes')(io)
Here is my setup:
app.js
const express = require('express');
const app = express();
const server = require('http').Server(app);
const io = require('socket.io')(server);
io.on('connection', function(client) {
console.log(io.sockets.sockets)
});
app.set('port', 7777);
const routes = require('./routes/index')(io);
app.use('/', routes);
app.listen('7777');
routes.js
module.exports = function(io) {
const express = require('express');
const router = express.Router();
router.get('/test', (req, res) => {
console.log(io.sockets.sockets);
});
return router;
}
If I connect to the WebSocket, my io.on event fires and I get a console log of the connected sockets in the form of a fairly large object that contains the socket id's etc.
If however, I get to the route '/test' I get a console log of a blank object: {}. I would imagine this is because the instance of the socket I am passing down to the routes does not have anyone connected to it at the time hence the blank object is returned, but this is just my best guess.
Where I'm a little stuck is how to get the full instance with all live connections to the route.
Alternatively, I've thought of attaching the io instance to the request object like so in order to have access to it in the routes:
server.on('request', function(req, res){
req.io = io;
}
but couldn't quite get it to work, nor am I sure this is the correct approach.
I imagine this must be a common issue so I would love a clear answer on how to work around this and the correct approach to tackle the issue.
EDIT
So I eventually got my above code working, I hit the '/test' endpoint from an AJAX GET request within the chrome extension instead of my just visiting the URL localhost:7777/test. What I can't understand here is why it works with an AJAX request but not page navigation? (The page navigation is done after I make the socket connection in the extension)

Express/node.js app.use for router makes client lag out

So I wrote up this code on the server side (called app.js):
console.log("Server started. If you're reading this then your computer is still alive."); //Unnecessary test command to make sure everything works.
var express = require("express");
var app = express();
var serv = require("http").Server(app);
const router = express.Router;
app.get("/", function(req, res) {
res.sendFile(__dirname + "/client");
});
app.use("/", router);
app.use("/client", express.static(__dirname + "/client"));
serv.listen(2000);
//Set up server stuff. This isn't touched.
var io = require("socket.io")(serv, {});
io.sockets.on("connection", function(socket) {
console.log("Socket connection"); //This will print to the server, not the developer console in your browser.
});
//Initialize sockets and set up socket listeners. This isn't touched either, except when adding new events.
console.log("Ok"); //Just make sure.
And I have a client:
<!DOCTYPE html>
<html>
<head>
<title>Multiplayer!</title>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<script>
var socket = io();
</script>
</body>
</html>
When I run node app.js in the terminal and then go to localhost:2000 on my browser, it takes a minute or two to load, and then says "localhost didn't send any data" (on Chrome). When I comment out app.use("/", router);, it loads nicely (it doesn't work, because it can't GET /), so I know that there's something wrong with that line, but I don't know what. I looked around the express API documentation but couldn't find anything, so I'm asking here. Thanks!
This code:
const router = express.Router;
app.use("/", router);
is just wrong.
If you wanted to actually create a separate router, you would call the express.Router() constructor like to actually create a new router and then assign some routes to the new router (doc and example code here):
// call router constructor and then assign some routes to it
const router = express.Router();
router.get('/something, function(req, res) {
// handle this route here
});
// hook the router into our instance of express
app.use("/", router);
The crux of the issue is that express.Router is a factory function that creates a router. It's not a router itself. You have to execute it with express.Router() to actually make a router.
The code you had before would not send any response because when it tried to execute express.Router, it would call that function expecting it to be middleware. And, any correctly implemented middleware has to either send a response OR call next() to chain to the next middleware/route in the chain. The express.Router factory function does neither of those (it creates a new Router when called and is not the proper type of function to actually be a route handler all by itself) so the request just gets orphaned at that point, never sending anything to the client and never advancing to the other route handlers.
Eventually the request will time out.

Categories

Resources