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.
$ npm install spigg
Running the above command in your shell will install the latest stable version
of spigg, which currently is 0.8.0.
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
# /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.
Create your own entity by extending the Entity-class provided by spigg, as shown below:
s = require("spigg")
class myEntity extends s.Entity
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.
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
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)
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))
Create your own mapper by extending the Mapper-class provided by spigg, as shown below:
s = require("spigg")
class myMapper extends s.Mapper
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
spigg is released under the MIT license and is hosted at Github, so feel free to contribute.
To run tests;
$ make test
Roadmap: