Skip to content

nasriyasoftware/MongoDB

Repository files navigation

N|Solid

MongoDB.

Static Badge Repository Size Last Commit Status

Visit us at www.nasriya.net.

A MongoDB client with schemas, data-hooks, permissions, and more.

Made with ❀️ in Palestine πŸ‡΅πŸ‡Έ

Important

🌟 Support Our Open-Source Development! 🌟 We need your support to keep our projects going! If you find our > work valuable, please consider contributing. Your support helps us > continue to develop and maintain these tools.

Click here to support us!

Every contribution, big or small, makes a difference. Thank you for > your generosity and support!


πŸ“š Full Documentation


Getting Started

Installation

npm i @nasriya/mongodb

Importing

Import in ESM modules:

import mongodb from '@nasriya/mongodb';

Import in CommonJS (CJS)

const mongodb = require('@nasriya/mongodb').default;

Preparing the Environment

Defining Databases and Collections

You can define databases and collections using the defineDatabase method. Here’s an example of how to define a simple database setup:

mongodb.defineDatabase({
    name: 'Auth',
    collections: [{ name: 'Passwords' }, { name: 'Sessions' }]
});

mongodb.defineDatabase({
    name: 'Blog',
    collections: [{ name: 'Posts' }, { name: 'Comments' }]
})
More Details

For advanced features like defining schemas, permissions, and more, refer to the Defining Databases page.

Defining Connections

Our MongoDB driver makes it easy to manage cluster connections by defining it once, and use it simply by referencing its name when creating a client.

Connections are defined using the defineConnection method.

defineConnection(name: string, connectionString: string): string

Example:

const localServer = mongodb.defineConnection('localServer', 'mongodb://localhost:27017');

console.log(localServer); // ⇨ 'localServer'

You can see that the defineConnection() method returns the connection name, so you can use it directly when creating clients, or you can use the name instead.

Creating & Using Clients

Our MongoDB driver allows you to create flexible clients for different clusters or users, providing secure and efficient access to your databases.

Basic Client Creation

To create a client, you need to specify the connection name using the createClient method. The connection property is required.

const client = mongodb.createClient({ name: 'LocalServer' });
Authorization Levels

You can set the authorization level for the client:

  • System: Bypasses all collection-level permissions.
  • User: Enforces collection-level permissions based on the user’s role and ID.
const systemClient = mongodb.createClient({
    name: 'LocalServer', 
    authorization: 'System'
});

const userClient = mongodb.createClient({
    name: 'LocalServer', 
    authorization: 'User',
    user: { id: 'userId', role: 'Member', loggedIn: true }
});

const userClient2 = mongodb.createClient({
    name: 'LocalServer', 
    authorization: 'User',
    user: { id: 'userId', role: 'Member', loggedIn: true },
    defaultDatabase: 'Members'
});

If you didn't specify a default database when creating the client, or if you wish to switch between databases during runtime, you can easily set the database to use like this:

// Switch or specify a database after creating the client
client.db('Auth');

This method allows you to dynamically change the database context for your operations. You can now perform actions like querying, inserting, or updating documents within the specified Auth database, without having to recreate the client.

Note: If you haven't set a default database during client initialization, calling client.db() is required before making database operations.

For more details on creating clients, see Creating Clients.

Advanced Collection Typing (TypeScript)

To enable full type-safety and autocomplete when working with your collections, you can define your own interfaces by extending the base CollectionItem type.

All items created and returned by this adapter are based on the CollectionItem interface:

/** Represents an item in a collection. */
interface CollectionItem {
    /** The ID of the item. */
    _id: string;
    /** The date this item was created at. */
    _createdDate: Date;
    /** The date this item was last updated at. */
    _updatedDate: Date;
    /** The ID of the owner. */
    _owner: string;
    /** Additional properties */
    [key: string]: any;
}

You can create your own collection interfaces by extending this base interface:

import { CollectionItem } from '@nasriya/mongodb';

interface UserItem extends CollectionItem {
    name: string;
    email: string;
}

Then use it with your collection like this:

const user: UserItem = await client.getItem('Members', 'userId');

This helps prevent mistakes, enables richer development tooling, and makes your data layer more robust.


CRUD Operations

