v-t-test

0.4.2-22 • Public • Published

Tower.js

Full Stack Web Framework for Node.js and the Browser.

Built on top of Node’s Connect and Express, modeled after Ruby on Rails. Built for the client and server, from the ground up.

Build Status

Follow me @viatropos.

Note, Tower is still very early alpha. Check out the roadmap to see where we’re going. If you’re up for it, please contribute!

The 0.5.0 release will have most of the features, and will roughly be equivalent to a beta release. From there, it’s performance optimization, workflow streamlining, and creating some awesome examples. 1.0 will be a plug-and-chug real-time app framework.

The master branch will always be functional, and (for the most part) in sync with the version you can install from the npm registry.

Default Development Stack

  • Ember
  • jQuery
  • Handlebars (templating)
  • Stylus (LESS is also supported)
  • MongoDB (database, it’s optional. Tower can just be used in the browser)
  • Redis (background jobs, also optional)
  • Mocha (tests)
  • CoffeeScript
  • Twitter Bootstrap

Includes a database-agnostic ORM with browser (memory and AJAX), and MongoDB support. Tower is modeled after ActiveRecord and Mongoid for Ruby. Includes a controller architecture that works the same for both client and server, modeled after Rails. The routing API is like that of Rails 3. Templates also work on client and server—and you can swap in any template engine, no problem. Includes asset pipeline that works just like Rails 3 (minifies and gzips assets with an md5-hashed name for optimal browser caching, if you desire). And it includes a watcher that automatically injects javascripts and stylesheets into the browser as you develop.

It solves a lot of our problems. We hope it solves yours, too.

Install

npm install tower -g

You will also need grunt, an awesome build tool, and coffee-script:

npm install grunt -g
npm install coffee-script -g

Finally, make sure you have mongodb installed and running:

brew install mongodb
mongod # starts server

If you would like to try out the background-worker code, you can also install and start redis:

brew install redis
redis-server

Generate

In your terminal, generate your app and start your server:

tower new app
cd app
npm install
tower generate scaffold Post title:string body:text
tower generate scaffold User firstName:string lastName:string email:string
node server

Then, in a second terminal, start the watcher. This will automatically compile files when they change:

cd app
cake watch

By having two separate windows, you can modify your code without having to run your server, and coffeescript/stylus/less tasks will still be executed. (And the same goes for any task in your grunt file.)

Also note, grunt’s watcher doesn’t currently get notified when new files are created. So if you run the tower generate command (or otherwise create files), stop and rerun the cake watch command. You also might want to check out grunt-contrib-watch, which was recently created, and may solve this issue. (I haven’t tried it yet, though.)

If you run into an error during npm install, remove the node_modules folder and try again.

To automatically restart your server if it crashes, run with forever:

npm install forever -g
forever server.js

Application

# app/config/shared/application.coffee 
global.App = Tower.Application.create()

Models

# app/models/shared/user.coffee 
class App.User extends Tower.Model
  @field 'firstName'required: true
  @field 'lastName'
  @field 'email'format: /\w+@\w+.com/
  @field 'activatedAt'type: 'Date'default: -> new Date()
 
  @hasOne 'address'embed: true
 
  @hasMany 'posts'
  @hasMany 'comments'
 
  @scope 'recent'-> createdAt: '>=': -> _(3).days().ago().toDate()
 
  @validates 'firstName''email'presence: true
 
  @after 'create''welcome'
 
  welcome: ->
    Tower.Mailer.welcome(@).deliver()
# app/models/shared/post.coffee 
class App.Post extends Tower.Model
  @field 'title'
  @field 'body'
  @field 'tags'type: ['String']default: []
  @field 'slug'
 
  @belongsTo 'author'type: 'User'
 
  @hasMany 'comments'as: 'commentable'
  @hasMany 'commenters'through: 'comments'type: 'User'
 
  @before 'validate''slugify'
 
  slugify: ->
    @set 'slug'@get('title').replace(/[^a-z0-9]+/g'-').toLowerCase()
# app/models/shared/comment.coffee 
class App.Comment extends Tower.Model
  @field 'message'
 
  @belongsTo 'author'type: 'User'
  @belongsTo 'commentable'polymorphic: true
# app/models/shared/address.coffee 
class App.Address extends Tower.Model
  @field 'street'
  @field 'city'
  @field 'state'
  @field 'zip'
  @field 'coordinates'type: 'Geo'
 
  @belongsTo 'user'embed: true

Chainable Scopes, Queries, and Pagination

App.User
  .where(createdAt: '>=': _(2).days().ago()'<=': new Date())
  .desc('createdAt')
  .asc('firstName')
  .paginate(page: 5)
  .all()

Associations

user  = App.User.first()
 
# hasMany 'posts' 
posts = user.get('posts').where(title: 'First Post').first()
post  = user.get('posts').build(title: 'A Post!')
post  = user.get('posts').create(title: 'A Saved Post!')
posts = user.get('posts').all()
 
post  = App.Post.first()
 
# belongsTo 'author' 
user  = post.get('author')

Validations

