npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mongoosy

v1.0.16

Published

Round-trip front end mongoose.

Downloads

6

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 &amp; the only one who can see Garfield</li>
    </ul>
    <hr>
    <a href="#">Add a cat</a>&nbsp;&nbsp;&nbsp;
    <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}&nbsp;&nbsp;&nbsp;
    <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);