Creating a DB service in an MVC Express app - node.js

I am creating a Node / Express / Mongo app and would like to have an MVC-style file layout with a separate database service. I'd like my routes (controllers) to be fairly lean and simply call methods from elsewhere when I need to perform CRUD functions.
This great post had a lot of solid answers. I'll paste below the the answer from EddieDean that seems to be closest to what I'd like to do.
However, when I'm trying to reference the Users object defined in the mongo.js file in my someFile.js file, it comes back as undefined. I am pretty sure I am missing something obvious and would love to know what that is.
I can get a reference to the DB connection itself and call dbConn.Users.addUser(x) but I cannot directly call Users.addUser(x) as in the someFile.js example below.
This seems minor but I've spent too much time on this to not have an answer. Call it a knowledge vendetta.
Thanks.
mongo.js
const { MongoClient } = require('mongodb');
const config = require('./config');
const Users = require('./Users');
const conf = config.get('mongodb');
class MongoBot {
constructor() {
const url = `mongodb://${conf.hosts.join(',')}`;
this.client = new MongoClient(url, conf.opts);
}
async init() {
await this.client.connect();
console.log('connected');
this.db = this.client.db(conf.db);
this.Users = new Users(this.db);
}
}
module.exports = new MongoBot();
Users.js
class User {
constructor(db) {
this.collection = db.collection('users');
}
async addUser(user) {
const newUser = await this.collection.insertOne(user);
return newUser;
}
}
module.exports = User;
app.js
const mongo = require('./mongo');
async function start() {
// other app startup stuff...
await mongo.init();
// other app startup stuff...
}
start();
someFile.js
const { Users } = require('./mongo');
async function someFunction(userInfo) {
const user = await Users.addUser(userInfo);
return user;
}

What I did is simply put all my routes into start function. This isn't the best solution, but as starting point at least not the worst.
So whenever you need an access to DB from some js file, just put them into start, so the mongo could establish the connection first.
So I want get the DB instance in /routes/users file.
const express = require("express");
const mongo = require("./mongo");
const app = express();
const PORT = process.env.PORT || 3000;
(async function start() {
await mongo.init();
app.use("/users", require("./routes/user"));
})();

If someFile.js is required before the mongo.init method is called, the mongoBot instance will have been created, but Users will indeed be undefined. If you need to require someFile before the init method is called, you can move your destructured assignment to inside your someFunction method.

Related

How to import file globally in node js

I have created a helper.js file that will contain helper functions. In order to use the helper functions, I have to import the file in all the locations where I want it to be used. Is there any way I can load/import the helper.js file globally?
helper.js
module.exports = {
responseObject: function (status, message, data, error) {
return {
status: status,
message: message,
data: data,
error: error
}
}
}
index.js
// load mongoose
require('./db/mongoose')
const express = require('express')
const app = express()
const port = process.env.PORT || 3000
app.use(helper)
// load routers
const userRouter = require('./routers/user')
app.use(express.json()) // automatically converts incoming requests to json
app.use(userRouter)
app.listen(port)
UserController.js
const User = require("../models/user")
const helper = require("../helper")
exports.createUser = async (req, res) => {
const user = new User(req.body)
try {
await user.save()
res.status(201).send({
status: true,
message: 'Registration success',
data: user,
error: {}
})
} catch (error) {
const response = helper.responseObject(false, "Wrong", {}, error)
res.status(400).send(response);
}
}
From the documentation:
In browsers, the top-level scope is the global scope. This means that
within the browser var something will define a new global variable. In
Node.js this is different. The top-level scope is not the global
scope; var something inside a Node.js module will be local to that
module.
This means you can't just define your responseObject in the "global" scope, as it always refers to the scope local to the module and will not be available to other modules.
But there's a way to do it, although I would not recommend it (search Google or SO why that's the case, this has been discussed in detail already). You can add your function/variable to the global object (which is somewhat similar to the window keyword in browsers):
// in your app.js "add" the exported stuff from the helper-module to the global object
const express = require('express')
...
global.myHelper = require('./helper');
// in e.g. your user-controller you could then access it like
const response = global.myHelper.responseObject(false, "Wrong", {}, error)

how to add custom function to express module

I am trying to add a method
loadSiteSettings to express module
In app.js
var express = require('express');
var path = require('path');
var mongoose = require('mongoose');
//Set up default monggose connection for mongo db
var mongoDB = 'mongodb+srv://***:*****#cluste******j.mongodb.net/cms?retryWrites=true&w=majority';
mongoose.connect(mongoDB,{useNewUrlParser: true});
//Get the default connection
var db = mongoose.connection;
//Bind connection to error event (to get notification of connection errors)
db.on('error',console.error.bind(console, 'MongoDB connection error:'));///????????
var app = express();
///////////////////////////////////////////////////////////
var indexRouter = require('./routes/index');
app.loadSiteSettings = async function()
{
let setting = await db.collection('settings').findOne();
app.locals.siteSettings = setting;
}
app.loadSiteSettings();
//////////////////////////////////////////////////////
module.exports = app;
Index.Js for router
var express = require('express');
var router = express.Router();
var app = require('../app');
var util = require('util');
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index');
});
///////////////////////////////////////////
router.get('/reloadSettings', function(req,res,next){
app.loadSiteSettings();
})
///////////////////////////////////////
module.exports = router;
so problem lies here, when server start it calls app.loadSiteSettings() in app.js
but when i use route '/reloadSettings' it seems app is undefined in index.js
This is an issue with circular dependencies and module.exports. This answer shows the same problem.
What's happening is app.js is required first and starts processing. The important thing to understand is that a file pauses execution while requiring a new file.
So when app.js requires ./routes/index, it has not finished processing, and has not reached module.exports = app. This means that when your routes file requires app.js, it's requiring it in its current state, which is the default export {}.
Thankfully there's a very simple fix:
// can be imported and tested separately from the app
const loadSiteSettings = function() {
return db.collection('settings').findOne();
}
router.get('/reloadSettings', async function(req,res,next){
let settings = await loadSiteSettings();
req.app.locals.siteSettings = settings
res.send(200); // signal the response is successful
})
This explains the issue in a lot more depth in case you're interested