user = App.User.build()
user.save()         #=> false 
user.get('errors')  #=> {"email": ["Email must be present"]} 
user.set('email''me@gmail.com')
user.save()         #=> true 
user.get('errors')  #=> {} 

Routes

# config/routes.coffee 
App.routes ->
  @match '/login''sessions#new'via: 'get'as: 'login'
  @match '/logout''sessions#destroy'via: 'get'as: 'logout'
 
  @resources 'posts'->
    @resources 'comments'
 
  @namespace 'admin'->
    @resources 'users'
    @resources 'posts'->
      @resources 'comments'
 
  @constraints subdomain: /^api$/->
    @resources 'posts'->
      @resources 'comments'
 
  @match '(/*path)'to: 'application#index'via: 'get'

Controllers

# app/controllers/server/postsController.coffee 
class App.PostsController extends Tower.Controller
  index: ->
    App.Post.all (error, posts) =>
      @render 'index'locals: posts: posts
 
  new: ->
    @post = App.Post.build()
    @render 'new'
 
  create: ->
    @post = App.Post.build(@params.post)
 
    super (success, failure) ->
      @success.html => @render 'posts/edit'
      @success.json => @render text: 'success!'
      @failure.html => @render text: 'Error'status: 404
      @failure.json => @render text: 'Error'status: 404
 
  show: ->
    App.Post.find @params.id(error, post) =>
      @render 'show'
 
  edit: ->
    App.Post.find @params.id(error, post) =>
      @render 'edit'
 
  update: ->
    App.Post.find @params.id(error, post) =>
      post.updateAttributes @params.post(error) =>
        @redirectTo action: 'show'
 
  destroy: ->
    App.Post.find @params.id(error, post) =>
      post.destroy (error) =>
        @redirectTo action: 'index'

Views

Views are all Ember.

Templates

Templates adhere to the Twitter Bootstrap 2.x markup conventions.

The default templating engine is CoffeeCup, which is pure CoffeeScript. It’s much more powerful than Jade, and it’s just as performant if not more so. You can set Jade or any other templating engine as the default by setting Tower.View.engine = "jade" in config/application. Tower uses Mint.js, which is a normalized interface to most of the Node.js templating languages.

Styles

It’s all using Twitter Bootstrap, so check out their docs. http://twitter.github.com/bootstrap/

Actually, all that’s built in! So for the simple case you don’t even need to write anything in your controllers (skinny controllers, fat models). The default implementation is actually a lot more robust than that, just wanted to show a simple example.

Databases

# app/config/server/databases.coffee 
module.exports =
  mongodb:
    development:
      name: 'app-development'
      port: 27017
      host: '127.0.0.1'
    test:
      name: 'app-test'
      port: 27017
      host: '127.0.0.1'
    staging:
      name: 'app-staging'
      port: 27017
      host: '127.0.0.1'
    production:
      name: 'app-production'
      port: 27017
      host: '127.0.0.1'

Mailers

class App.Notification extends Tower.Mailer
  # app/views/mailers/welcome.coffee template 
  @welcome: (user) ->
    @mail to: user.emailfrom: 'me@gmail.com'

Internationalization

# app/config/shared/locales/en.coffee 
module.exports =
  hello: 'world'
  forms:
    titles:
      signup: 'Signup'
  pages:
    titles:
      home: 'Welcome to %{site}'
  posts:
    comments:
      none: 'No comments'
      one: '1 comment'
      other: '%{count} comments'
  messages:
    past:
      none: 'You never had any messages'
      one: 'You had 1 message'
      other: 'You had %{count} messages'
    present:
      one: 'You have 1 message'
    future:
      one: 'You might have 1 message'

Helpers

Since all of the controller/routing code is available on the client, you can go directly through that system just like you would the server.

# Just request the url, and let it do its thing 
Tower.get '/posts'
 
# Same thing, this time passing parameters 
Tower.get '/posts'createdAt: "2011-10-26..2011-10-31"
 
# Dynamic 
Tower.urlFor(Post.first()) #=> "/posts/the-id" 

Those methods pass through the router and client-side middleware, so you have access to request and response objects—just like you would on the server.

Middleware

It’s built on connect, so you can use any middleware library you like.

Assets

# app/config/server/assets.coffee 
module.exports =
  javascripts:
    vendor: [
      '/vendor/javascripts/jquery.js'
      '/vendor/javascripts/underscore.js'
      '/vendor/javascripts/socket.io'
      '/vendor/javascripts/tower.js'
    ]
 
    lib: [
      '/lib/grid.js'
      '/lib/profiler.js'
    ]
 
    application: [
      '/app/models/shared/post.js'
      '/app/models/shared/comment.js'
    ]
 
  stylesheets:
    vendor: [
      '/vendor/stylesheets/reset.css'
    ]
    application: [
      '/app/assets/stylesheets/application.css'
      '/app/assets/stylesheets/theme.css'
    ]

Structure

Here’s the structure of a newly generated app with a Post model:

