dvblueprint

0.1.0 • Public • Published

DVblueprint

About

DVblueprint is an out of the box Node.js REST server.

Features:

  • Easily Extensible REST Interface
  • Built-in support for MySql and MongoDb
  • OAuth 2.0 Authentication Protocol
  • User Signup / Registration
  • Forgot Password Flow
  • Rapid Route Prototyping
  • Dynamic Data using Long-Polling
  • Route Error Handling
  • Email Templating
  • AWS SES and S3 Integration
  • Static File Server (Web Server)
  • Easy Logging using Bunyan
  • Self-Documenting API

Install Environment Dependencies

Install Node

The latest version of Node.js can be downloaded and isntalled from here: download node. To learn more, you can read the documentation here: node.js docs

Install CoffeeScript

Most of the DVblueprint server is written in CoffeeScript. It is syntactic sugar for JavaScript, and it is much easier to read and develop with. All of the examples below are going to be CoffeeScript. Node runs on JavaScript, so all Coffee files must be compiled before run time.

npm install -g coffee-script

Compile a .coffee file using the '-c' flag:

coffee -c my_file.coffee
Install MySql

Some of the features in DVblueprint require a MySql database, including Dynamic Push Data, and OAuth 2.0 Authentication. Access to a MySql server needs to be set up.

Here are some options to help get you started:

Install DVblueprint

Clone DVblueprint from bitbucket

Clone the repository to your local workspace directory and install node dependencies.

$ cd my/workspace/directory
$ git clone https://bitbucket.org/dv-mobile/blueprint.git
$ cd blueprint
$ npm instal

Initialize Database

$ cd my/workspace/directory/blueprint
$ mysql -u [username] -h [optioanl-db-host] -p

mysql> CREATE DATABASE blueprint
	-> DEFAULT CHARACTER SET utf8
	-> DEFAULT COLLATE utf8_general_ci;

mysql> use blueprint;
mysql> source db/bootstrap.sql;
mysql> exit

Create Application Directory

The main application is going to live as a peer of the DVblueprint repository. The following commands will setup an ideal project directory to run an application that uses the DVblueprint server.

$ cd my/workspace/directory
$ mkdir my_app
$ cd my_app
$ npm instal bunyan
$ ln -s ../blueprint ./node_modules/.
$ ln -s ../blueprint/html_root .
$ mkdir lib
$ mkdir routes
$ mkdir config

Configure the Application

Configuration Files

When the Blueprint server first launches, it will load default configuration details and merge it with the config file that matches the running processes enviroment.

Create a local config file using the editor of your choice (Make sure to compile after editing):

$ cd my/workspace/directory/my_app
$ vi config/local_dev.coffee
Configure Database
# local_dev.coffee
module.exports=
	db:
		mysql:
			pool:
				host: 'localhost'
				port: 8889 	# MAMP uses this port number
				user: 'root'
				password: 'root'
				database: 'blueprint'

Create Server File

Create main application file

$ cd my/workspace/directory/my_app
$ vi server.coffee

Add the following to the main application file:

#
# server.coffee: Main launch point for the application
#

server= require 'blueprint'
server.start()

Running

To run the application:

$ export npm_config_env="local_dev"
$ export npm_config_config_dir="config"
$ node server.js | bunyan -o short

Vist the REST API Documentation: here
Vist the Example Todo Web-App: here

Creating Custom Routes

The following is an example of how to add a route module that exposes endpoints for 'Fruit' resources.

Create Route Module File

Create a Route Module within the application's routes directory using the editor of your choice:

$ cd my/workspace/directory/my_app
$ vi routes/r_fruit.coffee

Install Route Module

Add the following to the application's config file to install the Route Module:

# local_dev.coffee
module.exports=
	db:
		...
	route_modules:
		Fruit: 
			enable: true
			name: 'Fruit'
			class: 'Fruit'
			file: 'routes/r_fruit'

Route Module Constructor

An instance of the Route Module will be created when the server is first fired up, and all endpoints defined in the module will be registered with Restify to be exposed over the API. The constructor will be passed the kit, which has access to all enabled services including the app config, and application logger (See creating a Custom Service). Best practice is to take what you need from the kit and attach to this (@).

Endpoints are defined by adding to the @endpoints variable of the module, and defining options for that endpoint.

