# Exporting functions that manipulate mongoose models

I have the following code in a file called database.js which will handle all the database operations for my small application. For some background, this application fetches stock data from an API and stores it in a local database so that future requests can pull the data from the database instead of making a network request (basically a rudimentary cache).

const DATABASE = 'mongodb://localhost/stockcli'

const _ = require('lodash')
const Promise = require('bluebird')
const moment = require('moment')
const mongoose = require('mongoose')
const Schema = mongoose.Schema
mongoose.Promise = Promise
mongoose.connect(DATABASE, { useMongoClient: true })

/**
* This schema will be part of a mongoose model that contains each entry
* in the stock data collections.
* @type {Schema}
*/
const stockDataSchema = new Schema({
timestamp: Date,
ticker: String,
open: Number,
high: Number,
low: Number,
close: Number,
volume: Number
})
stockDataSchema.index({ ticker: 1 })
stockDataSchema.index({ timestamp: 1 }, { unique: true })
const dataModels = {
'1min': mongoose.model('1min', stockDataSchema),
'5min': mongoose.model('5min', stockDataSchema),
'15min': mongoose.model('15min', stockDataSchema),
'30min': mongoose.model('30min', stockDataSchema),
'60min': mongoose.model('60min', stockDataSchema),
day: mongoose.model('day', stockDataSchema),
week: mongoose.model('week', stockDataSchema),
month: mongoose.model('month', stockDataSchema)
}

/**
* This schema will be part of a set of models that contain the last time
* an interval type was updated.
* @type {Schema}
*/
ticker: String,
lastUpdateTime: Date
})
stockUpdateSchema.index({ ticker: 1 }, { unique: true })
const lastUpdateModels = {
}

/**
* For a given data interval and company ticker, this function determines
* the last time that we updated that company's stock data in our database.
* It wraps the timestamp, regardless of validity, with a moment.js object.
* @param {string} interval The data point interval
* @param {string} ticker The company stock ticker
* @return {Promise}
*/
const getLastUpdateTime = (interval, ticker) => {
return lastUpdateModels[interval].findOne({ ticker }).exec().then(data => {
return moment(data === null ? null : data.timestamp)
})
}

/**
* This function updates the entry in the database containing the last time that
* a company's stock data was updated.
* @param {string} interval The data point interval
* @param {string} ticker The company stock ticker
* @return {Promise}
*/
const updateLastUpdateTime = (interval, ticker) => {
return lastUpdateModels[interval].findOneAndUpdate({ ticker }, {
lastUpdateTime: new Date()
}, {
upsert: true
}).exec()
}

/**
* Given a data interval, a company ticker, a start date, and an end date,
* this function will return all pertinent stock data stored in the database.
* @param {string} interval The time interval between the data points
* @param {string} ticker The company stock ticker
* @param {Object} start The moment.js object representing the start date
* @param {Object} end The moment.js object representing the end date
* @return {Promise}
*/
const getStockData = (interval, ticker, start, end) => {
return dataModels[interval].find({
ticker: ticker,
timestamp: { $gt: start,$lt: end }
}).exec()
}

/**
* Given a data interval and a company ticker, this function returns the oldest
* stock data point that we have stored in our database. This is used to
* determine if we should fetch new data for a query.
* @param {string} interval The time interval between the data points
* @param {string} ticker The company stock ticker
* @return {Promise}
*/
const getLatestStockEntry = (interval, ticker) => {
return dataModels[interval].findOne({ ticker }).sort({
timestamp: 'descending'
}).limit(1).exec()
}

/**
* Given data points fetched from the AlphaVantage API, this function
* determines which of the data points we are missing in our database and
* adds them to our database.
* @param {string} interval The data point interval
* @param {string} ticker The company stock ticker
* @param {Array<Object>} data The data points to insert into the database
* @return {Promise}
*/
const insertData = (interval, ticker, data) => {
return getLatestStockEntry(interval, ticker).then(latest => {
let filteredData = data
if (latest !== null) {
const latestTimestamp = moment(latest.timestamp)
filteredData = data.filter(element => {
return moment(element.timestamp).isAfter(latestTimestamp)
})
}
return Promise.all(filteredData.map(element => {
return dataModels[interval].create(_.merge({ ticker }, element))
})).finally(() => {
return updateLastUpdateTime(interval, ticker)
})
})
}

module.exports = exports = { getStockData, insertData, getLastUpdateTime }


Comments about the code are appreciated and welcome, but I have a few specific questions about best practices with regards to using Mongoose.

1. Previously, I implemented several of these methods as statics on the schema definition, that is:

stockDataSchema.statics.someFunction = function(...) {...}


To invoke these methods, I would then have to export the models to the external calling file, which seemed like bad practice to me because I would then be exposing unnecessary internal parts of the module. My reasoning for designing the module the way I did was so that I could abstract away the complexity and only expose necessary methods. Is this good practice or am I approaching this paradigm completely wrong?

2. This code works and accomplishes what I want it to do, so it falls under working code as per the site guidelines. If I invoke the exported functions in a one-off script however, the function will execute and then the script will hang because mongoose.disconnect() was never called.

As far as I can tell from the mongoose tutorials, database code is supposed to be wrapped within a mongoose.connect() call, though I am not entirely sure how that works since the code that I have above works fine for database access and update. Should I wrap the functionality here with mongoose.connect(), and if so, what is best practice for doing so, or should I wrap the calls with mongoose.connect() in the external file invoking these methods?

3. Is there a better way to do the bulk insert in the insertData() function?

• I don't know much about mongoose, but I just a small tip: if you're going to write one liners, you can prevent unnecesary brackets like this: changing this: .finally(() => { return updateLastUpdateTime(interval, ticker) }) To this: .finally(() => updateLastUpdateTime(interval, ticker)) – Javier García Manzano Sep 13 '17 at 8:20
• I'm aware of this. The only reason I don't is because I have an .eslintrc rule to disallow that. – omgimanerd Sep 13 '17 at 14:25