.
├── app
│   ├── config
│   │   ├── client
│   │   │   ├── bootstrap.coffee
│   │   │   └── watch.coffee
│   │   ├── server
│   │   │   ├── environments
│   │   │   │   ├── development.coffee
│   │   │   │   ├── production.coffee
│   │   │   │   └── test.coffee
│   │   │   ├── initializers
│   │   │   ├── assets.coffee
│   │   │   ├── bootstrap.coffee
│   │   │   ├── credentials.coffee
│   │   │   ├── databases.coffee
│   │   │   └── session.coffee
│   │   └── shared
│   │       ├── locales
│   │       │   └── en.coffee
│   │       ├── application.coffee
│   │       └── routes.coffee
│   ├── controllers
│   │   ├── client
│   │   │   ├── applicationController.coffee
│   │   │   └── postsController.coffee
│   │   └── server
│   │       ├── applicationController.coffee
│   │       └── postsController.coffee
│   ├── models
│   │   ├── client
│   │   ├── server
│   │   └── shared
│   │       └── post.coffee
│   ├── stylesheets
│   │   ├── client
│   │   │   └── application.styl
│   │   └── server
│   │       └── email.styl
│   ├── templates
│   │   ├── server
│   │   │   └── layout
│   │   │       ├── _meta.coffee
│   │   │       └── application.coffee
│   │   └── shared
│   │       ├── layout
│   │       │   ├── _body.coffee
│   │       │   ├── _flash.coffee
│   │       │   ├── _footer.coffee
│   │       │   ├── _header.coffee
│   │       │   ├── _navigation.coffee
│   │       │   └── _sidebar.coffee
│   │       ├── posts
│   │       │   ├── _flash.coffee
│   │       │   ├── _form.coffee
│   │       │   ├── _item.coffee
│   │       │   ├── _list.coffee
│   │       │   ├── _table.coffee
│   │       │   ├── edit.coffee
│   │       │   ├── index.coffee
│   │       │   ├── new.coffee
│   │       │   └── show.coffee
│   │       └── welcome.coffee
│   └── views
│       └── client
│           ├── layout
│           │   └── application.coffee
│           └── posts
│               ├── form.coffee
│               ├── index.coffee
│               └── show.coffee
├── data
│   └── seeds.coffee
├── lib
├── log
├── public
│   ├── fonts
│   ├── images
│   ├── javascripts
│   ├── stylesheets
│   ├── swfs
│   ├── uploads
│   ├── 404.html
│   ├── 500.html
│   ├── favicon.ico
│   ├── humans.txt
│   └── robots.txt
├── scripts
│   └── tower
├── test
│   ├── cases
│   │   ├── controllers
│   │   │   ├── client
│   │   │   └── server
│   │           └── postsControllerTest.coffee
│   │   ├── features
│   │   │   └── client
│   │   └── models
│   │       ├── client
│   │       ├── server
│   │       └── shared
│   │           └── postTest.coffee
│   ├── factories
│   │   └── postFactory.coffee
│   ├── client.coffee
│   ├── mocha.opts
│   └── server.coffee
├── tmp
├── wiki
│   ├── _sidebar.md
│   └── home.md
├── Cakefile
├── grunt.coffee
├── package.json
├── Procfile
├── README.md
└── server.js

All assets are read from /public, which is the compiled output of everything in /app, /lib, /vendor (and wherever else you might put things). The default is to use Stylus for CSS in /app/assets/stylesheets.

By having this assets.coffee file, you can specify exactly how you want to compile your files for the client, to ensure it’s as optimized and cacheable as possible in production.

Minify and Gzip

cake assets:bundle

Push to S3

cake assets:publish

Test

npm test

Run individual test file:

mocha $(find test -name "*persistenceTest.coffee")

Run test matching pattern:

mocha $(find test -name "*persistenceTest.coffee") -g "string property$"

Run tests matching directory and pattern:

mocha $(find test -name "*Test.coffee" | egrep "/*view*/")

Run tests not matching directory and pattern:

# run all tests except for client tests
mocha $(find test -name client -prune -o -name '*Test.coffee' -print)

Examples

Contributing to Tower

git clone https://github.com/viatropos/tower.git
cd tower
npm install
make install-dependencies

Building Tower

You can build tower manually with:

make

Or, you can have it recompile files whenever you change them:

make watch

“Linking” Tower

You can symlink your local tower repository to your global npm node_modules directory, which allows you use it in your apps. That way, if you make changes to the tower repository, you’ll see them in your app! Very useful.

In the tower repo:

npm link

In a tower app:

npm link tower

If you want to try installing tower from the remote npm registry, you can just unlink it and run npm install:

npm unlink tower
npm install tower

Using npm link makes it very easy to mess with the source.

Running Tests

In the tower repo, run server tests with:

make test-server

To run client tests, first compile the test app and start its server:

make build-test-client
make start-test-client

Then run the tests (uses phantomjs)

make test-client

If you don’t have phantomjs, you can install it with:

brew install phantomjs

License

(The MIT License)

Copyright © 2012 Lance Pollard <lancejpollard@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Readme

Keywords

none

Package Sidebar

Install

npm i v-t-test

Weekly Downloads

0

Version

0.4.2-22

License

none

Last publish

Collaborators

  • viatropos