nedb-shelter

This library promisifies NeDB methods to make easier to work with front end and app databases using javascript


Keywords
javascript, database, db, nedb, promise, async, await, drop
License
ISC
Install
npm install nedb-shelter@2.0.10

Documentation

NeDB Shelter

If you have any suggestion, you can use nedb-shelter's git hub repository to open an issue

This plugin aims to be an Promisified wrapper to NeDB adding some quality of life changes. The point of this lib is to make things even easier on front end side.

About NeDB

NeDB is an opensource javascript document database developed by Louis Chatriot. It was developed to be pretty damn similar to MongoDB in many aspects, like querying and deleting documents with the known MongoDB syntax.

However, as the operations runs we have to deal with responses manually, setting callbacks ourselves. This plugin aims to preserve the asynchronous behavior with promises

About this project

This was an abandoned project. As I needed to get back to work, dealing with this package was becoming hard. In the past I've made many "quality of life" functions that only have hidden the NeDB's api and to make my life easier. As I matured as a developer I've returned to this code and decided to make an simpler approach. Only wrap NeDB's functions with promises to make it more readable.

The objective of NeDB is to be MongoDB compliant at a simpler level to attend most of browser/front-end workflow. Keep in mind that this library break the MongoDB compatibility created in NeDB to offer a simpler API to use in the Front-End.

I've made many changes in the API, so I'm changing the active branch to 2.0.0 version.

In other words, it's just a wrapper with syntatic sugar

API

This API aims to follow the same NeDB's style.

  • Installation
  • Usage
  • Creating database
    • Defaults
    • Setting new defaults
  • Methods
    • Insert
    • Query
    • Update
    • Remove

Installation

  npm install 'nedb-shelter' --save

Usage

You have can import or require

  import database from 'nedb-shelter'

Creating database

This is a minimal boilerplate to create a browser/app database using NeDB Shelter

  await database.createDatabase({ dbName: 'planets', timestampData: true, autoload: true })

If you set autoload to true you don't have to call js loadDatabase(). Otherwise, you must load the database before in order to the queries work. All methods will be available after loading the database

Methods

Select

Select is a method that allows you to choose the database based on it's name. This choice was made to maintain some control over what is passing where in the scope of the project. For instance: I don't need to import the object referencing the database everywhere. I just import "nedb's shelter" and select the database from within and it's there. This allows you to use various databases. Remember to always select your database before querying

  database.select('planets')

Inserting

Insert

Insert is straightforward. Select your database and send the object to create a new entry in the database.

With objects

  let response = database.select('planets').insert( { planet: "Earth", satellites: ["Moon"], species: "humans" } )
  console.log(response) // [{ planet: "Earth", satellites: ["Moon"], species: "humans" }]

With Arrays ( bulk insert ) You can bulk insert data using an array of objects

  await database.select('planets').insert([ 
      { planet: "Jupiter", satellites: ["Deimos, Titan"], species: "vogons" }, 
      { planet: "Earth", satellites: ["Moon"], species: "humans" }
    ]) 

Querying

By default, an query without any object argument, will return all items inside the specified schema. The query section has many utilities that are, most of the time, only wrappers to make life easier. When you use .find you can pass any modifier that is documented in NeDB's documentation and will just work.

Find

This method will return all entries that match the object sent. It accepts 3 values.

query: This is the object you're filtering. If you're used to MongoDB, you know that every object that has that entry in it will be hit and returned options: This is an object for pagination, skipping and limiting a query. More info below projection: This will permit you to select the exact fields to return using an object notation

  let planets = await database.select('planets').query({ inhabited: true }) 
  // returns all entries that has inhabited true
  console.log(planets)

Pagination, Limiting and Sorting

Pagination method works a little different from other methods. It needs a object to execute the query. It happens because NeDB's implementation of the methods above use a chain-like syntax. You can define every one of the four expected props: query, skip, limit and sort. Not defining one means that it will use a default value.

  • query: Expects a object.Means the search terms used to query database. For instance { planet: "Mars"}. Default is query all items into database
  • skip : Expects a number. It will skip the results from first to last item. Defaults to 0, wich means it will NOT skip any results
  • limit: Expects a number. It will put a limit to the number of results from database. Defaults to 0, wich means it will NOT limit any results
  • sort : Expects a number. It will order the results based on a field and a number. Can be reversed. Defaults to 1. Acceptable values are 1 for ascending and -1 for descending
  let queryOptions = {
    skip: 1,
    limit: 5,
    sort: { planet: 1 } // 1 means natural order, using -1 means it will reverse the order
  }

  await database.select('planets').find({}, finder) // limited, skipped and sorted results

Projecting

You can project values, removing or keeping fields manually. This is useful when you need to hide data in someway:

  await database.select('planets').find({}, null, { _id: 0, createdAt: 0, updatedAt: 0 })
  // the returned object will NOT have _id, createdAt and updatedAt entries

