Shorten ObjectId in node.js and mongoose - javascript

my URLs look like this at the moment:
http://www.sitename.com/watch?companyId=507f1f77bcf86cd799439011&employeeId=507f191e810c19729de860ea&someOtherId=.....
So, as you can see, it gets pretty long, pretty fast.
I was thinking about shortening these ObjectIds.
Idea is that I should add new field called "shortId" to every model in my database. So instead of having:
var CompanySchema = mongoose.Schema({
/* _id will be added automatically by mongoose */
name: {type: String},
address: {type: String},
directorName: {type: String}
});
we would have this:
var CompanySchema = mongoose.Schema({
/* _id will be added automatically by mongoose */
shortId: {type: String}, /* WE SHOULD ADD THIS */
name: {type: String},
address: {type: String},
directorName: {type: String},
});
I found a way to do it like this:
// Encode
var b64 = new Buffer('47cc67093475061e3d95369d', 'hex')
.toString('base64')
.replace('+','-')
.replace('/','_')
;
// -> shortID is now: R8xnCTR1Bh49lTad
But I still think it could be shorter.
Also, I found this npm module: https://www.npmjs.com/package/short-mongo-id
but I don't see it's being used too much so I can't tell if it's reliable.
Anyone has any suggestions?

I ended up doing it like this:
Install shortId module (https://www.npmjs.com/package/shortid)
Now you need to somehow stick this shortId to your objects when they're being saved in the database. I found the easiest way to do this is to append this functionality on the end of mongoose's function called "save()" (or "saveAsync()" if you promisified your model). You can do it like this:
var saveRef = Company.save;
Company.save = function() {
var args = Array.prototype.slice.call(arguments, 0);
// Add shortId to this company
args[0].shortId = shortId.generate();
return saveRef.apply(this, args);
};
So you just basically at each Model.save() function append this functionality to add shortId. That's that.
Edit:
Also, I discovered that you can do it better and cleaner like this straight in Schema.
var shortId = require('shortid');
var CompanySchema = mongoose.Schema({
/* _id will be added automatically by mongoose */
shortId: {type: String, unique: true, default: shortId.generate}, /* WE SHOULD ADD THIS */
name: {type: String},
address: {type: String},
directorName: {type: String}
});
EDIT :
Now you can use the nanoid library which is much more performant and optimized. The documentation is also nice : https://github.com/ai/nanoid/

All existing modules use 64 chars table for conversion. So they have to use '-' and '_' chars in charset. It causes url encoding when you share short url through twitter or facebook. So be careful with it.
I use my own short id module id-shorter that free from this problem because it uses alpha-numeric set for converting.
Wish you success!

Related

How can I modify the resulting query in Mongoose without affecting the actual document?

Status (as of 5/6/2020): Solved, see the identified answer below.
I hope all is well despite the global crisis we are experiencing right now. I am currently doing a school web project and need to render a specific feature assigned to me. I am using Mongoose with Express, and Handlebars for templating. Please see the attached model schema and explanation below.
collegeModel - Collection A
var collegeSchema = new Schema({
shortName: {type: String}, //value that I intend to synchronously query its occurrence with Collection B
longName: {type: String},
logo: {type: String},
contactUs:{
telNum: {type: String},
faxNum: {type: String},
email: {type: String}
},
aboutUs: {type: Array},
visionMission: {type: String},
coreValues: {type: String},
goals: {type: String},
founderBio: {type: String},
philosophy: {type: String},
icon: {type: String}
});
professorModel - Collection B
var professorSchema = new Schema({
profNumber: {type: Int32},
college: {type: String}, //value to be compared with shortName
gender: {type: String},
profName: {type: String},
profCourse: {type: String}
});
Pseudocode - Desired logic to be achieved
app.get('/testCount', function(req,res) {
collegeModel.find({}).lean().exec(function(err,collegeRes){
var collegeObject = [];
collegeRes.forEach(function(document){
professorModel.countDocuments({college:document.shortName}, function(err2,professorCount){
document.count = professorCount;
collegeObject.push(document); //doing a console.log(collegeObject) would return empty objects [].
});
});
});
});
I don't know what I'm doing wrong and I know document.count exists since it returns a value everytime I do console.log(document.count) but when it's pushed it becomes []. Hope you can help me achieve my goal. Thanks!
Your queries resolve asynchronously, you have to find a way to wait for all of them to complete to make sure you have all the data you need.
One way to solve this is using async/await (Node.js >= 7.6.0)
app.get('/testCount', async function(req, res) { // note the async keyword
const collegeRes = await collegeModel.find({}).lean().exec() // .exec() returns a Promise, so you can `await` it.
const resultPromises = collegeRes.map(async college => { // arrow function is equivalent to function in this context
const professorCount = await professorModel.countDocuments({ college: college.shortName })
college.count = professorCount
return college
})
const collegeObject = await Promise.all(resultPromises)
console.log(collegeObject)
})
A bit more readable would be using Promise.map from bluebird or you can also use other promise utility library
const collegeObject = await Promise.map(collegeRes, college => {
const professorCount = await professorModel.countDocuments({ college: college.shortName })
college.count = professorCount
return college
})
console.log(collegeObject)

Is there anything wrong with my mongoose schema

I've stuck for 3 hours wrong and couldn't solve this issue. I'm getting nothing from my document.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var accountSchema = mongoose.Schema({
username: String,
salt: String,
hash: String,
cover: String,
createAt: {type: Date, default: Date.now},
subscriber_email: String
});
module.exports = mongoose.model('account', accountSchema);
I tested with other schema they all worked but not this one. My data's like below
I think the reason you are not able to view anything in your document is the way you are exporting the accountSchema.
Try replacing Account with account while exporting the schema.
Try this:
module.exports = mongoose.model('Account', accountSchema);
Everything else looks fine.
I hope this helps.