NasriyaDataOptions

Before we dive into each of the CRUD operations, it's important to understand the NasriyaDataOptions interface, which can be used across various operations to customize their behavior.

/** Options for data operations. */
export interface NasriyaDataOptions {
    /** Prevents permission checks from running for the operation. Defaults to `false`. */
    suppressAuth?: boolean;
    /** Prevents hooks from running for the operation. Defaults to `false`. */
    suppressHooks?: boolean;
}

Properties:

  • suppressAuth:
    • If set to true, this option will skip any authentication or authorization checks that would normally be run for the operation. This is useful when you want to perform an operation without enforcing the usual security measures.
    • Defaults to false.
  • suppressHooks:
    • If set to true, this option will prevent any hooks (e.g., pre-insert or post-insert hooks) from running during the operation. This is useful when you want to bypass custom logic that might be applied during these operations.
    • Defaults to false.

These options can be passed to any CRUD operation where you want to modify the default behavior, such as insert, bulkInsert, find, etc.

βž• Creating Data

To add new items to a collection, you can use the insert or bulkInsert methods provided by the client. These methods allow you to insert a single item or multiple items into a collection, respectively.

Insert a Single Item
insert(collectionName: string, item: Item, options?: NasriyaDataOptions): Promise<CollectionItem>;

The insert method is used to insert a single item into a collection. Here's how you can use it:

Example:

client.insert('Members', { name: 'John Doe', email: '[email protected]' });

Parameters:

  • collectionName: The name of the collection where the item will be inserted (e.g., Members).
  • item: The object you want to insert. This should conform to the collection's item interface(e.g., CollectionItem).
  • options: Optional NasriyaDataOptions options for the operation.

Returns: A promise that resolves to the inserted CollectionItem, which includes the unique_id of the item and other metadata (like _createdDate and _updatedDate).

Bulk Insert Multiple Items
bulkInsert(collectionName: string, items: Item[], options: NasriyaDataOptions = { suppressAuth: false, suppressHooks: false }): Promise<BulkInsertResult>

The bulkInsert method allows you to insert an array of items into a collection at once, making it more efficient when dealing with large datasets.

Example:

client.bulkInsert('Members', [
    { name: 'Charlie', email: '[email protected]' },
    { name: 'Eve', email: '[email protected]' }
]);

Parameters:

  • collectionName: The name of the collection where the items will be inserted (e.g., Members).
  • items: An array of objects to insert. Each object should conform to the collection's it interface.
  • options: Optional NasriyaDataOptions options for the operation.

Returns: A promise that resolves to the result of the bulk insert operation (BulkInsertResult), which contains details like the number of inserted items.

/** Result of a bulk insert operation. */
export interface BulkInsertResult {
    /** The inserted items. */
    items: CollectionItem[];
    /** Statistics about the operation. */
    stats: {
        /** The number of inserted items. */
        inserted: number;
        /** The number of skipped items. */
        skipped: number;
        /** The IDs of the inserted items. */
        insertedIds: string[];
        /** The IDs of the skipped items. */
        skippedIds: string[];
    };
}

Example:

const result = await client.bulkInsert('Members', [
    { name: 'David', email: '[email protected]' },
    { name: 'Zara', email: '[email protected]' }
]);

console.log('Bulk Insert Result:', result.stats);
console.log('Inserted Items:', result.items);
console.log('Inserted IDs:', result.stats.insertedIds);
console.log('Skipped IDs:', result.stats.skippedIds);

πŸ” Reading Data

You can retrieve data from your collections using the following methods: getItem, find, and count. Each method allows you to specify optional operation options using the NasriyaDataOptions.

Getting an item
getItem(collectionName: string, itemId: string, options?: NasriyaDataOptions): Promise<CollectionItem | null>;

Use getItem to retrieve a single item from a collection by its ID.

Parameters:

  • collectionName: The collection ID from which to retrieve the item.
  • itemId: The ID of the item to retrieve.
  • options: Optional NasriyaDataOptions options for the operation.

Returns Promise<CollectionItem | null> containing the item if it exists, or null if no item matches the provided ID.

Example:

const member = await client.getItem('Members', 'd8a9e87b-2a3f-4f0c-b6a5-9a5364a1c2a4');

