spigg.js

Introduction

spigg brings the data mapper pattern to node.js by introducing entities for your business logic and mappers to handle persistence. The result is modular, testable code to which DRY principles apply .

spigg.js ships with a minimal featureset to get out of your way and doesn't care about how you choose to store your data or what business logic your application requires.

Install

$ npm install spigg

Running the above command in your shell will install the latest stable version of spigg, which currently is 0.8.0.

Why?

We needed to apply business logic to data, regardless of whether the data originated from a REST API, in-memory-storage or a persistent database.
The data-mapper pattern allowed for per-resource separation between datasource code and business logic, so we decided to roll our own solution as no available library at the time did fit our needs.
As a bonus our codebase became even more modular, whilst adding minimal complexity.

Centralizing business logic into entities makes a lot of sense, as you can always fire up an entity and invoke the custom business logic you defined in it.

When working with user submitted data, using a lot of setters isn't unusual. Setting email addresses to lowercase and trimming off spaces, for instance. With spigg setters on any depth are automatically recognized and run whenever the assigned property changes.g

Quick example

# /entities/User.coffee
s = require("spigg")
class User extends s.Entity

  # Call the init method which is automatically
  # invoked upon construction by the SpiggEntity
  init: ->
    # Optional:
    # Set a object of fields that are allowed in this entity to
    # prevent non-permitted properties from being stored.
    # This is helpful to when you need to build an entity out of 
    # user submitted data.
    @fields =
      name:    true
      email:   true
      tweets:  true
      meta:
        lastlogin: true

    # Optional:
    # Create a hashmap of default properties to be used in
    # the entity.
    @defaults =
      tweets:  []
      meta:
        lastlogin: new Date()
      
    # Optional: 
    # Store setters that is automatically invoked when you store a property
    # to your entity. In this example, all stored names will be lowercased.
    # Note that setters work on depth as well, so you could define nested setters.
    @setters = 
      name: (str) ->
        str.toLowerCase()

    # Here goes your business logic
    # Example:
    isValid: (callback) ->
      errors = []
      errors.push "Name is missing" unless @data.name
      callback(errors, @data)

module.exports.User = User
# /mappers/User.coffe
s = require("spigg")
r = require("requestah")(80)
class UserMapper extends s.Mapper

  getByEmail: (email, callback) ->
    callback mysql.query("SELECT * FROM USERS WHERE EMAIL="+email)
  
  getTweets: (twitterUsername, callback) ->
    url = [
      "https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=",
      twitterUsername,
      "&count=100"
    ]
    
    r.get url.join(""), (res) ->
      callback res.body

  save: (user, callback) ->
    callback mysql.query("INSERT INTO USERS...")


module.exports.UserMapper = new UserMapper 
# /app.coffee  
express = require("express")
app =     express()
app.use express.bodyParser()

app.post "/users", (req, res) ->
  user =       require("./entities/Users")
  userMapper = require("./mappers/Users")
  user =       new User req.body

  user.isValid (err, data) ->
    userMapper.save user
    return res.send 201 unless err
    res.send JSON.stringify(err), 400

app.listen 3000 

Above is written in coffeescript. Please use this tool to convert to plain JS.

Documentation: Entity

Creating entities

Create your own entity by extending the Entity-class provided by spigg, as shown below:

s = require("spigg")
class myEntity extends s.Entity	

Get & set data

Set data by passing arguments to the constructor

u = new user(name: "John Doe")

Set data by key/value

u.set "name", "Jane Doe"

Set data by object

u.set {name: "Jane Doe"}

Get all data

u.get()

Get data by key

u.get("name")

Get data by dot notation

u.get("meta.lastlogin")

Unset a property from the entity

u.unset("name")

Reset the entity back to default values

u.reset()

Clear the entity

u.clear() # All properties are now removed.

Allowed fields

Create a property inside the init-method, named fields which should represent the structure of your data. By default any properties are allowed unless the fields-object has been set.

s = require("spigg")
class myEntity extends s.Entity	
  init: ->
    # This entity can now only contain name, tweets & meta.lastlogin. Everything else is rejected.
    @fields =
      name:    true
      tweets:  true
      cats:    false
      meta:
        lastlogin: true

Custom setters

Add setters inside the init-method as a representation of how your data is organized. Setters are automatically invoked when a property changes and modifies the value of said property accordingly.

s = require("spigg")
class myEntity extends s.Entity	
  init: ->
    @setters =
      name: (str) ->
        str.toLowerCase()
      meta: 
        lastlogin: (timestamp) ->
          new Date(timestamp*1000)

Calling the filter method

In certain cases, it is preferably to not allow updates of all properties, so you can write your own impmentation of the _filter-method already available in the entity to remove non-permitted fields as shown below:


class myEntity extends s.Entity

  @fieldsAllowedInUpdate:
    name:  true
    email: true
  
  isValidForUpdate: (callback) ->
    errors = []
    errors.push "Name is missing" unless @data.name
    callback(errors, @_filter(@data, @fieldsAllowedInUpdate))
	

Documentation: Mapper

Creating mappers

Create your own mapper by extending the Mapper-class provided by spigg, as shown below:

s = require("spigg")
class myMapper extends s.Mapper

Ensure origin of data

Mappers should generally not be aware of entities, but this is a good exception if you need to make sure that data passed to a method inside a mapper must origin from an entity. Invoke the provided isEntity-method as shown below accordingly:

s = require("spigg")
class myMapper extends s.Mapper
  save: (user) ->
    db.save user.data if @isEntity user	

Development

spigg is released under the MIT license and is hosted at Github, so feel free to contribute.

To run tests;

$ make test

Roadmap:

  • Better event support
  • Dot notation setter