How to reuse the same sequelize connection anywhere in my Express app?

Here is my problem, I have an Express app where I separate the controllers (i.e. the functions called for each route) from the application entry point:
index.js
...
const sequelize = new Sequelize(...);
const PersonController = require('controllers/PersonController');
router.route('/persons').get(PersonController.index)
...
controllers/PersonController.js
...
exports.index = (req, res) => {
//Return all the lines of the `Persons` table
};
...
But to access the database, I must have access to the sequelize instance in my controllers. How should I do?
Thank you for your help.
There are a lot of ways...
My favorite : you can create your constroller as a class...
Here is a kind of code, (not the best, but something running)
Controller class
class PersonController {
constructor(sequelize) {
this.sequelize = sequelize;
}
index(req, res){
const sequelize = sequelize;
... your code ...
}
}
module.exports = PersonController;
After
const sequelize = new Sequelize(...);
const PersonController = require('controllers/PersonController');
router.route('/persons').get((req, res) => {
const controller = new PersonController(sequelize);
controller.index(req, res);
})

Route.post() requires a callback function but got a [object Undefined] but it return a function

I know the question have been posted several times, and always it's a basic mistake, i already had this issue and always it was a mistake the kind of the already posted question.
It's like the 6th server i make this way, and this time i have no clue why it consider that not being a function.
here is the error displayed on the console
the error occur here
const router = require('express').Router();
module.exports = (api) => {
router.post('/',
api.middlewares.ensureAuthenticated,
api.middlewares.bodyParser.json(),
api.middlewares.agentDispenser.createMyAgent,
api.actions.hub);
return router;
}
when i comment
api.middlewares.agentDispenser.createMyAgent
it doesn't crash
my middleware index look like that
module.exports = (api) => {
api.middlewares = {
bodyParser: require('body-parser'),
ensureAuthenticated: require('./ensureAuthenticated'),
agentDispenser: require('./agentDispenser')
};
};
and agentDispenser look like this
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
var agent;
module.exports = (api) => {
function createMyAgent(req, res, next) {
agent = new WebhookClient({ req, res })
return next()
}
function getMyAgent() {
return agent
}
return {
createMyAgent,
getMyAgent
}
}
as i said, i use the same syntaxe/structure on many project, and it work, so i dont really know what is the probleme, i have read a lot of topic on that, a lot are about a forgoten return or missing (api)... but here i dont know
Thanks for your help in advance
EDIT: shame on me...
api.middlewares.agentDispenser.createMyAgent is undefined because api.middlewares.agentDispenser is a function that accepts api and returns your middleware namespace, and is not the namespace itself.
I'm guessing you want something like
module.exports = (api) => {
api.middlewares = {
bodyParser: require('body-parser'),
ensureAuthenticated: require('./ensureAuthenticated'),
agentDispenser: require('./agentDispenser')(api),
};
};

Exporting a Mongoose connection that becomes available in an async callback?

I'm new to coding in Node.js and I've created a DB connection through mongoose, then exporting the database object into my main app.js file:
// db.js
var mongoose = require("mongoose");
var uri = /*The URL*/;
var db = mongoose.connect(uri, {
useMongoClient: true
});
exports.db = db;
Here I'm trying to use that DB connection to perform CRUD operations:
// app.js
var db = require('./db');
app.post('/', function (req, res) {
db.collection(/* … */); // throws error
});
The error is:
TypeError: db.collection is not a function
I think this is because that the call of connect in the db.js is asynchronous and the require in app.js is synchronous - so it'll be undefined when I execute db.collection?? How can Mongoose be used to avoid this error?
Goal: I want to be able to call db.collection.insertOne() within my app.js
I found a somewhat related topic but it doesn't use Mongoose, so I'm confused as to how to resolve this issue: How to export an object that only becomes available in an async callback?
Answering my own question:
It'll need a mongoose model in the app.js. So instead of doing db.collection in the main app.js, I ended up routing the CRUD operations to functions defined in the controller file.
//app.js
var app = express();
var router = express.Router();
app.use(router);
require("./Routes")(router);
router.get("/xxx", xxx.get);
Here are the routes:
//routes.js - takes care of routing URLs to the functions
var XXX = require('../xxx');
module.exports = function(router){
router.get('/XXX', XXX.get)
};
Here is the actual function:
//xxx.js - functions defined here
exports.get = function (req, res) {
XXX.getAll(req.body, function(err, result){
if (!err) {
return res.send(result); // Posting all the XXX
} else {
return res.send(err); // 500 error
}
});
};

Resources