Skip to content

A ground layer for your service architecture which helps you keep your application structured by offering helper methods and reactive hooks.

Notifications You must be signed in to change notification settings

MartinDrost/fundering

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

A pragmatic service framework for structured and reactive data handling using Mongoose.

Fun · deer · ing - The Dutch word for foundation

NPM Version NPM Downloads Minified bundle size Minified + GZipped bundle size

Description

Fundering acts as a ground layer of your service architecture and helps you keep your application structured by offering helper methods and reactive hooks. Rather than reinventing the wheel, the framework uses Mongoose ORM for communication with MongoDB and extends upon the functionality that is already there. A few examples of these functionalities are; dynamic population, recursive authorization, query type casting and service based document middleware.

Installation

$ npm install fundering mongoose

Getting started

Initializing a service is as easy as extending the abstract CrudService class and passing your schema model in the constructor. This class registers your service with fundering for cross-service authorization and casting, and offers a set of methods which help you with basic CRUD actions.

import { CrudService } from 'fundering';
import { IUser } from './user.interface';
import { model } from 'mongoose';
import { userSchema } from './user.schema';

export class UsersService extends CrudService<IUser> {
  constructor() {
    super(model('User', userSchema));
  }
}

Examples

Querying

MongoDB aggregations are great, powerful and fast, but they can also be complex, hard to maintain and (really) slow. The find methods in the CrudService help you keep the positive set of adjectives by utilizing common aggregations for you. The features vary from adding referenced documents to your conditions to randomly sorting the collection before selecting results. The IQueryOptions object also allows you to extend existing methods with extra query conditions for a more dynamic codebase.

class UsersService extends CrudService<IUser> {
  constructor() {
    super(model('User', userSchema));
  }

  getPopulatedGroups() {
    // get all users with their group fields populated
    return this.find({}, { populate: ['group'] });
  }

  getByGroupName(name: string) {
    // find users based on the name of their referenced group
    // sort the users on the createdAt date of their group descending
    return this.find({ 'group.name': name }, { sort: ['-group.createdAt'] });
  }

  getFiveUniqueNames(options?: IQueryOptions) {
    // find 5 randomly sorted unique first names while supporting extending the query
    return this.find({}, { ...options, distinct: 'firstName', random: true, limit: 5 });
  }
}

Authorization

Authorization plays a huge part in most production applications and implementing it properly can come at the cost of readability and/or performance. You can implement the IOnAuthorization interface to utilize the onAuthorization() method in your CrudService. This method is triggered on every find query called through the CrudService and allows you to return a MongoDB expression object to which the returned documents must comply.

Note: you can imbue the options object with user data to create rules based on context. This is elaborated upon in the wiki.

class UsersService extends CrudService<IUser> implements IOnAuthorization {
  constructor() {
    super(model('User', userSchema));
  }

  async onAuthorization(options: IAuthOptions): Promise<Expression> {
    // limit the user's access to just his/her own account
    return { $eq: ['$_id', options.user?._id] };
  }
}

Document middleware

Fundering offers opt-ins for document middleware through service methods. By moving these methods to the service level you are able to keep your logic more centralized and make use of patterns like dependency injection more easily. Fundering also extends the functionality by allowing context injection and keeping all arguments neatly typed.

class UsersService extends CrudService<IUser> implements IPreSave {
  constructor() {
    super(model('User', userSchema));
  }

  async preSave(payload: IUserModel, options?: IQueryOptions<IUser>) {
    // encrypt modified passwords
    if(payload.isModified('password')) {
        payload.password = encrypt(payload.password)
    }
  }
}

Documentation

You can consult the wiki for more detailed information about the following subjects:

Using Fundering with Nestjs?

Make sure you check out the nest-utilities package for flexible endpoints and a great developer experience for front-enders and back-enders alike.


Made by Martin Drost - Buy me a ☕

About

A ground layer for your service architecture which helps you keep your application structured by offering helper methods and reactive hooks.

Resources

Stars

Watchers

Forks