How can I test rest api using express and only JEST? - javascript

Can I use only JEST to test my rest API endpoints in express? I've searched several articles and also saw some of the questions of stackoverflow to see how can I do this. But the problem is for testing in express most of the people are using mocha/chai or supertest. But I want to use only JEST in my test. Is this possible?
this is my code so far where I want to implement this test:
index.js
const express = require('express');
const app = express();
app.post('/insert', function (req, res, next) {
const values = req.body; //name, roll
pool.query(`INSERT INTO student SET ?`, [values], (err, result) => {
if (err){
let err = new Error('Not Connected');
next(err);
} else {
res.status(201).json({ msg: `added ${result.insertId}`});
console.log(result);
}
});
});
what i've tried so far is :
index.test.js:
const express = require('express');
const app = express();
app.use('../routes');
test('Test POST, Success Scenario', async () => {
const response = await app.post('/insert')({
const values //dummy values will be insert here
});
expect(response.statusCode).toBe(200);
});
I know my test code is not correct, it's just a pseudocode I'm actually confused how I will hit the end point here

Here is the unit test solution for testing Nodejs web framework express REST API ONLY USING JEST:
index.js:
const express = require('express');
const { Pool } = require('pg');
const app = express();
const pool = new Pool();
app.post('/insert', (req, res, next) => {
const values = req.body;
pool.query(`INSERT INTO student SET ?`, [values], (err, result) => {
if (err) {
err = new Error('Not Connected');
next(err);
} else {
res.status(201).json({ msg: `added ${result.insertId}` });
console.log(result);
}
});
});
index.spec.js:
const routes = {};
jest.mock('express', () => {
const mExpress = {
post: jest.fn((path, controller) => {
routes[path] = controller;
})
};
return jest.fn(() => mExpress);
});
let queryCallback;
jest.mock('pg', () => {
const mpool = {
query: jest.fn((query, values, callback) => {
queryCallback = callback;
})
};
const mPool = jest.fn(() => mpool);
return { Pool: mPool };
});
require('./index');
const express = require('express');
const { Pool } = require('pg');
const app = express();
const pool = new Pool();
describe('insert', () => {
afterEach(() => {
jest.restoreAllMocks();
});
test('should insert data correctly', done => {
const logSpy = jest.spyOn(console, 'log');
expect(app.post).toBeCalledWith('/insert', expect.any(Function));
const mReq = { body: 1 };
const mRes = { status: jest.fn().mockReturnThis(), json: jest.fn().mockReturnThis() };
routes['/insert'](mReq, mRes);
expect(pool.query).toBeCalledWith('INSERT INTO student SET ?', [1], expect.any(Function));
const mResult = { insertId: 1 };
queryCallback(null, mResult);
expect(mRes.status).toBeCalledWith(201);
expect(mRes.status().json).toBeCalledWith({ msg: 'added 1' });
expect(logSpy).toBeCalledWith(mResult);
done();
});
test('should call error handler middleware', () => {
expect(app.post).toBeCalledWith('/insert', expect.any(Function));
const mReq = { body: 1 };
const mRes = { status: jest.fn().mockReturnThis(), json: jest.fn().mockReturnThis() };
const mNext = jest.fn();
routes['/insert'](mReq, mRes, mNext);
expect(pool.query).toBeCalledWith('INSERT INTO student SET ?', [1], expect.any(Function));
const mError = new Error('network error');
queryCallback(mError, null);
expect(mNext).toBeCalledWith(new Error('Not Connected'));
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/56635460/index.spec.js (7.391s)
insert
✓ should insert data correctly (15ms)
✓ should call error handler middleware (1ms)
console.log node_modules/jest-mock/build/index.js:860
{ insertId: 1 }
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.js | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 8.571s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/56635460

Related

Unable to stub asynchronous method inside Express server module with Chai-http and sinon js

I am trying to stub asynchronous methods using sinon js when using with chai-http
Please find the code below
index.js
const { getConfig, getConn } = require('./service/service');
const express = require('express');
const app = express();
let getConfPromise = getConfig();
let getConnPromise = getConfPromise.then(conf => getConn(conf));
Promise.all([getConfPromise, getConnPromise])
.then(([config, conn]) => {
console.log('config: ', config);
console.log('conn: ', conn);
app.get('/test', (req, res) => {
res.send(conn);
});
const port = 3000;
app.listen(3000, () => console.log(`Listening on port ${port}...`));
}).catch(err => {
console.error("Things did not go well: ", err)
})
module.exports = app;
service.js
const getConfig = () => {
return new Promise((resolve, reject) => {
resolve({
server: {
port: 8080
},
status: "from service"
})
})
}
const getConn = (conf) => {
return new Promise((resolve, reject) => {
try {
console.log('conf: ', conf);
let confLocal = JSON.parse(JSON.stringify(conf));
confLocal.connStatus = 'Connection is up';
resolve(confLocal)
} catch (error) {
console.log("Did not get value: ",error);
}
})
}
module.exports = {
getConfig,
getConn
}
Please find the test code below
let mocha = require('mocha');
let sinon = require('sinon')
let chai = require('chai');
let chaiHttp = require('chai-http');
let service = require('../src/service/service');
let app = require('../src/index');
const { after } = require('mocha');
chai.use(chaiHttp);
describe('Index test', () => {
it('test1', (done) => {
sinon.stub(service, 'getConfig')
.resolves({
server: {
port: 7777
},
status: "from test"
})
sinon.stub(service, 'getConn')
.resolves({
server: {
port: 7777
},
status: "from test",
connStatus: 'Connection is up from test'
})
chai.request(app)
.get('/test')
.end((err, res) => {
console.log('Chai HTTP err: ', err);
if (err) done(err);
chai.assert.equal(res.status, 200, 'Response status is incorrect')
console.log('Test case: ',res.body);
done();
});
})
})
When running the test cases, I am not getting the stubbed response, but I am getting the actual object from the service.js.
How to correctly stub? Please help.
Thanks in advance

How do I unit test an API mocking PrismaClient when the API is written in CommonJS?

I am testing a nodejs express API with a PG db wrapped with Prisma ORM
I configured a testing singleton instance as described by Prisma docs
Since the API is implemented in CommonJS and not TS, I had to make some changes as described in this beautiful page.
Here is a synthesis of what I did, will try to make it short, so it's easier to read
orgs.js (A GET route served by the mock server later on ...)
const Router = require('express-promise-router')
const router = new Router()
const PrismaPool = require('../db/PrismaPool');
module.exports = router
router.get('/assessments', async (req, res) => {
try{
const prisma = PrismaPool.getInstance();
const data = await prisma.org.findUnique({
select:{
assessments:true,
},
where: {
id: res.locals.orgId,
},
})
res.send(data)
}
catch(err){
handleError(err, "[GET]/orgs/assessments", 400, req, res)
}
})
PrismaPool.js (A wrapper to access the unique prisma client instance)
const prisma = require('./PrismaClientInstance').default
class PrismaPool {
constructor() {
throw new Error('Use PrismaPool.getInstance()');
}
static getInstance() {
return prisma
}
}
module.exports = PrismaPool;
PrismaClientInstance.js The unique instance of PrismaClient class. This is the tricky part stitching between the CommonJS world and the TS world.
'use strict';
exports.__esModule = true;
const { PrismaClient } = require('#prisma/client')
const prisma = new PrismaClient()
exports['default'] = prisma;
All this configuration works GREAT at runtime, now, when wrapping it with JEST in unit tests, things go south quickly ...
mock_server.js (a simplified server to expose the orgs API above)
const http = require('http');
const express = require('express');
var orgsRouter = require('../orgs');
const app = express();
app.use('/orgs', orgsRouter);
const port = 3011
app.set('port', port);
const server = http.createServer(app);
function onError(error) {
// herror handling
}
function onListening() {
// some debug messages
}
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
module.exports = server
PrismaSingletonForTesting.ts (A jest deep mock of the PrismaClient instance)
import { PrismaClient } from '#prisma/client'
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended'
import prisma from './PrismaClientInstance'
jest.mock('./PrismaClientInstance', () => ({
__esModule: true,
default: mockDeep<PrismaClient>()
}))
beforeEach(() => {
mockReset(prismaMock)
})
export const prismaMock = prisma as unknown as DeepMockProxy<PrismaClient>
orgs.test.js (The tests of the orgs API)
const Request = require("request")
const { prismaMock } = require('../../db/PrismaSingletonForTesting')
const TEST_ORGID = 1
describe('egrest server', () => {
let server
beforeAll(() => {
server = require('./test_server')
})
afterAll(() => {
server.close()
})
describe('assessments', () => {
let data = {}
beforeAll(() => {
const testorg = {
id: TEST_ORGID,
name: 'jestest',
admin:33,
avail_tests: 1234
}
prismaMock.org.findUnique.mockResolvedValue(testorg)
})
it(`read remaining assessments for org ${TEST_ORGID}`, (done) => {
Request.get("http://localhost:3011/orgs/assessments", (error, response, body) => {
data.status = response.statusCode
data.body = body
data.error = error
console.dir(body)
done()
})
})
})
})
I also configured .jest.config with the required line setupFilesAfterEnv: ['./db/PrismaSingletonForTesting.ts']
When I run this test, I get data=undefined in orgs.js, even-though I mocked prisma.org.findUnique by doing prismaMock.org.findUnique.mockResolvedValue(testorg) as described by prisma docs.
Any help would be appreciated.

How to dynamically pass API data to MongoDB database

I am building a boat visualizer using AISHub APIs. After inquiring the APIs I am able to obtain a json file with the vessels and filter with only the vessel I am interested in, and inject them into a table on the webpage. The API gives the following fileds:
[NAME, MMSI, LONGITUDE, LATITUDE]. I can correctly connect to MongoDB as I npm start.
The problem: I would like to send also this data to a collection in to MongoDB database every 5 minutes. I tried many different way to do that, but none of them seems to be working.
It was very difficult to think which parts of the code to pass and which not, but below I believe there are enough information to understand what the problem is:
app.js is where I set the MongoDB connection
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var mongoose = require('mongoose');
const bodyParser = require('body-parser');
const vesselController = require('./controllers/VesselController');
require('./config/keys');
var app = express();
app.use(cors());
app.options('*', cors());
// DB Config
const db = require('./config/keys').MongoURI;
const options = {
useNewUrlParser: true,
reconnectTries: Number.MAX_VALUE,
poolSize: 10
};
mongoose
.connect(db, options)
.then(() => console.log('MongoDB Connection established'))
.catch((err) => console.log('Error connecting MongoDB database due to: ', err));
const PORT = process.env.PORT || 3000;
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
app.use(bodyParser.json({ limit: '50mb' }));
app.use(cors());
app.route('/vessels/all').get(vesselController.getBaseAll);
app.route('vessels/:id/track').get(vesselController.getCurrent);
app.route('/vessels').get(vesselController.getHistory);
app.listen(PORT, console.log(`Server started on port ${PORT}`));
module.exports = app;
index.js is where I am caching the data (and where I think the app should send data to MongoDB)
var express = require('express');
var router = express.Router();
var axios = require('axios');
const NodeCache = require('node-cache');
const myCache = new NodeCache();
let hitCount = 0;
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
const mmsiOfInterest = [
'367029520',
'366909730',
'367128570'
];
const shipNamesOfInterest = [
'MICHIGAN',
'JP BOISSEAU',
'DELAWARE BAY'
];
router.get('/hello', async function(req, res, next) {
const allData = myCache.get('allData');
if (!allData) {
hitCount++;
console.log(`hit ${hitCount} number of times`);
const { data } = await axios.get(
'http://data.aishub.net/ws.php?username=KEY&format=1&output=json&compress=0&latmin=11.42&latmax=58.20&lonmin=-134.09&lonmax=-52.62'
);
const [ metaData, ships ] = data;
console.log(data);
const shipsOfInterest = ships.filter(
(ship) => mmsiOfInterest.includes(ship.MMSI) || shipNamesOfInterest.includes(ship.NAME)
);
myCache.set('allData', shipsOfInterest, 70);
res.send(data);
return;
}
res.send(allData);
});
module.exports = router;
VesselController.js: is where I have the functions for getting different information such as currect vessel, all vessels, history of vessels
module.exports.getBaseAll = (req, res) => {
Promise.all([
Compnanies.find(),
Vessels.find(),
Positions.aggregate([
{
$sort: {
date: -1
}
},
{
$group: {
_id: '$callsign',
details: {
$push: '$$ROOT'
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayElemAt: [ '$details', 0 ]
}
}
}
])
])
.then(([ companies, vessels, positions ]) => {
// apply vessels detail table as join:
positions.forEach((pos) => {
vessels.forEach((ves) => {
if (pos.callsign == ves.callsign) {
p._detail = ves;
}
});
companies.forEach((com) => {
if (p._detail.company == com.number) {
p._detail = com;
}
});
});
res.status(200).json(positions);
})
.catch((err) => {
return res.status(500).send(err);
});
console.log(vesselController.getBaseAll);
};
module.exports.getHistory = (req, res) => {
var id = req.param.id;
Positions.find(
{
callsign: id,
date: {
$gte: new Date(Date.now() - 1000 * 60 * 60 * 24)
}
},
(err, task) => {
if (err) {
return res.status(500).send(err);
}
res.status(200).json(task);
}
);
console.log(vesselController.getHistory);
};
module.exports.getCurrent = (req, res) => {
var currentPos = Positions.find({
date: {
$gte: new Date(Date.now() - 1000 * 60 * 60)
}
});
currentPos.exec((err, task) => {
if (err) {
return res.status(500).send(err);
}
res.status(200).json(task);
});
console.log(vesselController.getCurrent);
};
In LatitudeLongitude.js I set the proper format according to MongoDB documentation
const mongoose = require('mongoose');
const LatitudeLongitudeSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
mmsi: {
type: Number,
required: false
},
longitude: {
type: Number,
required: false
},
latitude: {
type: Number,
required: false
}
});
const LatitudeLongitude = mongoose.model('LatitudeLongitude', LatitudeLongitudeSchema);
module.exports = LatitudeLongitude;
users.js is where I set the router.post
var express = require('express');
var router = express.Router();
const LatitudeLongitude = require('../models/LatitudeLongitude');
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
router.post('/vessles/map', function(req, res) {
const { name, mmsi, longitude, latitude } = req.body;
let errors = [];
// Check required fields
if (!name || !mmsi || !longitude || !latitude) {
errors.push({ msg: 'No data received' });
}
if (
LatitudeLongitude.findOne({ mmsi: mmsi }).then((pos) => {
if (pos) {
// vessel exists
const newVessel = new Vessles({
name,
mmsi,
longitude,
latitude
});
}
})
);
});
module.exports = router;
Below the collection set inside MongoDB, which is clearly empty:
How can I successfully pass information from the API I mentioned above, into my MongoDB?
What I have donbe so far:
I tried many different ways to pass data from the API to MongoDB every 5 minutes. Among the dirfferent approaches, the one I included in this post are, I think, the most effective, however there is something missing I can't catch/understand.
I believe that the file index.js should be the one that is in charge of taking care of doing that, as I pre-set all the check in that file. However I am now confused whether that is the right location or not.
the reasons why you see the routes as app.route('/vessels/all').get(vesselController.getBaseAll); in the app.js file is because I will use that as end point api check with Postman once everything is working. I figured I would leave it there so you see how I designed the process.
Thanks for shedding light on this matter.
In my opinion, you should first test my approach by attaching following code to your server.js file. then refactor it
setInterval( () => {
fetch('http://your/get/route').
then( updateDB() ).
catch(doWhateverYouWant());
},3000
);

how can i use db.collection() outside of mongodb.connect() [duplicate]

I've been reading and reading and still am confused on what is the best way to share the same database (MongoDb) connection across whole NodeJs app. As I understand connection should be open when app starts and reused between modules. My current idea of the best way is that server.js (main file where everything starts) connects to database and creates object variable that is passed to modules. Once connected this variable will be used by modules code as necessary and this connection stays open. E.g.:
var MongoClient = require('mongodb').MongoClient;
var mongo = {}; // this is passed to modules and code
MongoClient.connect("mongodb://localhost:27017/marankings", function(err, db) {
if (!err) {
console.log("We are connected");
// these tables will be passed to modules as part of mongo object
mongo.dbUsers = db.collection("users");
mongo.dbDisciplines = db.collection("disciplines");
console.log("aaa " + users.getAll()); // displays object and this can be used from inside modules
} else
console.log(err);
});
var users = new(require("./models/user"))(app, mongo);
console.log("bbb " + users.getAll()); // not connected at the very first time so displays undefined
then another module models/user looks like that:
Users = function(app, mongo) {
Users.prototype.addUser = function() {
console.log("add user");
}
Users.prototype.getAll = function() {
return "all users " + mongo.dbUsers;
}
}
module.exports = Users;
Now I have horrible feeling that this is wrong so are there any obvious problems with this approach and if so how to make it better?
You can create a mongoUtil.js module that has functions to both connect to mongo and return a mongo db instance:
const MongoClient = require( 'mongodb' ).MongoClient;
const url = "mongodb://localhost:27017";
var _db;
module.exports = {
connectToServer: function( callback ) {
MongoClient.connect( url, { useNewUrlParser: true }, function( err, client ) {
_db = client.db('test_db');
return callback( err );
} );
},
getDb: function() {
return _db;
}
};
To use it, you would do this in your app.js:
var mongoUtil = require( 'mongoUtil' );
mongoUtil.connectToServer( function( err, client ) {
if (err) console.log(err);
// start the rest of your app here
} );
And then, when you need access to mongo somewhere else, like in another .js file, you can do this:
var mongoUtil = require( 'mongoUtil' );
var db = mongoUtil.getDb();
db.collection( 'users' ).find();
The reason this works is that in node, when modules are require'd, they only get loaded/sourced once so you will only ever end up with one instance of _db and mongoUtil.getDb() will always return that same instance.
Note, code not tested.
There are many ways this could be tweaked to accept configuration objects in places, but overall it's similar to how you have your code laid out, albeit with more modern JS syntax. Could easily be rewritten to prototypes and callbacks, if that's your requirement.
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;
}
Here's how I do it with contemporary syntax, based on go-oleg's example. Mine is tested and functional.
I put some comments in the code.
./db/mongodb.js
const MongoClient = require('mongodb').MongoClient
const uri = 'mongodb://user:password#localhost:27017/dbName'
let _db
const connectDB = async (callback) => {
try {
MongoClient.connect(uri, (err, db) => {
_db = db
return callback(err)
})
} catch (e) {
throw e
}
}
const getDB = () => _db
const disconnectDB = () => _db.close()
module.exports = { connectDB, getDB, disconnectDB }
./index.js
// Load MongoDB utils
const MongoDB = require('./db/mongodb')
// Load queries & mutations
const Users = require('./users')
// Improve debugging
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at:', p, 'reason:', reason)
})
const seedUser = {
name: 'Bob Alice',
email: 'test#dev.null',
bonusSetting: true
}
// Connect to MongoDB and put server instantiation code inside
// because we start the connection first
MongoDB.connectDB(async (err) => {
if (err) throw err
// Load db & collections
const db = MongoDB.getDB()
const users = db.collection('users')
try {
// Run some sample operations
// and pass users collection into models
const newUser = await Users.createUser(users, seedUser)
const listUsers = await Users.getUsers(users)
const findUser = await Users.findUserById(users, newUser._id)
console.log('CREATE USER')
console.log(newUser)
console.log('GET ALL USERS')
console.log(listUsers)
console.log('FIND USER')
console.log(findUser)
} catch (e) {
throw e
}
const desired = true
if (desired) {
// Use disconnectDB for clean driver disconnect
MongoDB.disconnectDB()
process.exit(0)
}
// Server code anywhere above here inside connectDB()
})
./users/index.js
const ObjectID = require('mongodb').ObjectID
// Notice how the users collection is passed into the models
const createUser = async (users, user) => {
try {
const results = await users.insertOne(user)
return results.ops[0]
} catch (e) {
throw e
}
}
const getUsers = async (users) => {
try {
const results = await users.find().toArray()
return results
} catch (e) {
throw e
}
}
const findUserById = async (users, id) => {
try {
if (!ObjectID.isValid(id)) throw 'Invalid MongoDB ID.'
const results = await users.findOne(ObjectID(id))
return results
} catch (e) {
throw e
}
}
// Export garbage as methods on the Users object
module.exports = { createUser, getUsers, findUserById }
If you are using Express, then you can use mongo-express-req module that allows you to get db connection in request object.
Install
npm install --save mongo-express-req
server.js
var app = require('express')();
var mongoExpressReq = require('mongo-express-req');
app.use(mongoExpressReq('mongodb://localhost/test'));
routes/users.js
app.get('/', function (req, res, next) {
req.db // => Db object
});
Note: mongo-express-req is fork of not maintained express-mongo-db.
A tested solution based on the accepted answer:
mongodbutil.js:
var MongoClient = require( 'mongodb' ).MongoClient;
var _db;
module.exports = {
connectToServer: function( callback ) {
MongoClient.connect( "<connection string>", function( err, client ) {
_db = client.db("<database name>");
return callback( err );
} );
},
getDb: function() {
return _db;
}
};
app.js:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
var mongodbutil = require( './mongodbutil' );
mongodbutil.connectToServer( function( err ) {
//app goes online once this callback occurs
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var companiesRouter = require('./routes/companies');
var activitiesRouter = require('./routes/activities');
var registerRouter = require('./routes/register');
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/companies', companiesRouter);
app.use('/activities', activitiesRouter);
app.use('/register', registerRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
res.render('error');
});
//end of calback
});
module.exports = app;
activities.js -- a route:
var express = require('express');
var router = express.Router();
var mongodbutil = require( '../mongodbutil' );
var db = mongodbutil.getDb();
router.get('/', (req, res, next) => {
db.collection('activities').find().toArray((err, results) => {
if (err) return console.log(err)
res.render('activities', {activities: results, title: "Activities"})
});
});
router.post('/', (req, res) => {
db.collection('activities').save(req.body, (err, result) => {
if (err) return console.log(err)
res.redirect('/activities')
})
});
module.exports = router;
Here is my setup in 2020:
./utils/database.js
const { MongoClient } = require('mongodb');
class Mongo {
constructor () {
this.client = new MongoClient("mongodb://127.0.0.1:27017/my-app", {
useNewUrlParser: true,
useUnifiedTopology: true
});
}
async main () {
await this.client.connect();
console.log('Connected to MongoDB');
this.db = this.client.db();
}
}
module.exports = new Mongo();
/app.js
const mongo = require('./utils/database');
const express = require('express');
const app = express();
const boot = async () => {
await mongo.main();
app.listen(3000);
};
boot();
go-oleg is basically right, but in these days you (probably) dont want use "mongodb" itself, rather use some framework, which will do a lot of "dirty work" for you.
For example, mongoose is one of the most common. This is what we have in our initial server.js file :
const mongoose = require('mongoose');
const options = {server: {socketOptions: {keepAlive: 1}}};
mongoose.connect(config.db, options);
This is everything what is needed to set it up. Now use this anywhere in your code
const mongoose = require('mongoose');
And you get that instance you set up with mongoose.connect
I´m late to the party, but hopefully this answer will help someone, this is a functional code:
db.js
const MongoClient = require("mongodb").MongoClient
const urlMongo = "mongodb://localhost:27017"
var db;
function connectToServer( callback ) {
MongoClient.connect(urlMongo, { useUnifiedTopology: true , useNewUrlParser: true }, function( err, client ) {
db = client.db('auth');
return callback( err );
})
}
function getDb() {
return db
}
module.exports = {connectToServer, getDb}
We export one function to connect to the mongo and another to get de instanceof the connection.
app.js
const express = require('express')
const app = express()
const mongo = require('./db.js');
mongo.connectToServer( function( err) {
if (err) console.log(err);
const auth = require('./modulos')
app.post('/login', (req, res) => { auth.login(req, res)})
app.listen(3000, function () { console.log('Corriendo en puerto 3000')})
});
We must do the require of the auth module after we initiallize the connection, otherwise the getDb function will return undefined.
module.js
const db = require('../db.js').getDb()
const usuariosCollection = db.collection('usuarios')
function login(req, res){
usuariosCollection.find({ 'username': 'Fran' }).toArray(function (err, doc) {
...
})
}
As this is tagged with Express, I thought I would mention that Express has a built in feature to share data between routes. There is an object called app.locals. We can attach properties to it and access it from inside our routes. You simply instantiate your mongo connection in your app.js file.
var app = express();
MongoClient.connect('mongodb://localhost:27017/')
.then(client =>{
const db = client.db('your-db');
const collection = db.collection('your-collection');
app.locals.collection = collection;
});
// view engine setup
app.set('views', path.join(__dirname, 'views'));
This database connection can now be accessed within your routes as below without the need for creating and requiring additional modules.
app.get('/', (req, res) => {
const collection = req.app.locals.collection;
collection.find({}).toArray()
.then(response => res.status(200).json(response))
.catch(error => console.error(error));
});
This method ensures that you have a database connection open for the duration of your app unless you choose to close it at any time. It's easily accessible with req.app.locals.your-collection and doesn't require additional modules.
Initialize the connection as a promise:
const MongoClient = require('mongodb').MongoClient
const uri = 'mongodb://...'
const client = new MongoClient(uri)
const connection = client.connect() // initialized connection
And then call the connection whenever you wish you perform an action on the database:
// if I want to insert into the database...
const connect = connection
connect.then(() => {
const doc = { id: 3 }
const db = client.db('database_name')
const coll = db.collection('collection_name')
coll.insertOne(doc, (err, result) => {
if(err) throw err
})
})
Here's a suggestion using TypeScript and ES6 features and syntax:
db.ts
import { Db, MongoClient } from 'mongodb'
let client: MongoClient
let db: Db
const connectToDatabase = async () => {
client = new MongoClient('databaseURI')
await client.connect()
db = client.db('dbname')
}
export {
connectToDatabase,
client,
db,
}
index.ts
import express from 'express'
import { someRouter } from './routes/someRoute'
import { connectToDatabase } from './db'
connectToDatabase().then(() => {
const app = express()
app.use('/someRoute', someRouter)
const port = process.env.PORT || 5000
app.listen(port, () => {
console.log(`Server is listening on port ${port}`)
})
})
routes/someRoute.ts
import express from 'express'
import { db } from '../db'
const someRouter = express.Router()
someRouter.route('/')
.get(async (req, res) => {
const results = await db.collection('collectionName').find().toArray()
return res.send(results)
})
export {
someRouter,
}
we can create a dbconnection file like dbconnection.js
const MongoClient = require('mongodb').MongoClient
const mongo_url = process.env.MONGO_URL;
module.exports = {
connect: async function(callback) {
var connection;
await new Promise((resolve, reject) => {
MongoClient.connect(mongo_url, {
useNewUrlParser: true
}, (err, database) => {
if (err)
reject();
else {
connection = database;
resolve();
}
});
});
return connection;
}
};
and then use this file in the your app like
var connection = require('../dbconnection');
and then use like this inside your async function
db = await connection.connect();
hope this will work
I find this works well :)
mongoUtil.ts
import { MongoClient } from 'mongodb';
const uri =
'MONGOSTRING';
let connPoolPromise: any = null;
const mongoPoolPromise = () => {
if (connPoolPromise) return connPoolPromise;
connPoolPromise = new Promise((resolve, reject) => {
const conn = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
if (conn.isConnected()) {
return resolve(conn);
} else {
conn
.connect()
.then(() => {
return resolve(conn.db('DATABASENAME'));
})
.catch(err => {
console.log(err);
reject(err);
});
}
});
return connPoolPromise;
};
export = {
mongoPoolPromise,
};
anyFile.ts
const { mongoPoolPromise } = require('./mongoUtil');
async function getProducts() {
const db = await mongoPoolPromise();
const data = await db
.collection('myCollection')
.find({})
.toArray();
console.log(data);
return data;
}
export { getProducts };
I'm a bit late for this, but I'll add my solution too. It's a much noobier approach compared to the answers here.
Anyway if you are using MongoDB version 4.0 and Node.js 3.0 (or higher versions) you can use isConnected() function from the MongoClient.
const MongoClient = require('mongodb').MongoClient;
const uri = "<your connection url>";
const client = new MongoClient(uri, { useNewUrlParser: true });
if (client.isConnected()) {
execute();
} else {
client.connect().then(function () {
execute();
});
}
function execute() {
// Do anything here
// Ex: client.db("mydb").collection("mycol");
}
This worked fine for me. Hope it helps.
Based on accepted answers, I use a simple approach. But use this only if you want to use db inside function which will be executed after some time. For ex: In express route functions, it is the easiest approach you can take.
mongo.js
const MongoClient = require("mongodb").MongoClient
var db
const connectDb = (callback) => {
if (db) return callback()
MongoClient.connect( uri, {ops},
(err, database) => {
if (err) return console.log(err)
db = database.db("dbName")
console.log("Database Connected")
callback()
}
)
}
const getDb = (collectionToGet) => {
return db.collection(collectionToGet)
}
module.exports = {
connectDb,
getDb,
}
Now, in other files where you want the db object,
user.js
const { connectDb, getDb } = require('mongo.js')
var db // store db object in this object
connectDb(() => ( db = getDb("user") ))
app.get('/', (req, res) => {
// do something with req
db.insert({})
// do something with res
}
If you opt for using mongoose in your application edit your app.js file with the following snippet
app.js
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/Your_Data_Base_Name', {useNewUrlParser:true})
.then((res) => {
console.log(' ########### Connected to mongDB ###########');
})
.catch((err) => {
console.log('Error in connecting to mongoDb' + err);
});`
Next Step:
Define Models for your application require them and perform CRUD operation directly for example
blogSchema.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const blogSchema = new Schema({
_id : mongoose.Schema.Types.ObjectId,
title : {
type : 'String',
unique : true,
required : true
},
description : String,
comments : [{type : mongoose.Schema.Types.ObjectId, ref: 'Comment'}]
});
module.exports = mongoose.model('Blog', blogSchema);
Usage
createBlog.js
const Blog = require('../models/blogSchema');
exports.createBlog = (req, res, next) => {
const blog = new Blog({
_id : new mongoose.Types.ObjectId,
title : req.body.title,
description : req.body.description,
});
blog.save((err, blog) => {
if(err){
console.log('Server Error save fun failed');
res.status(500).json({
msg : "Error occured on server side",
err : err
})
}else{
//do something....
}
U don't need to connect to mogoDB always ....
var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://localhost:27017/';
var Pro1;
module.exports = {
DBConnection:async function()
{
Pro1 = new Promise(async function(resolve,reject){
MongoClient.connect(url, { useNewUrlParser: true },function(err, db) {
if (err) throw err;
resolve(db);
});
});
},
getDB:async function(Blockchain , Context)
{
bc = Blockchain;
contx = Context;
Pro1.then(function(_db)
{
var dbo = _db.db('dbname');
dbo.collection('collectionname').find().limit(1).skip(0).toArray(function(err,result) {
if (err) throw err;
console.log(result);
});
});
},
closeDB:async function()
{
Pro1.then(function(_db){
_db.close();
});
}
};
const express = require('express')
const server = express()
const mongoClient = require('./MongoDB.js').client
const port = 3000
;(async () => {
await mongoClient.connect()
server.listen(port, () => console.log(`Server is listening on port ${port}!`))
})().catch(console.error)
You can use the Singleton Design Pattern to achive cross file usage of your MongoDB connection.
Init.mjs
/* ################ Controller ################ */
import ctrlLib from '../../controller/lib.mjs';
/* ################ MongoDB ################ */
import mongodb from 'mongodb';
/* ################ Logs ################ */
import log from 'fancy-log';
import chalk from 'chalk';
/** Init MongoDB connection */
export class Init {
/**
* Check if its first time usage of this class.
If true set class instance to this that we always get same instance.
* Then get MongoDB details from config.yml and set as global.
* In the last step we return the class instance.
*/
constructor() {
if (Init.instance == null) Init.instance = this;
const config = ctrlLib.getConfig();
this.MongoURL = config.MongoDB.url;
this.MongoName = config.MongoDB.dbname;
({MongoClient: this.MongoClient} = mongodb);
return Init.instance;
}; // constructor(){
/** Connect to Database and return connection */
async connect() {
try {
const client = await this.MongoClient.connect(
this.MongoURL, {useNewUrlParser: true, useUnifiedTopology: true},
);
this.connection = {'db': client.db(this.MongoName), 'client': client};
return this.connection;
} // try {
catch (e) {
log( `${chalk.red.bold('❌ ERROR')} while try to connect to MongoDB DB
${chalk.white.bold('Error:\n')} ${e}` );
} // catch (e) {
}; // async connect() {
/**
* Return connection for cross file usage
* #return {object}
*/
getConnection() {return this.connection;};
}; // export class Init {
app.mjs
Make sure to 1x time create your MongoDB connection anywhere inside of your project that you can use it later in other files.
/* ################ Services ################ */
import {Init} from './Init.mjs';
(async ()=>{
await new Init().connect();
})().catch(e=>{log('app.mjs - Catch error: ' + e);});
anyOtherFile.mjs
/* ################ Services ################ */
import {Init} from './Init.mjs';
/** Subclass of Search which contains lib functions */
class Lib {
/**
* Find data by using search query and return result.
* #param {string} collection - Name of collection
* #param {object} query - Search query
*/
async findOne(collection, query) {
const connection = new Init().getConnection();
return await connection.db.collection(collection).findOne(query);
}; // async findOne() {
}; // class Lib {
Updated for 2022 MongoClient new updates
MongoUtil.js (For database connection and return database instance)
const { MongoClient } = require('mongodb');
const uri = "your database connection url";
var _db;
module.exports = {
connectToServer: function (callback) {
MongoClient.connect(uri, { useNewUrlParser: true }, function (err, client) {
_db = client.db('testdb');
return callback(err);
});
},
getDb: function () { //this returns database instance
return _db;
}
};
app.js (You can use in any routes or js by importing mongoUtil)
var mongoUtil = require('./mongoUtil');
mongoUtil.connectToServer(function (err, client) {
if (err) console.log(err);
console.log(`server is running`);
insertData(); //or do functions and db queries in any js
});
async function insertData() { //Functions should be async
var database = mongoUtil.getDb();
var movies = database.collection('movies');
const doc = {
title: "Movie title",
content: "Movie content",
}
const result = await movies.insertOne(doc);
console.log(`A document was inserted with the _id: ${result.insertedId}`);
}
I tried #go-oleg answer and it works pretty well. Inside getDb() , I make sure _db must be defined. And if not defined, I call the connectToServer() so that it will get defined again. After this I don't have to call connectToServer() in the app.js which makes my code clean.
let getDb = async() => {
if(_db) {
return _db
} else {
_db = await connectToServer()
return _db
}
}
And then, I simply call getDb() everywhere. Also, What I observed, It takes about 64ms on first call. After first call it takes about, 2-6ms everytime.
I answered here because i have less reputation to comment.
all after long effort my working by this operational method:
Please follow this link this is also good solution:
https://mrvautin.com/re-use-mongodb-database-connection-in-routes/
Folks, in 2022 there is no need for reconnection logic, the Node.js MongoDB driver handles this all for you (v4+).
You can simply connect as described in the official docs. Put this in a db.js file, then you can import client or db anywhere in your app:
import { MongoClient, ServerApiVersion } from 'mongodb'
const uri = `mongodb+srv://...`;
// Create a new MongoClient
export const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
export const db = client.db('your_db');
When making queries, use try/catch to capture potential connection errors.
try {
const res = await db.collection("testdata").insertOne({test: Math.random()});
console.log('Inserted', res);
} catch(e) {
console.error('MONGO ERROR', e);
}
AFAIK, the Mongo driver will keep retrying forever if the connection is lost.
Try it yourself: put the above code in a setInterval and turn off your internet connection for a while then turn it back on, Mongo will automatically reconnect, even after hours of downtime. It will even submit some queries that were made while the connection was down.
Updated for 2023
MongoDB Connection
const { MongoClient, ServerApiVersion } = require('mongodb');
const dbconfig = require('./config');
module.exports = {
client: client = new MongoClient(dbconfig.uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 }),
connectToDB: async () => {
try {
await client.connect()
console.log('connected!')
} catch (err) {
console.log('Err', err)
}
}
}
In your controller
const db = require('../config/mongodb.connection')
const hha_data = db.client.db('hha-sit').collection('hnh-data')
exports.addNewCustomer = async (req, res) => {
try {
await db.connectToDB()
let result = await hha_data.findOne({}, { account_id: 'MDB829001337' })
console.log('result', result)
} catch (err) {
console.error('Connection Error !', err)
} finally {
await db.client.close()
}
res.send('Hi')
}
Please feel free to revise it if you have any suggestions. :)
This approach is correct, and it can be improved in the following ways:
1.Wrap the MongoClient connect function inside a module and export it as a singleton object to be used across your application. This way, you can make sure only one connection is established to the MongoDB server and is reused across your modules.
2.Add error handling to your code to handle potential issues like a connection failure.
3.Use the MongoDB native driver's connection pooling feature instead of maintaining a single connection throughout the application's lifetime, as this can lead to resource exhaustion and poor performance.
This is an example of a improved implementation:
const MongoClient = require('mongodb').MongoClient;
let _db;
const connectToDb = async (url) => {
if (db) return db;
let client;
try {
client = await MongoClient.connect(url, {
useNewUrlParser: true,
useUnifiedTopology: true
});
_db = client.db();
} catch (err) {
console.error('Error connecting to MongoDB: ', err);
process.exit(1);
}
return _db;
};
module.exports = connectToDb;
const connectToDb = require('./db');
const userModule = async (app) => {
const db = await connectToDb('mongodb://localhost:27017/marankings');
return {
addUser: () => console.log('add user'),
getAll: () => 'all users'
};
};
module.exports = userModule;
const userModule = require('./userModule');
(async () => {
const users = await userModule();
console.log(users.getAll());
})();