class Fruit
	constructor: (kit)->
		@log= 		kit.services.logger.log
		@config= 	kit.services.config
		@sdb= 		kit.services.db.mysql

		# Fruit Endpoints
		@endpoints=
			getFruit:
				verb: 'get', route: '/Fruit'
				use: true, wrap: 'default_wrap', version: any: @S_GetFruit
				sql_conn: true, auth_required: false
			buyFruit:
				verb: 'post', route: '/Fruit'
				use: true, wrap: 'default_wrap', version: v1: @S_BuyFruit, v2: @S_BuyFruit2
				sql_conn: true, sql_tx: true, auth_required: true
			eatFruit:
				verb: 'del', route: '/Fruit/:frid/eat'
				use: true, wrap: 'default_wrap', version: any: @S_EatFruit
				sql_conn: true, sql_tx: true, auth_required: true
				pre_load: fruit: @S_PlFruit
	
	S_GetFruit: (ctx, pre_loaded)-> ...
	S_BuyFruit: (ctx, pre_loaded)-> ...
	S_BuyFruit2: (ctx, pre_loaded)-> ...
	S_EatFruit: (ctx, pre_loaded)->	 ...
	S_PlFruit: (ctx, pre_loaded)-> ...
exports.Fruit= Fruit

Available endpoint options and their possible values:

option values description
verb get, post, put, delete The HTTP Verb
route '/Fruit/:frid' The HTTP Url to match
use bool Use the Self Documenting API.
wrap 'default_wrap', 'simple_wrap' The Wrapper to use around custom route logic
version v1: @logic, any: @otherLogic Custom Logic to Use based on API Version requested
sql_conn bool Grab a Sql Database Connection
sql_tx bool Start a Sql Transaction
auth_required bool Use OAuth 2.0 for this route
pre_load varName: @preLoadLogic Run @preLoadLogic and stuff return value in to varName

Custom Route Logic

Custom Route Logic is the logic that is to be run when DVblueprint and restify validate an incoming request and map it to a particular endpoint defined in a Route Module. The idea is to separate out the unique business logic for a particular route and simplify the logic in the process. Best practice is to define the custom route logic as a function within a Route Module and associate it with a particular endpoint by using the 'version' endpoint option in the Route Modules constructor.

Versioning

Custom Route Logic is mapped to a URL, based on what was defined in the constructor. DVblueprint supports API Versioning by allowing you to specify different logic for different versions of the api. For example, when the server gets a request for 'POST /api/v1/Fruit', it will run the logic found in @S_GetFruit. When the server gets a request for 'POST /api/v2/Fruit', it will run the logic found in @S_GetFruit2.

DVblueprint will first look for a specific version, otherwise, it will run the logic defined by the 'any:' key on the 'version' option of an endpoint.

Wrapping

Before custom route logic is run, it is wrapped with the logic defined in the 'wrap' option for the endpoint. The idea is that this wrapper can perform tedious, repetitive logic that is typically required across multiple different routes. For example, the 'default_wrap' can grab a database connection, start a transaction, verify authentication, pre_load any data before calling the custom logic, handle any errors during route processing, roll-back a bad transaction, return the database connection to the pool, and even respond back to the incoming HTTP request.

simple_wrap

The simple wrap will call the custom route logic the exact same way restify would, by passing in the incoming request object, response object, and the next callback. The simple_wrap does not perform any special post processing, so error handling, HTTP responses and calling next() are the responsibility of the route logic. The simple_wrap accepts the auth_required endpoint option to verify access to the endpoint.

RouteUsingSimpleWrap: (req, res, next)->
	params= req.params
	if 'name' of params
		res.send "Hello, #{params.name}"
	else
		res.send new Error 'Missing the name param!'
	next()
default_wrap

The default wrap is a little smarter and can perform pre and post processing of the custom logic. The default wrapper will pass a Context (ctx) and some Pre Loaded variables (pre_loaded) to the custom logic. The default wrapper will catch any errors that are thrown in the custom logic, and then on successfully running the custom logic, the wrapper will look at the return value for any data that needs to be sent back on to incoming request.

RouteUsingDefaultWrap: (ctx, pre_loaded)->
	params= ctx.p 	 # Query params, form data, parsed req body
	conn=	ctx.conn # Mysql Database connection
	_log= 	ctx.log  # Bunyan logger w/ req.id attached
	authId= pre_loaded.auth_id # Authorized ident_id
	
	throw new E.MissingParam 'name' unless 'name' of params
	send: "Hello, #{params.name}"
Signatures and options

Here are the wraps that are currently available and the endpoint options that affect them:

wrap wrapped logic signature available options
simple_wrap (req, res, next)-> auth_required
default_wrap (ctx, pre_loaded)-> sql_conn, sql_tx, auth_required, pre_load

Pre-load logic

