Package Exports
- nodal
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (nodal) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Nodal
An ES6 API Server and Framework for iojs
v0.2.x is pre-release, suggested for use in development only
Nodal is an opinionated API Server and Framework for quickly generating RESTful API services in iojs.
It is intended to be used for cross-platform applications with various client-side implementations or loosely-coupled services where responses to requests are generally limited to structured data. (Though there is support for HTML and static resources, they are not a priority.)
With a built-in command-line interface, models, controllers, templates, migrations and application architecture, Nodal provides all of the tools a developer needs to get a new iojs project started in a very short amount of time.
Writing your server software in Nodal should feel somewhat natural coming from other MVC frameworks such as Django and Rails. While Nodal certainly borrows many concepts from these frameworks (standing on the shoulders of giants), it has a distinct architecture and decisions about feature implementation vary.
Features
- ES6
- Models
- Controllers
- Templates
- Migrations
- Routing
- Query Composition
- Multiple database connections
- Multiple environment configurations
- Middleware (gzip/deflate middleware pre-packaged)
- Initializers
Additionally, a built-in CLI that supports:
- Starting a new project
- Easy generation of models, controllers, migrations, middleware, initializers
- Database drop, create, prepare, migrate, rollback
- Running your Nodal server
Nodal comes configured to deploy to Heroku easily (using git) for rapid prototyping.
Installation
To install the latest version of Nodal, make sure you have iojs installed.
You can then run:
$ sudo npm install nodal -g
And voila! You now have access to the Nodal command-line interface.
If you're intending on data layer integration, Nodal only supports Postgres at present time. I personally recommend using Postgres.app for your dev environment if you're running OSX.
Getting Started
Docs are a work in progress, so if you want to just dive in, follow these simple instructions to start hacking away with Nodal. ;)
Getting started with Nodal is simple. Just run nodal new
and you'll be guided
through the process. Nodal will create a project directory for you in your
current directory based on your project name.
Now you can run nodal s
to start your server, and voila! Your index page will
be available at localhost:3000
under default configurations. A 404 page and
static resources page are also available. (Static resources are stored in
your ./static/
directory.)
Connect your database, create a RESTful resource
First, Make sure you have Postgres installed. (OSX developers check out Postgres.app.)
Next, create a "postgres" superuser with no password if one does not already exist
$ createuser postgres -s
Open your app/init.js
file and uncomment the line:
// app.useDatabase('main', Nodal.my.Config.db.main);
Finally, run the following commands:
$ nodal db:create
$ nodal db:prepare
$ nodal g:model --user
$ nodal g:controller v1 --for:User
$ nodal db:migrate
Now run your server again using nodal s
, and you should be able to run GET,
POST, PUT, and DELETE requests to localhost:3000/v1/users
.
Look in app/routes.js
, app/controllers/v1/users_controller.js
,
app/models/user.js
, app/db/schema.json
and app/db/migrations
to see what
went on behind the scenes. :)
Documentation
Directory Structure
Nodal uses the following directory structure:
-- [app]
\-- [controllers]
\-- [models]
\-- [templates]
\-- init.js
\-- routes.js
-- [config]
-- [db]
-- [initializers]
-- [middleware]
-- [static]
-- .nodal
-- Procfile
-- server.js
Most of these directories will be pre-populated with some boilerplate examples so you can begin getting comfortable with the application architecture.
Configuration
(The following assumes you're including Nodal using
const Nodal = require('nodal');
)
While Nodal for the most part favors convention-over-configuration, you can set
environment-specific secrets in the config
folder by
creating .json
files with your application secrets. These will be loaded into
your Nodal.my.Config
object based on their filenames, with the environment
dictated by the NODE_ENV
environment variable of your process (defaults to
'development'
).
For example, the db.json
values associated with "development"
will get
loaded into Nodal.my.Config.db
when you run Nodal locally.
init.js
init.js is the bootstrapping script for your server.
You should use it to assign initializers, middleware, bind your data layers, and prepare your application to listen for incoming connections.
If you want to use a data layer, make sure you un-comment app.useDatabase!
module.exports = (function() {
"use strict";
const Nodal = require('nodal');
const StaticAssetInitializer = Nodal.require('initializers/static_asset_initializer.js');
const GzipMiddleware = Nodal.require('middleware/gzip_middleware.js');
const app = new Nodal.Application();
/* use initializer */
app.initializers.use(StaticAssetInitializer);
/* use middleware */
app.middleware.use(GzipMiddleware);
/* bind data layer */
// app.useDatabase('main', Nodal.my.Config.db.main);
/* Initialize App */
app.initialize(function() {
app.listen(Nodal.my.Config.secrets.port);
});
})();
As shown here, Nodal comes pre-packaged with an initializer and middleware.
The StaticAssetInitializer
pre-loads all static assets into RAM at present time
(be wary of this constraint) before the application begins listening for
connections. All Initializers will be run, in the order they're added, upon a
call to app.initialize()
.
The GzipMiddleware
uses deflate/gzip compression on page renders, if
applicable. Middleware, in regards to Nodal, intercepts Controller#render
calls and applies transformations and state changes to the controller and
associated rendering data.
Routes
Routes are located in app/routes.js
. You must import each Controller
you
wish to use separately, and tell the router what regular expression to match
compare the request url against.
As an example,
const IndexController = Nodal.require('app/controllers/index_controller.js');
router.route(/^\/?$/, IndexController);
Any match to the provided regular expression will cause the router to proceed with following the route to the controller.
Routes are ordered by priority, with routes declared first getting match precedence over routes declared subsequently. If no route is matched to a request, Nodal will fall back basic plaintext 404 error.
You'll also notice the use of Nodal.require
, which acts as a wrapper for
require
, automatically targeting the root directory of your application.
Controllers
New controllers can be generated with:
$ nodal g:controller name
or, if you want a controller to be associated with a model,
$ nodal g:controller namespace --for:model_name
For example, nodal g:controller test
would create
./app/controllers/test_controller.js
, nodal g:controller ns/test
would
create ./app/controllers/ns/test_controller.js
, and
nodal g:controller ns --for:test
would create
./app/controllers/ns/test_controller.js
that contains a require
for a model
named Test
.
One of the first things you'll notice about your Nodal project is that you
already have some controllers set up, mainly an IndexController
, an
Error404Controller
and a StaticController
.
We'll examine controllers/error/404_controller.js
to begin:
module.exports = (function() {
"use strict";
const Nodal = require('nodal');
class Error404Controller extends Nodal.Controller {
get(self, params, app) {
self.status(404);
self.render(app.template('error/404.html'), params);
}
}
return Error404Controller;
})();
As seen here, the get
method is associated with an HTTP GET request.
Currently supported request types are GET
, POST
, PUT
and DELETE
via
get
, post
, put
and del
, respectively. All four methods take the same
parameters.
self
The self
parameter contains a reference to the controller. The reason for this
is to avoid having to use let self = this
in the body of your function in
preparation for asynchronous processing.
params
params
is an object with the following properties: path
, id
, query
,
body
, ip_address
, headers
.
params.path
is an Array containing the result of a regex match to your route,
including capturing groups. For example, the route /^\/static\/(.*)/
with the
request url as /static/image.jpg
would have a params.path
value of
['/static/image.jpg', 'image.jpg']
.
params.id
is the part of your request url that isn't matched by your route,
if applicable. For example, if your route was /^\/users\/?/
with the request
url /users/52
, params.id
would have a value of '52'
.
params.query
is an object containing your query string parameters,
if applicable.
params.body
is an object containing any HTTP POST body data, if applicable.
app
App refers to your main application object as defined in init.js
,
which has references to your databases, the query composer and additional
features.
Templates
Templates are all compiled using the lightweight
doT.js templating engine and can be
accessed through app.template(path)
where path is the relative path of your
template within the templates
directory. If you pass a template to a
Controller#render
call, the second argument of the call is used as the data
object in the template.
There is no intention to add to the complexity of templates, as Nodal is intended to primarily be used as an API server.
Database (and Multiple Connections)
Set up your database in init.js
by adding the line:
app.useDatabase('main', Nodal.my.Config.db.main);
This will allow you to access your 'main'
database instance via
app.db('main')
. Multiple databases can be aliased using the
Application#useDatabase
method.
Models and Migrations
Models and migrations go hand-in-hand. While you can create models that are migration-independent, that's a little more advanced, so let's keep them coupled for now.
If you want to jump in to some code and look at a pre-fabricated model, Nodal has a built-in user model that can be generated using:
$ nodal g:model --user
Otherwise, create your first Model manually. :)
$ nodal g:model Person name:string age:int
Which will generate the necessary model and migration.
Supported data types are:
serial
int
currency
float
string
text
datetime
boolean
Nodal also supports array
types with PostgreSQL, and these fields can be
generated automatically using:
$ nodal g:model HasAnArray arr_of_ints:int:array arr_of_strings:string:array
Your Person
model will look a little something like this:
module.exports = (function() {
"use strict";
const Nodal = require('nodal');
class Person extends Nodal.Model {
__preInitialize__() {}
__postInitialize__() {}
/* Model Extensions */
}
Person.prototype.schema = Nodal.my.Schema.models.Person;
Person.prototype.externalInterface = [
'id',
'name',
'age',
'created_at'
];
return Person;
})();
Model#preInitialize
This method is run before the model is initialized (values set, data loaded). It is intended to be used for preparing your model validations. An example would be:
__preInitialize__() {
this.validates(
'name',
'must be at least five characters in length',
function(value) {
return value && value.length > 5;
}
);
}
Model#postInitialize
This method is run after data is loaded and validations are run.
Model.prototype.schema
A reference to your schema (containing table and column data) for your model
Model.prototype.externalInterface
These are a list of whitelisted or "secure" fields that are allowed to be shown
on the external interface of your API (an API response). The query composer
uses this property with Composer#externalQuery
.
Migrations
If you've followed the previous step, you already have a migration. You can
also create an empty migrate using nodal g:migration MigrationName
.
Since you have a migration ready, it's time to prepare your database. First set
your local connection details in config/db.json
(only PostgreSQL is currently
supported), then run:
$ nodal db:create
$ nodal db:prepare
If you run into issues with these commands (under default configurations), and you're using Postgres.app, run the following in your command line:
$ createuser postgres -s
This will create the database specified and then prepare it for migrations.
Note that db:prepare
will always reset all migrations and your schema. Be
careful!
Ready to migrate!
From the previous step, we'll see that we have a migration in db/migrations/
that looks something like this:
module.exports = (function() {
"use strict";
const Nodal = require('nodal');
class CreatePerson extends Nodal.Migration {
constructor(db) {
super(db);
this.id = 2015051801423419;
}
up() {
return [
this.createTable("people", [{"name":"name","type":"string"},{"name":"age","type":"int"}])
];
}
down() {
return [
this.dropTable("people")
];
}
}
return CreatePerson;
})();
How do we run it? easy!
$ nodal db:migrate
And your persons
table from the previous step should have been created.
If you want to undo a change, use:
$ nodal db:rollback --step:1
Note that --step:1
is an optional flag. The default rollback count is 1
,
up to however many migrations you have. --step
can also be provided for
db:migrate
.
More...
Nodal is very active development, with version 0.2.x representing a pre-release version. The plan is to transition 0.2.x to 1.x once the intended feature set is included, the architecture is solidified and test coverage is thorough.
At present time, Nodal only supports PostgreSQL. With full support for SQL adapters, it is a priority to allow for other data layers soon.
Docs will be fleshed out ASAP! Thanks for your patience. :)
Project Direction
The following features are in development on Nodal 0.2.x
- Test coverage (in progress)
- Model relationships (links to other models)
- Task scheduler
- Easy authorization (requires redis for more than one instance)
About
Nodal is under active development and maintained by Keith Horwood.
Follow me on Twitter, @keithwhor
Fork me on GitHub, keithwhor
Thanks for checking out Nodal!
Feel free to open issues related to any questions you might have. Suggestions for project direction are helpful as well, if there's anything you believe Nodal is missing.
PRs are welcome but will be screened thoroughly for code style as well as how they fit into the overall architecture. I will be picky. Suggestions for active areas of cleanup / improvement are on tests and the CLI.