How to write test with Jest on call nested in a try { } catch { }

I'm setting up some tests with Jest on a Node.js express server, but I can't figure how to test calls nested in try/catch blocks.
Here is a part of my server.js :
const start = async () => {
try {
if (process.env.NODE_ENV) {
await db.sync({ force: false });
}
...
app.get("/", (request, response) => {
response.send("Please feel free to use our api with /api");
});
...
app.listen(port, () => {
console.log(`Server running on port ${port}`);
return app;
});
} catch (err) {
console.log(err.message);
}
};
export default new Promise ( async () => {
return await start();
});
Here I would like to test what is the app.listen() status code, but I'm still not that familiar with testing.
Any suggestion ?
Here is the test I wrote :
const request = require('supertest');
const app = require('../server');
describe('Test the root path', ()=>{
test("GET method returns status code 200", ()=>{
request(app).get('/').then( response =>{
expect(response.statusCode).toBe(200);
});
});
})
I assume that app is not what I expected because Jest tells me that app.address is not a function, so my export default new Promise is not the right solution.
Sorry if this seems messy, hope you can help !
Here is the solution:
server.js:
const express = require('express');
const app = express();
const port = 3000;
const db = {
async sync(options) {},
};
const start = async () => {
try {
if (process.env.NODE_ENV) {
await db.sync({ force: false });
}
app.get('/', (request, response) => {
response.send('Please feel free to use our api with /api');
});
return app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
} catch (err) {
console.log(err.message);
}
};
export default start;
server.test.js:
import request from 'supertest';
import start from './server';
describe('Test the root path', () => {
let server;
beforeAll(async () => {
server = await start();
});
afterAll((done) => {
server.close(done);
});
test('GET method returns status code 200', () => {
expect.assertions(1);
return request(server)
.get('/')
.then((response) => {
expect(response.statusCode).toBe(200);
});
});
});
Integration test result with coverage report:
PASS src/stackoverflow/55986832/server.test.js
Test the root path
✓ GET method returns status code 200 (45ms)
console.log src/stackoverflow/55986832/server.js:3342
Server running on port 3000
-----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files | 92.31 | 50 | 100 | 92.31 | |
server.js | 92.31 | 50 | 100 | 92.31 | 22 |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.075s, estimated 10s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/55986832

Categories

Resources