mongoosy
v1.0.16
Published
Round-trip front end mongoose.
Downloads
6
Maintainers
Readme
Mongoosy
Round-trip front-end mongoose.
Install
npm i mongoosy
Setting up the backend
You don't have to npm install express and mongoose. Mongoosy will do that for you.
You import mongoose, express and your app (your server) from mongoosy.
You also send your settings to mongoosy when requiring it. Typically you will want to change the database name (otherwise it defaults to test).
You also need to start the app (the server) on a port of your choice.
// Change these values
const dbName = 'mydbname';
const serverPort = 3000;
const { mongoose, express, app, pwencrypt, session } = require('mongoosy')({
// settings for mongoosy
connect: {
url: 'mongodb://localhost/' + dbName
}
});
app.listen(serverPort, () => console.log('Server listening on port ' + serverPort));
Mongoosy expect mongoose models to be stored as separate files that each export a monoogse model in a folder called models.
What you require
You get actually require five different things when requiring mongoosy:
- mongoose - the mongoose module - if you should need to use it directly somewhere in your code
- express - the express module - if you should need to use it directly somewhere in your code
- app - the express server created by mongoosy - you can add your own routes, middleware etc to it if needed
- pwencrypt - an encryption function for passwords. Use it to encrypt the passwords if you create any users on the backend.
- session - session middleware function created by the express-session module, use it for things like integrating your sessions with sockets (via a module like express-socket.io-session).
Default settings
You can change which folder to look in for models and a some other settings if you want to. These are the defaults settings used if you don't change them:
{
query: {
route: '/api/mongoosy*'
},
expressJson: {
limit: '1mb'
},
models: {
path: './models'
},
connect: {
url: 'mongodb://localhost/test',
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true
},
login: {
pseudoModel: 'Login',
connectedModel: 'User',
passwordField: 'password',
encryptPassword: true,
encryptionSalt: 'unique and hard to guess',
secureSession: false // set to true if https
},
acl: {
query: ({ user, model, instance, methods }) => true,
result: ({ user, model, instance, methods, result }) => result
}
}
Setting up the frontend
Note: We expect you to use mongoosy in a frontend environment that support the import command.
Import your models
import mongoosy from 'mongoosy/frontend';
const {
// Replace with names
// of models you have defined
// in the models folder
Cat,
CatOwner
} = mongoosy;
That's it! Now you can use your models exactly as you would on the backend (utilizing the full Mongoose API)!
Note: Make sure to always use them with modern syntax - await/promises - not callbacks.
Example
Just a basic example - you can do so much more since you have all of Mongoose available - population and other advanced queries...
Note: As you can see below we are asking for the property .js in our console.logs. You should only use this in console.logs and for checking properties that might be undefined - it gives the log of objects and arrays a little cleaner look since the objects are proxy objects and otherwise will be logged as such.
async function doStuff() {
// Use mongoose from the frontend
// through mongoosy
// Create a new cat and save to db
let aCat = new Cat({ name: 'Garfield' });
await aCat.save();
// after saving the cat it has an id
console.log('aCat', aCat.js);
// Read all cats from the db
let allCats= await Cat.find();
console.log('allCats', allCats.js);
// Create a new cat owner and save to db
let aCatOwner = new CatOwner({ name: 'Jon'});
await aCatOwner.save();
// after saving the cat owner he has an id
console.log('aCatOwner', aCatOwner.js);
// Read that cat owner again from the db
let foundCatOwner = await CatOwner.findOne({ _id: aCatOwner._id });
console.log('foundCatOwner', foundCatOwner.js);
// Read all cat owners from the db
let allCatOwners = await CatOwner.find();
console.log('allCatOwners', allCatOwners.js);
}
Gotchas: Reading a property that might be undefined
When you are trying to read a property that might be undefined from a document you will get a Proxy object back. This is because mongoosy is based on Proxy-objects and has now way of knowing if you are asking for a method or a property in the specific case when a property is undefined.
As a workaround you can use document.js.property this will correctly return the property value *even for undefined properties.
Login and ACL
Mongoosy automatically handles logins connected to a model (like User), encrypts passwords, handles sessions... A "fake model" called Login is always available, with the three methods login, logout and check (se example code below).
Mongoosy also allows andvanced ACL (Access Control List) security based on users and/or user roles by giving you two "hooks" you can connect to your own functions in the settings. One prevents queries to run if you return false, the other one lets you filter results. The hooks recieve detailed information about the query and the logged in user - it is up to you to setup your control structure based on this!
There is a working example you can run:
cd node_modules/mongoosy/example
node index
This is the code used in the example:
Backend code (with examples of how to use ACL)
const path = require('path');
const { mongoose, express, app, pwencrypt } = require('mongoosy')({
// settings for mongoosy
connect: {
url: 'mongodb://localhost/login-example-db'
},
acl: {
query: aclQuery,
result: aclResult
}
});
app.listen(3000, () => console.log('Server listening on port 3000'));
app.use(express.static('www'));
app.get('/frontend', (req, res) => res.sendFile(path.resolve(__dirname, '../frontend.js')));
function aclQuery({ user, model, instance, methods }) {
// blacklisting is safest
// - i.e.return false unless you want to allow something
console.log('aclQuery', JSON.stringify(arguments, '', ' '));
return false ||
(user && user.roles.includes('god')) ||
(user && user.roles.includes('catwatcher') && methods[0].method === 'find' && methods.length === 1) ||
(user && user.roles.includes('catcreator') && methods[0].method === 'save' && methods.length === 1);
}
function aclResult({ user, model, instance, methods, result }) {
// can modify results
console.log('aclResult', JSON.stringify(arguments, '', ' '));
if (!user || !user.roles.includes('god') && model === 'Cat' && result instanceof Array) {
console.log('You are not a god so no Garfield for you!');
result = result.filter(x => x.name !== 'Garfield');
}
return result;
}
async function createGodUser() {
let User = require('./models/User');
let foundGod = await User.findOne({ email: '[email protected]' });
if (foundGod) { return; }
let god = new User({ email: '[email protected]', password: pwencrypt('666'), roles: ['god'] });
console.log('Created god user...');
await god.save();
}
createGodUser();
Frontend code (with examples of how to use Login)
import mongoosy from 'mongoosy/frontend';
const {
Cat,
Login,
User
} = mongoosy;
const $ = cssSelector => document.querySelector(cssSelector);
start();
async function start() {
document.body.innerHTML = /*html*/`
<h1>Mongoosy login/ACL example</h1>
<p>First <a href="#">setup/reset the test data</a></p>
<p>Then you can try to login as </p>
<ul>
<li>[email protected] (pw: 1234) - can see cats</li>
<li>[email protected] (pw: 4321) - can see and create cats</li>
<li>[email protected] (pw: 666) - can do everything & the only one who can see Garfield</li>
</ul>
<hr>
<a href="#">Add a cat</a>
<span class="user"></span>
<hr>
<h3>Cats</h3>
<div class="cats"></div>
`;
$('body').addEventListener('click', e => {
let t = (e.target.closest('a') || {}).innerText;
t === 'Login' && login();
t === 'Logout' && logout();
t === 'Add a cat' && addCat();
t === 'setup/reset the test data' && setupTestData();
});
updateLoginInfo();
}
async function updateLoginInfo() {
let user = await Login.check();
$('.user').innerHTML = user.js.email ?
`Logged in as ${user.email}
<a href="#">Logout</a>` :
`<a href="#">Login</a>`
await listCats();
}
async function login() {
let email = prompt('Email');
let password = prompt('Password');
let loginResult = await Login.login({ email, password });
loginResult.js.error && alert(loginResult.js.error);
updateLoginInfo();
}
async function logout() {
await Login.logout();
updateLoginInfo();
}
async function addCat() {
let name = prompt('Add a new cat:');
let cat = new Cat({ name });
await cat.save();
if (cat.error === 'Not allowed by query ACL') {
alert('You are not allowed to create cats!');
}
await listCats();
}
async function listCats() {
let allCats = await Cat.find({});
$('.cats').innerHTML = allCats.error === 'Not allowed by query ACL' ?
'You are not allowed to see the cats.' :
allCats.map(x => x.name + '<br>').join('');
}
async function setupTestData() {
// login as a god and create som test data
await Login.login({ email: '[email protected]', password: '666' });
let catNames = ["Garfield", "Heathcliff", "Felix the Cat", "Tom", "Hello Kitty", "Sylvester", "Tigger", "Simba"];
let userDetails = [
{ email: '[email protected]', password: '1234', roles: ['catwatcher'] },
{ email: '[email protected]', password: '4321', roles: ['catwatcher', 'catcreator'] }
];
await Cat.deleteMany({});
await User.deleteMany({ email: { $ne: '[email protected]' } });
for (let name of catNames) {
let cat = new Cat({ name });
await cat.save();
}
for (let detail of userDetails) {
let user = new User(detail);
await user.save();
}
// god leaves the building
await Login.logout();
alert('Test data created (and no user logged in)!');
updateLoginInfo();
}
Models
User
const { Schema, model } = require('mongoose');
const modelName = 'User';
let schema = new Schema({
email: { type: String, unique: true, required: true },
password: { type: String, required: true },
roles: [String]
});
module.exports = model(modelName, schema);
Cat
const { Schema, model } = require('mongoose');
const modelName = 'Cat';
let schema = new Schema({
name: String
});
module.exports = model(modelName, schema);