README
pine2e
Pinetwo's pluggable stack for twelve-factor web apps based on Express.js and PostgreSQL. Runs on Heroku and similar environments.
Under development. Normally, parts of the stack are developed elsewhere and then extracted from production apps.
How to create a Pine2e app
Create a new folder:
mkdir example
cd example
git init
Create .gitignore:
.env
.env.*
Create package.json:
{
"name": "example",
"version": "0.0.0",
"private": true
}
Install npm packages:
npm install pine2e --save
npm install debug --save
npm install supervisor --save-dev
npm install grunt grunt-es6-transpiler grunt-contrib-less --save-dev
Create Gruntfile.js to set up p2e:...
tasks, ES6 transpiler and LESS compiler:
module.exports = function(grunt) {
grunt.initConfig({
es6transpiler: {
server: {
expand: true,
cwd: 'src/',
src: '**/*.js',
dest: 'lib/'
},
client: {
expand: true,
cwd: 'assets/js6/',
src: '**/*.js',
dest: 'assets/js/',
options: {
globals: {
"jQuery": false
}
}
}
},
less: {
client: {
files: {
"assets/css/styles.css": "assets/less/styles.less"
}
}
}
});
grunt.loadNpmTasks('pine2e');
grunt.loadNpmTasks('grunt-es6-transpiler');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.registerTask('server', ['es6transpiler:server']);
grunt.registerTask('client', ['es6transpiler:client', 'less']);
grunt.registerTask('default', ['client', 'server']);
};
Create server.js:
require('./app').startServer();
Create app.js:
module.exports = require('pine2e').initializeRootApp(__dirname);
Create src/config.js:
exports.configureApp = function(app) {
};
exports.configureRootApp = function(app) {
};
Create src/routes.js:
module.exports = function(app) {
app.get('/', (req, res) => {
res.render('home');
})
}
Create views/home.jade:
extends layout
block content
.container
h1 Hello, world!
Create views/layout.jade (this example uses Bootstrap, Google Fonts and LiveReload):
doctype html
html(lang="en")
head
meta(charset='utf8')
meta(http-equiv="X-UA-Compatible", content="IE=edge")
link(href='http://fonts.googleapis.com/css?family=Droid+Sans:400,700', rel='stylesheet', type='text/css')
link(href='/bower_components/bootstrap/dist/css/bootstrap.min.css', rel='stylesheet', type='text/css')
link(href='/assets/css/styles.css', rel='stylesheet', type='text/css')
<!--[if lt IE 9]>
script(src="/bower_components/html5shiv/dist/html5shiv.min.js")
script(src="/bower_components/respond/dest/respond.min.js")
<![endif]-->
title Example
body
header
.container
.col-sm-8
p Hello
.col-sm-4
p World
block content
script(src="/bower_components/jquery/jquery.min.js")
script(src="/bower_components/bootstrap/dist/js/bootstrap.min.js")
script(src="/assets/js/app.js")
script.
if ((location.host || '').split(':')[0] === 'localhost')
document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')
Create assest/less/styles.less:
h1 {
color: red;
}
Create assets/js6/app.js:
jQuery(function($) {
$('h1').click((e) => {
e.preventDefault();
$('h1').after('<p>Hello from JavaScript</p>');
})
});
Compile all assets (note: LiveReload is recommended for ongoing compilation):
grunt
Create bower.json:
{
"private": true,
"dependencies": {
"jquery": "~1.9.0",
"bootstrap": "~3.1.0",
"html5shiv": "~3.7.1",
"respond": "~1.4.2"
}
}
then install Bower components:
bower install
Create Procfile for Heroku:
web: node server.js
Create Procfile.dev for auto-restarting development server:
web: ./node_modules/.bin/supervisor -i assets,migrations,views,src,test server.js
If you use CoffeeScript and/or Streamline.js, you need to add -x
and -e
to Procfile.dev:
web: ./node_modules/.bin/supervisor -x ./node_modules/.bin/_coffee -e '_coffee|coffee' -i assets,migrations,views,src,test server.js
Start your local PostgreSQL server (Postgres.app recommended).
Create a dev database in the local PostgreSQL installation:
echo 'CREATE DATABASE example_dev' | psql -d postgres
Set up your development environment in .env
pointing to your local PostgreSQL database:
PORT=5000
DATABASE_URL=postgres://andreyvit:@localhost:5432/example_dev
Create staging and production Heroku apps:
heroku apps:create --remote staging example-staging
heroku apps:create --remote production example
Add PostgreSQL addon with automatic backups:
heroku addons:add --app example-staging heroku-postgresql
heroku pg:promote --app example-staging HEROKU_POSTGRESQL_YELLOW_URL # use your URL
heroku addons:add --app example-staging pgbackups:auto-month
heroku addons:add --app example heroku-postgresql
heroku pg:promote --app example HEROKU_POSTGRESQL_AMBER_URL # use your URL
heroku addons:add --app example pgbackups:auto-month
Set up SESSION_SECRET, a required config variable to prevent session hijacking attacks:
heroku config:set --app example-staging "SESSION_SECRET=asdfghjklasdfghjkl"
heroku config:set --app example "SESSION_SECRET=zxcvbnmzxcvbnm"
echo "SESSION_SECRET=qwertyuiop" >>.env
Set up logging:
heroku config:set --app example-staging "DEBUG=sql,p2e:*,app:*"
heroku config:set --app example "DEBUG=sql,p2e:*,app:*"
echo "DEBUG=sql,p2e:*,app:*" >>.env
This enables logging of all SQL statements, all Pine2e messages and all messages coming from your app (as long as you use app:smt
for your app's logging via the debug module). In the future you may want to log less in production.
Dump Heroku config into local env files:
heroku config -s --app example-staging >.env.staging
echo "HEROKU_APP=myapp-staging" >>.env.staging
heroku config -s --app example >.env.production
echo "HEROKU_APP=example" >>.env.production
Append ?ssl=1
to each DATABASE_URL (in .env.staging
and .env.production
).
You can also set GIT_REMOTE
to the name of the Heroku remotes, if they don't match the environment names (“staging”, “production”).
Run the app using:
foreman start
Run the app in autorestarting mode using:
foreman start -f Procfile.dev
Lock dependency versions for reproducable deployments (you need to rerun this command after any package.json dependency changes):
npm shrinkwrap
Commit everything into Git, then deploy to Heroku using:
grunt p2e:deploy:staging
grunt p2e:deploy:production
Configuration and environments
Pine2e apps follow Heroku's twelve-factor apps methodology. In particular, the app's configuration is loaded from environment variables like PORT
and DATABASE_URL
. (Unlike, say, Rails, which loads its configuration from YAML files like config/database.yml
.)
Normally, you set up a Procfile
and use the Foreman gem to run the app. Foreman loads the environment variables from a file called .env
, although the file name can be provided from the command line.
Pine2e embraces the notion of .env
file, extending the idea to multiple environments (configurations) of the app. You can use any environment names you want, but the following four are special and conventional:
dev
, for running the app during developmenttest
, to use when running tests (its database gets be wiped out by each test run)staging
production
(Pine2e will take extra care when deploying to production, and will refuse to perform destructive actions with production database)
(Any other environment names you might use are treated exactly like staging
.)
The configuration for the dev environment is stored in .env
(so that it's the default one used by Foreman), and other environments can be configured in .env.test
, .env.staging
, .env.production
, etc.
In addition to the normal Heroku variables you may set up via Heroku commands and find in heroku config -s
output, Pine2e defines the following ones:
HEROKU_APP
specifies the name of the Heroku app for the given environment (frequently passed as--app $HEROKU_APP
to Heroku commands); this must be specified for staging and production envs (i.e..env.staging
and.env.production
), otherwise many Grunt commands won't workGIT_REMOTE
specifies the name of the Git deployment remote for the given environment, if it's different from the name of the environment
Grunt tasks
Be sure to npm install -g grunt-cli
if you haven't already.
To create a migration:
grunt p2e:new:migration:create-widget
To execute migrations:
grunt p2e:migrate:dev
grunt p2e:migrate:test
grunt p2e:migrate:staging
grunt p2e:migrate:production
To dump the schema of ‘dev’ environment's database into schema.sql:
grunt p2e:schema:dump
To copy the database from production to staging, overwriting the staging database:
grunt p2e:db:copy:production:staging
To deploy:
p2e:deploy:staging
p2e:deploy:production
To run an arbitrary Heroku command in the given environment's app:
p2e:heroku:staging:ps
p2e:heroku:staging:config
p2e:heroku:staging:pgbackups:restore
# note: currently there's no way to pass arguments
Tests
Uses mocha, run npm test
to execute tests.
The MIT License
Copyright (c) 2014 Andrey Tarantsov (andrey@tarantsov.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.