Sometimes a route, or routes will need to run the same logic every single time. The biggest example of this would be to grab a record from the database based on an id passed in to the route. The default_wrapper has the ability to run any custom 'pre_load' logic, stash the result in to a variable and pass all of these variables to the custom route logic for further processing.

Using the 'eatFruit' endpoint from the example above, the return value of this function will be added to the pre_loaded variable passed in to the custom logic as 'fruit':

# Preload the Fruit
# Assumes 'Fruit/:frid' in the URL
S_PlFruit: (ctx, pre_loaded)->
	f= 'Auth:S_PlFruit:'
	fruit_id= ctx.p.frid

	Q.resolve()
	.then ()=>

		# Grab the Fruit from the database
		@sdb.fruit.get_by_id ctx, fruit_id
	.then (db_rows)=>
		throw new E.NotFoundError 'PRELOAD:FRUIT' if db_rows.length isnt 1
		return db_rows[0]
		
S_EatFruit: (ctx, pre_loaded)->
	...
	fruit= pre_loaded.fruit
	...

Self-Documentation

To include a route in the Self-Documentation of the API, simply return a 'use_doc' if the first parameter to the custom route logic is the string 'use'. A 'use_doc' is a Hash with 'params' and 'response', which in turn are key/value hashes of names and types that are to be included or returned.

RouteUsingDefaultWrap: (ctx, pre_loaded)->
	use_doc=
		params: {}
		response: success: 'bool', users: '[]'
	return use_doc if ctx is 'use'

RouteUsingSimpleWrap: (req, res, next)->
	use_doc=
		params: state:'S', country: 'S'
		response: profile: '{}'
	return use_doc if req is 'use'

Complete Example

class Fruit
	constructor: (kit)->
		@log= 		kit.services.logger.log
		@config= 	kit.services.config
		@sdb= 		kit.services.db.mysql
		@E=			kit.services.error

		# Fruit Endpoints
		@endpoints=
			getFruit:
				verb: 'get', route: '/Fruit'
				use: true, wrap: 'default_wrap', version: any: @S_GetFruit
				sql_conn: true, auth_required: false
			eatFruit:
				verb: 'del', route: '/Fruit/:frid/eat'
				use: true, wrap: 'default_wrap', version: any: @S_EatFruit
				sql_conn: true, sql_tx: true, auth_required: true
				pre_load: fruit: @S_PlFruit
	
	S_GetFruit: (ctx, pre_loaded)->
		use_doc=
			params: {}
			response: success: 'bool', fruit: '[]'
		return use_doc if ctx is 'use'
		fruit= []
		
		Q.resolve()
		.then ()=>
		
			# Grab all fruit from the database
			@sdb.fruit.GetCollection ctx
		.then (db_rows)=>
			fruit= db_rows
			
			# Respond to the client
			success= true
			send: {success, fruit}

	S_EatFruit: (ctx, pre_loaded)->	
		use_doc=
			params: {}
			response: success: 'bool', fruit: '[]'
		return use_doc if ctx is 'use'
		
		Q.resolve()
		.then ()=>
			
			# Dispose of the pre-loaded fruit
			@sdb.fruit.DisposeById ctx, pre_loaded.fruit.id
		.then (db_result)=>
			throw new @E.DbError 'Unable to Eat Fruit' unless db_result.affectedRows is 1
			
			# Respond to the client
			success= true
			send: {success}
		

	# Preload the Fruit
	# Assumes 'Fruit/:frid' in the URL
	S_PlFruit: (ctx, pre_loaded)->
		
		Q.resolve()
		.then ()=>

			# Grab the Fruit from the database
			@sdb.fruit.get_by_id ctx, ctx.p.frid
		.then (db_rows)=>
			throw new E.NotFoundError 'PRELOAD:FRUIT' if db_rows.length isnt 1
			return db_rows[0]

exports.Fruit= Fruit

TODO

Documentation:

  • Document how to create a Service and where to access
  • Document how to create a MySql Module
  • Document Default Config File

Features:

  • Authenticate Long-Poll Handles
  • Server Analytics Endpoint
  • Testing Framework using Mocha + Chai
  • Integrate Grunt
  • Event Logging
  • Agnostic Database Interface
  • Agnostic Email Interface
  • SSL
  • Param Validator
  • Cron Job Processor
  • Re-design Role Manager (Roles, ACLS, Permits)

License

Copyright © DV-mobile 2014 - 2015. All Rights Reserved.

Readme

Keywords

none

Package Sidebar

Install

npm i dvblueprint

Weekly Downloads

2

Version

0.1.0

License

none

Last publish

Collaborators

  • dvmobile