Does order of schema matter in Node.JS?

Im about to make a huge schema for a form that I have just built... that being said does my schema order have to mimic the form order, or can it just have all the inputs in any order I put them in ?
Example below.
can it be like this?
// link to mongoose
var mongoose = require('mongoose');
// define the article schema
var mapSchema = new mongoose.Schema({
created: {
type: Date,
default: Date.now
},
dd1: {
type: String,
default: ''
},
dd2: {
type: String,
default: ''
},
com1: {
type: String,
default: ''
},
com2: {
type: String,
default: ''
}
});
// make it public
module.exports = mongoose.model('Map', mapSchema);
Or does it have to be like this?
// link to mongoose
var mongoose = require('mongoose');
// define the article schema
var mapSchema = new mongoose.Schema({
created: {
type: Date,
default: Date.now
},
dd1: {
type: String,
default: ''
},
com1: {
type: String,
default: ''
},
dd2: {
type: String,
default: ''
},
com2: {
type: String,
default: ''
}
});
// make it public
module.exports = mongoose.model('Map', mapSchema);
does my schema order have to mimic the form order, or can it just have all the inputs in any order I put them in?
mongoose.Schema accepts a JavaScript object as its parameter. So your question boils down to:
Are JavaScript objects aware of the order their keys were defined in?
The answer to that is: No, key order is not maintained in JavaScript objects. The JS spec explicitly states that objects are unordered key/value collections. (compare)
Therefore it follows that mongoose.Schema could not rely on key order even if it tied to, which means you are free to order the keys in any way you like.
We can also tackle the question from the other end:
Is it likely that a front-end change like form field order forces me to rewrite my database backend code?
And the answer to that is: No, that is pretty darn unlikely. We can dismiss that thought without even looking into any kind of spec, because it would not make any kind of sense.

Whats the reason for not being able to nest Schemas without an array

I was just wondering why something like this isn't allowed in mongoose schema definitions:
var NameSchema = new mongoose.Schema({
first: {type: String, trim: true },
last: {type: String, trim: true }
});
var UserSchema = new mongoose.Schema({
name: NameSchema, // this line causes an error
age: {type: Number}
});
It seems like a design decision, I was just wondering if I could get an explanation as to why it isn't supported
Thanks!
You can nest a schema using this method:
name: [{ some: "props" } ]
or
name: [NameSchema]
The problem with giving directly the schema definition (without using "type: ...") is that Mongoose can't make the difference between the option object and the schema object. Mongoose will think that NameSchema is actually an option object (containing options such as the type, trim...).

Mongoose schema within schema

How can I add a schema to another schema? This doesn't seem to be valid:
var UserSchema = new Schema({
name : String,
app_key : String,
app_secret : String
})
var TaskSchema = new Schema({
name : String,
lastPerformed : Date,
folder : String,
user : UserSchema
})
I checked the website and it shows how to declare it for an array but not for single.
Thanks
There are a few ways to do this. The simplest is just this:
var TaskSchema = new Schema({
name : String,
lastPerformed : Date,
folder : String,
user : Schema.ObjectId
});
Then you just have to make sure your app is writing that id and using it in queries to fetch "related" data as necessary.
This is fine when searching tasks by user id, but more cumbersome when querying the user by task id:
// Get tasks with user id
Task.find({user: user_id}, function(err, tasks) {...});
// Get user from task id
Task.findById(id, function(err, task) {
User.findById(task.user, function(err, user) {
// do stuff with user
}
}
Another way is to take advantage of Mongoose's populate feature to simplify your queries. To get this, you could do the following:
var UserSchema = new Schema({
name : String,
app_key : String,
app_secret : String,
tasks : [{type: Schema.ObjectId, ref: 'Task'}] // assuming you name your model Task
});
var TaskSchema = new Schema({
name : String,
lastPerformed : Date,
folder : String,
user : {type: Schema.ObjectId, ref: 'User'} // assuming you name your model User
});
With this, your query for all users, including arrays of their tasks might be:
User.find({}).populate('tasks').run(function(err, users) {
// do something
});
Of course, this means maintaining the ids in both places. If that bothers you, it may be best to stick to the first method and just get used to writing more complex (but still simple enough) queries.
As of version 4.2.0, mongoose supports single subdocuments.
From the docs:
var childSchema = new Schema({ name: 'string' });
var parentSchema = new Schema({
// Array of subdocuments
children: [childSchema],
// Single nested subdocuments. Caveat: single nested subdocs only work
// in mongoose >= 4.2.0
child: childSchema
});
What about this simple solution?
var TaskSchema = new Schema({
name : String,
lastPerformed : Date,
folder : String,
user : {
name : String,
app_key : String,
app_secret : String
}
})

Categories

Resources