if (member) {
    console.log(member.name);
} else {
    console.log('User not found');
}
Querying Data
query(collectionName: string): DataQuery;

To perform advanced data queries on your collections, use the query() method. This returns a DataQuery instance, which provides powerful utilities like filtering, pagination, and data retrieval.

Basic Example

const query = client.query('Members');

For advanced usage with filters and pagination, see the full guide on building a query β†’

πŸš€ Executing the Query

Once you've built a query using client.query(), there are two primary ways to execute it:

  1. Fetching the actual results – using .find()

    const result = await client.query('Members')
     .filter(client.filter().eq('role', 'admin'))
     .limit(10)
     .skip(0)
     .find();

    Returns a promise that resolves to a DataQuery instance.

    Parameters:


2. Getting the number of matching items – using `.count()`
const total = await client.query('Members')
 .filter(client.filter().eq('role', 'admin'))
 .count();

Returns a promise that resolves to a number.

Parameters:

Wants to learn how to build powerful filters? πŸ”— See the Filter Builder β†’

πŸ“ Updating Data

You can update records in your database using four different methods:

  • update: Update a single item that must already exist.
  • bulkUpdate: Update multiple items that must already exist.
  • save: Create or update a single item β€” more flexible.
  • bulkSave: Create or update multiple items β€” more flexible.
🧩 Understanding the Difference
Method Requires _id Fails if item doesn't exist Can create new item
update βœ… Yes βœ… Yes ❌ No
bulkUpdate βœ… Yes βœ… Yes ❌ No
save ❌ Optional ❌ No βœ… Yes
bulkSave ❌ Optional ❌ No βœ… Yes
  • Use update / bulkUpdate when you're sure the items already exist.
  • Use save / bulkSave when you want to either insert or update items as needed.
Updating a Single Item with update()
await client.update('Members', {
    _id: 'abc123',
    name: 'Ahmad Nasriya'
});

Parameters:

  • collectionName: The collection ID to use.
  • item: An item that must contain a valid _id.
  • options: Optional NasriyaDataOptions options for the operation.

Updating Multiple Items with bulkUpdate()
await client.bulkUpdate('Members', [
    { _id: 'id1', name: 'John' },
    { _id: 'id2', name: 'Jane' }
]);

Parameters:

  • collectionName: The collection ID to use.
  • items: An array of items that must each contain a valid _id.
  • options: Optional NasriyaDataOptions options for the operation.

Saving a Single Item with save()
await client.save('Members', {
    name: 'Ahmad Nasriya',
    email: '[email protected]'
});

Parameters:

  • collectionName: The collection ID to use.
  • item: An item that may or may not include an _id.
  • options: Optional NasriyaDataOptions options for the operation.

Saving Multiple Items with bulkSave()
await client.bulkSave('Members', [
    { name: 'Ali' },                            // Insert
    { _id: 'id3', name: 'Updated Member' }      // Update
]);

Parameters:

  • collectionName: The collection ID to use.
  • items: Array of items to save β€” new ones will be inserted, existing ones updated.
  • options: Optional NasriyaDataOptions options for the operation.

πŸ—‘οΈ Deleting Data

To delete items from your database, you can use:

  • remove: Delete a single item by its ID.
  • bulkRemove: Delete multiple items using their IDs.
Deleting a Single Item with remove()
await client.remove('Members', 'abc123');

Parameters:

  • collectionName: The collection ID to use.
  • itemId: The _id of the item to remove.
  • options: Optional NasriyaDataOptions options for the operation.

Deleting Multiple Items with bulkRemove()
await client.bulkRemove('Members', ['id1', 'id2', 'id3']);

Parameters:

  • collectionName: The collection ID to use.
  • itemIds: An array of item _ids to remove.
  • options: Optional NasriyaDataOptions options for the operation.

πŸš€ Upcoming Features


⚠️ Error Handling (Coming Soon)

Error handling is an important part of any robust system, and we are working on implementing a comprehensive error handling mechanism. In the near future, errors will be communicated through specific error codes and detailed messages, making it easier for developers to understand and address issues in their applications.

Stay tuned for updates on how errors will be managed and reported in future versions!


License

Please read the license from here.