universal-pattern

    1.0.29 • Public • Published

    Universal Pattern

    Universal Pattern is single and easy way to build professionals API using MongoDB and Node.js.

    This is a Node.js module available through the npm registry.

    Powered by Cesar Casas

    Examples

    See the test folder

    $ cd test
    $ node index

    Simple social network API with Universal Pattern

    Instalation

    $ npm install universal-pattern --save

    Implementation

    First, create a new project using npm, and install required modules.

    $ npm init
    $ npm install express config --save

    creating app.js

    Create the app.js file, and put this code inside. Important: this project use 'config' npm package. Install this first. Then, create 'config' folder and default.json file into it.

    !Important: remember set both params for connection.mongodb, the uri and the database name.

    default.json example:

    {
      "basePath": "/services",
      "host": "localhost",
      "port": 5000,
      "name": "up-example",
      "version": "0.1",
      "connection": {
        "mongodb": {
          "uri": "mongodb://127.0.0.1",
          "name": "uptest"
        }
      }
    }
    const http = require('http');
    const express = require('express');
    const path = require('path');
    const config = require('config');
    const up = require('universal-pattern');
    
    const port = config.get('port');
    const app = express();
    const server = http.createServer(app);
    
    up(app, {
      swagger: {
        baseDoc: config.get('basePath'),
        host: `${config.get('host')}:${config.get('port')}`,
        folder: path.join(process.cwd(), 'swagger'),
        info: {
          version: 10.0,
          title: 'Universal Pattern Example',
          termsOfService: 'www.domain.com/terms',
          contact: {
            email: 'cesarcasas@bsdsolutions.com.ar',
          },
          license: {
            name: 'Apache',
            url: 'http://www.apache.org/licenses/LICENSE-2.0.html',
          },
        },
      },
      compress: true,
      cors: true,
      production: process.env.NODE_ENV === 'production',
      database: {
        uri: config.get('connection.mongodb.uri'),
        name: config.get('connection.mongodb.name'),
      },
      routeController: (req, res, next, props) => next(),
    })
      .then((upInstance) => server.listen(port, () => console.info(`listen *:${port}`)))
      .catch(err => console.error('Error initializing ', err));
    • production: if this props is false, we wil have available the interactive documentation (swagger ui)

    Creating models.yaml

    Now, create the folder 'swagger' and put into it the first yaml file (e.g models.yaml)

    paths:
      /models:
        get:
          tags:
            - models
          summary: models list
          x-swagger-router-controller: universal.search
          parameters:
            - $ref: '#/parameters/q'
            - $ref: '#/parameters/page'
            - $ref: '#/parameters/sorting'
            - $ref: '#/parameters/limit'
            - $ref: '#/parameters/fields'
    
          responses:
            '200':
              description: reports
              schema:
                $ref: '#/definitions/models'
        put:
          tags:
            - models
          summary: insert new cart
          x-swagger-router-controller: universal.insert
          parameters:
            - name: modeldata
              in: body
              required: true
              schema:
                $ref: '#/definitions/modelInput'
          responses:
            '200':
              description: cart added
              schema:
                $ref: '#/definitions/models'
    
        delete:
          tags:
            - models
          summary: delete cart
          x-swagger-router-controller: universal.remove
          parameters:
            - name: _id
              in: query
              required: true
              type: string
          responses:
            '200':
              description: deleted cart
              schema:
                $ref: '#/definitions/models'
    
        patch:
          tags:
            - models
          summary: for updated cart document
          x-swagger-router-controller: universal.update
          parameters:
            - name: modeldata
              in: body
              required: true
              schema:
                $ref: '#/definitions/modelUpdate'
          responses:
            '200':
              description: updated cart
              schema:
                $ref: '#/definitions/models'
    
    definitions:
      modelInput:
        x-swagger-model-version: 3
        type: object
        properties:
          name:
            type: string
            required: true
          level:
            type: integer
            required: true
          props:
            type: array
            items:
              type: string
            minLength: 4
    
    
      modelUpdate:
        type: object
        properties:
          _id:
            type: string
            format: mongoId
    
      models:
        type: object
        properties:
          name:
            type: string
    
    

    Runing example.

    Finally, run the first UP App.

    $ node app.js

    Open your browser and go to (http://localhost:5000/services/docs)

    Magic props when define you swagger.

    startAt and endAt

    Automatic this props will converted into Date ISO Object.

    Options object

    swagger: { // Swagger property, required.
      baseDoc: config.get('basePath'), // this is the baseDoc, is a default initial folder path.
      host: config.get('host'), // what is the actual host?
      folder: path.join(process.cwd(), 'swagger'), // the folder with yamls files
    },
    compress: false, // is true, the add compression mws into app. Default is false
    cors: false, // is true, add cors into header response. Default is false
    database: { // the database (mongodb) properties
      uri: config.get('connection.mongodb.uri'), // database (mongodb) uri connection string
      name: config.get('connection.mongodb.name'),
    },

    services methods.

    The services are available into 'service' prop of 'Universal Pattern', usually called upInstance.

    In all cases, the first argument is 'module name'. Remember, into UP the module name is the first name after '/' in url. For example, for use the module 'users', the argument is '/users'.

    search

    search into collection.

    search(module [,query, page, fields]);

    return Promise with result.

    • module: string, the module name. Ex: '/users'
    • query: just a MongoDB query.
    • page: is a object with pagination and sorting properties:
    {
      limit: integer, // default is 30
      page: integer, // default is 1
      sorting: string // string with props separated by ',' ex: 'name:desc,age:asc'
    }
    

    Example:

    const result = await upInstance.services.search('/users',
    {
      age: { $gt: 5 },
    },
    {
      page: 1,
      limit: 10,
      sorting: 'name:desc',
    });

    today

    Get the last 500 documents inserted today.

    today(module);

    • module: string, the modulo name.

    Example:

      const logs = await upInstance.services.today('/logs');

    insert

    Insert a new document into module.

    insert(module, document);

    • module: string, the module name. Ex: '/users'
    • document: object, the document will be save.

    Example:

      upInstance.services.insert('/logs', {
        url: req.url,
      });

    Important: The service insert will add 'added' prop.

    findOne

    Search the first document.

    `findOne(module, query, fields);``

    • module: string, the module name. Ex: '/users'
    • query: object, the mongoDB query.
    • fields: object, the props should be populate. If empty, return all document prop.

    Example:

      const userData = await upInstance.service.findOne('/users', { _id: upInstance.db.ObjectId(userId) }, { name: 1, age: 1 });

    remove

    Remove document by id.

    remove(module, id); `

    • module: string, the module name. Ex: '/users'
    • id: string, the mongoDB document id.

    Example:

      const removed = await upInstance.services.remove('/users', '5a1f3dabe8c5272a5f78f779');

    removeAll

    Remove all document when match with query.

    removeAll(module, query);

    • module: string, the module name. Ex: '/users'
    • query: object, the mongoDB query.
      upInstance.services.removeAll('/logs', { added: { $lt: new Date() } });

    update

    Update document. update(module, id, data[, opts = { updated: true, set: true }])

    • module: string, the module name. Ex: '/users'
    • id: string, the document id.
    • data: object, the new props to assign.
    • opts: object, options:
      • updated: boolean, if true, added or updated the prop updated. Default is true.
      • set: boolean, if true, the method will add the $set operator. Default is true.

    Example:

      const updated = await upInstace.services.update('/items',
        '5a1f3da42c0f5e2a41ed0439',
        {
          $inc: { totalComments: 1 },
        },
        {
          set: false,
        },
      );

    updateByFilter

    Update documents by query.

    updateByFilter(module, query, data [,opts = { updated: true, set: true }])

    • module: string, the module name. Ex: '/users'
    • id: string, the document id.
    • query: object, the mongoDB query.
    • data: object, the new props to assign.
    • opts: object, options:
      • updated: boolean, if true, added or updated the prop updated. Default is true.
      • set: boolean, if true, the method will add the $set operator. Default is true.

    Example:

      const updated = await upInstace.services.updateByFilter('/items',
        '5a1f3da42c0f5e2a41ed0439',
        {
          'user.age': { $lg: 5 },
        },
        {
          $inc: { totalComments: 1 },
        },
        {
          set: false,
        },
      );

    aggregate

    Run query using aggregate framework.

    aggregate(collection, query, [options = {}])

    Example: get all duplicates emails entries.

    const query = [
      {
        $group: {
          _id: {
            email: '$email',
          },
          uniqueIds: {
            $addToSet: '$_id',
          },
          count: {
            $sum: 1,
          },
        },
      },
      {
        $match: {
          count: {
            $gt: 1,
          },
        },
      },
    ];
    
    const items = await Application.services.aggregate(`/${collection}`, query);

    count

    Return the total document matched with query.

    count(module, query)

    • module: string, the module name. Ex: '/users'
    • query: object, the mongoDB query.
      const totalComments = await upInstance.services.count('/comments', {
        'user._id': '5a1f3dabe8c5272a5f792137',
      });
      const updated = await upInstance.services.updateByFilter('/users', {
        totalComments,
      });

    getLast

    Return the last document match with query.

    getLast(module, query, fields);

    • module: string, the module name. Ex: '/users'
    • query: object, the mongoDB query.
    • fields: object, the props should be populate. If empty, return all document prop.
      const lastComment = await upInstance.services.getLast('/comments', {
      'user._id': '5a1f3da42c0f5e2a41ed042c',
    }, {
      _id: 1,
      text: 1,
    });

    find

    Search documents without pagination system.

    find(module, query[, fields]);

    • module: string, the module name. Ex: '/users'
    • query: object, the mongoDB query.
    • fields: object, the props should be populate. If empty, return all document prop.
      const messages = await upInstance.services.find('/messages', {
      'user._id': '5a1f3da42c0f5e2a41ed042c',
      }, {
        _id: 1,
        body: 1,
        urlCanonical: 1,
      });
      const tasks = await Promise.all(
        messages.map(m => upInstance.services.updateByFilter('/likes', {
            messageId: m._id.toString(),
          },
          {
            'message.urlCanonical': m.urlCanonical,
          }
        )),
      );

    Hooks

    // Insert hooks
    upInstance.addHook('/endpoint', 'beforeInsert', async (req, dataDocument, UPInstance) => {
      return Promise.resolve(params);
    });
    
    upInstance.addHook('/endpoint', 'afterInsert', async (req, insertedDocument, UPInstance) => {
      return Promise.resolve(params);
    });
    
    // Update hooks
    upInstance.addHook('/endpoint', 'beforeUpdate', async (req, dataDocument, UPInstance) => {
      return Promise.resolve(params);
    });
    
    upInstance.addHook('/endpoint', 'afterUpdate', async (req, updatedDocument, UPInstance) => {
      return Promise.resolve(params);
    });
    
    // Remove hooks
    upInstance.addHook('/endpoint', 'beforeRemove', async (req, documentId, UPInstance) => {
      return Promise.resolve(params);
    });
    
    upInstance.addHook('/endpoint', 'afterRemove', async (req, removedDocument, UPInstance) => {
      return Promise.resolve(params);
    });
    
    
    // Search hooks
    upInstance.addHook('/endpoint', 'beforeSearch', async (req, searchParams, UPInstance) => {
      return Promise.resolve(params);
    });
    
    upInstance.addHook('/endpoint', 'afterSearch', async (req, searchResults, UPInstance) => {
      return Promise.resolve(params);
    });
    
    // global Hooks
    upInstance.addHook('*', 'beforeSearch', async (req, searchParams, UPInstance) => {
      return Promise.resolve(params);
    });
    upInstance.addHook('*', 'afterSearch', async (req, searchResults, UPInstance) => {
      return Promise.resolve(params);
    });
    
    upInstance.addHook('*', 'beforeInsert', async (req, dataDocument, UPInstance) => {
      return Promise.resolve(params);
    });
    
    upInstance.addHook('*', 'afterInsert', async (req, insertedDocument, UPInstance) => {
      return Promise.resolve(params);
    });
    
    // Update hooks
    upInstance.addHook('*', 'beforeUpdate', async (req, dataDocument, UPInstance) => {
      return Promise.resolve(params);
    });
    
    upInstance.addHook('*', 'afterUpdate', async (req, updatedDocument, UPInstance) => {
      return Promise.resolve(params);
    });
    
    // Remove hooks
    upInstance.addHook('*', 'beforeRemove', async (req, documentId, UPInstance) => {
      return Promise.resolve(params);
    });
    
    upInstance.addHook('*', 'afterRemove', async (req, removedDocument, UPInstance) => {
      return Promise.resolve(params);
    });
    

    Register controllers

    upInstance.registerController('module.methodControllerName', (req, res, next) => {
      console.info(req.swagger);
      res.json({ ok: true });
    });

    internal swagger properties.

    Important: the model definition should be named 'modeldata' ever!

    ex:

    put:
      tags:
        - models
      summary: insert new cart
      x-swagger-router-controller: universal.insert
      parameters:
        - name: modeldata
          in: body
          required: true
          schema:
            $ref: '#/definitions/modelInput'

    The definition should have any name.

    Props by UP

    After insert a new document, UP will add own props for a better document manager.

    {
      _v: 0, // the document version model. This props should be modify by x-swagger-model-version
      _n: 0, // the updated count
    }

    versioning

    x-swagger-model-version prop should be use for set the data model version. If the prop isn't present, UP automatic will add the prop _v: 1 . If preset, the prop _v value will be x-swagger-model-version value (parser to integer)

    parameters

    input email format

    Example:

    definitions:
      logs:
        type: object
        properties:
          type:
            type: number
            default: 5
            required: false
          email:
            type: string
            format: email
            required: true

    x-swagger-regex

    Test the input value using the regex.

    definitions:
      cartInput:
        type: object
        properties:
          name:
            type: string
            x-swagger-regex: "[az]*"
          cost:
            type: integer
            format: float
          color:
            type: string
            enum:
              - black
              - white
              - blue
              - green
            required: true
          modelId:
            type: string
            format: mongoId
            x-swagger-lookup:
              collection: models
              populate:
                - _id
                - name
    

    input mongoId format

    with 'mongoId' you can indicate the format with the follow validations:

    • maxLength: 24
    • minLength: 24
    • is a valid Hex value

    Example:

    definitions:
      logs:
        type: object
        properties:
          categoryId:
            type: string
            format: mongoId

    working with Array

    We can add arrays like props into modeldata. You can set items type and minLength (this mean you can set the minimum array elements)

    modelInput:
      x-swagger-model-version: 3
      type: object
      properties:
        name:
          type: string
          required: true
        level:
          type: integer
          required: true
        props:
          type: array
          items:
            type: string
          minLength: 4

    lookup

    the prop x-swagger-lookup within a prop definition indicate to UP is necessary have the follow behavior:

    • Check if the parent prop if have the 'format: mongoId'
    • Run query into defined collection to get the document (over _id collection prop)
    • Populate the props and saved into a new object
    • append the new object to the original input model data.

    x-swagger-lookup should be use only for PUT and UPDATE methods, never for GET or DELETE.

    unique prop

    The prop x-swagger-unique indicate to Universal Pattern the prop should be check if the value already exists or not.

    Example

    definitions:
      userInput:
        type: object
        properties:
          firstname:
            type: string
            required: true
          lastname:
            type: string
            required: true
          email:
            type: string
            format: email
            required: true
            x-swagger-unique: true

    the param 'q'

    The param q is use for all search endpoints (getters). Only with this property we can do any search action.

    by ObjectId

    Just use "_" before prop name. Remember, for embed props don't save the data like ObjectId, just like string. Ex: search by _id

    q=_id:3A5a1f3da747404c2a510dfa24

    by regular expression

    Search all document where the prop name like 'tiago'

    q=name:/tiago/

    Search all documents where prop age is equal to nunber 31

    by numbers (integers)

    q=age:.31.

    By boolean props

    Get all documents where prop enable is false. Options: true or false

    q=enable:|false|

    Is NOT this values

    Get all documents where name is NOT pepe

    q=name:!pepe!

    By NULL OR NOTNULL

    If we need filter by NULL or NOTNULL just use this words to upper case. Ex: get all document where valid is NOTNULL

    q=valid:NOTNULL

    By range

    for filter by number range, use the special chars [] and | for separate from/to Ex: get all document where score is between 100 and 200

    q=score:[100|200]

    By gt or lt number

    You can search using "<" or ">", setting the number value between the chars. Ex: get all document where age is > 40

    q=age:>40>

    Using $in

    For search into a prop with differents options, you can use {} operators.

    q=name:{Toyota|Fiat}

    Example

    For a real example, see (https://github.com/lortmorris/up-example)

    Change log

    Dec 18, 2017

    • Added _id into update object for hooks

    nov 18, 2018

    • Hotfix:
    • Fix props.value when the type is integer.
    • Added support for array prop type.
    • Added support for minLength into array type.
    • Added support for items.type into array type.
    • Added new properties into default saved document:
      • _n: count the document updates.
      • _v: the document version (x-swagger-model-version).
    • Added lookup support!.

    Jan 9, 2020

    • Hotfix for object properties definitions.

    Jan 13, 2020

    • Hotfix for boolean type
    • Replace Object.assign for {}
    • Added routeController prop into upInstance. params (req, res, next, props )
    • Removed value subprop from swagger.params.
    • coordinates field fixed.

    Feb 25, 2020

    • Hotfix: remove by _id

    May 17, 2021

    • Removed nodejs package
    • New vg-mongo package implemented (Vision Group MongoDB driver written by Cesar Casas)

    June 16, 2021

    • Reeplaced MongoJS for vg-mongo

    June 21, 2021

    • Added support for use Universal Pattern without MongoDB connection.
    • Fix error handler

    License

    MIT

    Install

    npm i universal-pattern

    DownloadsWeekly Downloads

    15

    Version

    1.0.29

    License

    ISC

    Unpacked Size

    9.11 MB

    Total Files

    36

    Last publish

    Collaborators

    • lortmorris
    • david.martinez