Counting

Count

This method will return a Number counting all objects that a schema already have

await database.select('planets').count() // returns is 7

Count from a query

You can filter the results for your count. Using a query, the result returning will be a count of the actual query results

await database.select('planets').count( { system: "solar" } )// returns 6 because one system is futurama

Updating

You can update your documents in a MongoDB fashion using NeDB shelter api. This method always will return an object with the fields: numAffected, affectedDocuments, upsert. Of course this is not the best usage if you're interested in performance. So you can pass a third parameter to the update method, wich is a object, setting affectedDocuments to false. For more information on update methods and operators, you can refer to NeDB

options is an object with two possible parameters multi (defaults to false) which allows the modification of several documents if set to true upsert (defaults to false) if you want to insert a new document corresponding to the update rules if your query doesn't match anything. If your update is a simple object with no modifiers, it is the inserted document. In the other case, the query is stripped from all operator recursively, and the update is applied to it. returnUpdatedDocs (defaults to true ) if set to true and update is not an upsert, will return the array of documents matched by the find query and updated. Updated documents will be returned even if the update did not actually modify them.

  await database.select('planets').update({ planet: "Earth" } , { $set: { planet: "Nova Earth" } })

You have to be cautious as the update method, when not using the $set keyword will REPLACE your object entirely. This is pretty common in most databases, but I always try to alert to this behavior, as it is not intuitive

Example replacing an entry

  await database.select('planets').update({ planet: "Earth" } , { planet: "Nova Earth" }) 
  // this will swap the Earth object entirely, removing all the subfields as well, leaving only the specified object into the former place

You can set a subfield using dot notation

  await database.select('planets').update( { planet: "Earth"}, { $set: { "position.horizontal": "650" }} )

Removing

Remove

Remove is pretty simple. Pass the query in the first argument and it removes the object from database for you

  await database.select('planet').remove({ planet: "Earth" })

It is important to note that you can delete many objects with a single query, so, to prevent undesired deletions, I've put an lock into the method. To delete more than one object, you must pass the second argument multi as true. This will allow your query to delete more than one document

  await database.select('planet').remove({ planet: "Earth" }, { multi: true })

If you need the object back after you delete it, you can pass the returnDeletedData option in the third parameter. This is not native in NeDB library and if you want maximum performance, you should keep this off as it queries data before deleting it

  await database.select('planet').remove({ planet: "Earth" }, { multi: true }, { returnDeletedData: true }) // returns what you have deleted

Droping databases

I've added two new Methods to the stack. One of them removes the database, the other flushes the data. Those are not compliant with MongoDB standard nor NeDB API. I've written those methods because, more than one time, I saw myself trying to flush data or deleting an entire entry into IndexedDB manually as NeDB doesn't offer this method:

Drop database

This will delete the database entirely. If you use this method, you can't reuse the old database to insert data. You will have to re-create the database. This works removing a row from IndexedDB thus making the entry unavailable.

    await database.select('planets').drop()

Flushing collection

This will flush all data from an database. When you use this method, the database is still there, but all data is gone. Using this method, you can use the old database without having the need to re-create the database.

    await database.select('planets').flush()

Indexing Data

Index

You can index data to make the database reads faster. Keep in mind that this will work for simple queries and queries using the modifiers $in, $lt, $lte, $gt and $gte. The _id field is auto indexed, so you do not need to index this field unless you add this field manually.

From NeDB's page, those are the parameter list:

fieldName (required): name of the field to index. Use the dot notation to index a field in a nested document. unique (optional, defaults to false): enforce field uniqueness. Note that a unique index will raise an error if you try to index two documents for which the field is not defined. sparse (optional, defaults to false): don't index documents for which the field is not defined. Use this option along with "unique" if you want to accept multiple documents for which it is not defined. expireAfterSeconds (number of seconds, optional): if set, the created index is a TTL (time to live) index, that will automatically remove documents when the system date becomes larger than the date on the indexed field plus expireAfterSeconds. Documents where the indexed field is not specified or not a Date object are ignored

Indexing an field and adding a unique constraint

  await database.select('planets').index({ fieldName: 'planet', unique: true })

When you add the unique constriaint to the field, if you try to insert duplicate, it will raise an error and will not insert the data into the database. So use with caution

Acessing RAW NeDB Object

I've added an entry to the base object, so you can access NeDB directly without having to make magic. If you call database._nedb you will access the raw NeDB instance. Keep in mind that, if you're using this too much, it is probably better for you to use the library itself

  database._nedb // returns the actual nedb library

TO-DO list

I'm still working in tests for this package and analizing if it will need more methods. For now, the package is ready to use. But if you see a flaw or something I missed or have a sugestion as well, open an issue on github :)