├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── blueprint.js ├── lib ├── blueprint.js └── blueprint │ ├── conf.js │ ├── loader.js │ ├── router.js │ ├── scaffold.js │ └── zone.js ├── package.json ├── templates ├── app.js ├── app │ ├── controllers │ │ └── items.js │ └── models │ │ └── Item.js ├── package.json └── public │ ├── 404.html │ └── css │ └── style.css └── test ├── index.test.js └── scaffold.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | templates/node_modules/ 3 | *.monitor -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.4 4 | - 0.5 5 | - 0.6 6 | - 0.7 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Copyright 2011, Ingk Labs & Edward Hotchkiss. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # blueprint 3 | 4 | [![Build Status](https://secure.travis-ci.org/edwardhotchkiss/blueprint.png)](http://travis-ci.org/edwardhotchkiss/blueprint) 5 | 6 | ## Motivation 7 | 8 | NodeJS is awesome. However; there are not a lot of good resources to get you rolling FAST. 9 | So I wanted to write a lightweight blueprint for a startup. There are certain requirements 10 | that all startups / web based apps have, I'm trying to address/automate them. 11 | 12 | ## Inspirations: Monk (ruby), Sinatra, Rails, Express/Connect 13 | 14 | ## Setup & Usage 15 | 16 | ```bash 17 | $ sudo npm install blueprint -g 18 | $ blueprint init yourappname 19 | ``` 20 | 21 | ## blueprint CLI (blueprint [my_command]) 22 | 23 | Usage: 24 | 25 | help Display usage information 26 | init Initialize a blueprint app 27 | generate [mvc-item] Generate a scaffold 28 | routes Display alls application routes 29 | 30 | -------------------------------------------------------------------------------- /bin/blueprint.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var blueprint = require("../lib/blueprint"); 4 | var scaffold = blueprint.scaffold; 5 | 6 | var args = process.argv.slice(2); 7 | var command = args.shift(); 8 | 9 | if (command === undefined) { 10 | scaffold.usage(); 11 | return false; 12 | }; 13 | 14 | switch(command) { 15 | case "h": 16 | scaffold.usage(); 17 | case "help": 18 | scaffold.usage(); 19 | break 20 | case "--help": 21 | scaffold.usage(); 22 | break; 23 | case "i": 24 | scaffold.init(args[0]); 25 | break; 26 | case "init": 27 | scaffold.init(args[0]); 28 | break; 29 | case "--init": 30 | scaffold.init(args[0]); 31 | break; 32 | default: 33 | scaffold.usage(); 34 | break; 35 | } -------------------------------------------------------------------------------- /lib/blueprint.js: -------------------------------------------------------------------------------- 1 | 2 | exports.createServer = require("./blueprint/router").createServer; 3 | exports.conf = require("./blueprint/conf").conf; 4 | exports.load = require("./blueprint/loader").load; 5 | exports.scaffold = require("./blueprint/scaffold").scaffold; 6 | 7 | /* EOF */ -------------------------------------------------------------------------------- /lib/blueprint/conf.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | blueprint.conf.use("package.json"); 5 | blueprint.conf.get("myPackageProperty"); 6 | 7 | use package.json to expose app config info 8 | 9 | */ 10 | 11 | var fs = require("fs"); 12 | 13 | var conf = exports.conf = { 14 | use:function(confFile) { 15 | confFile = confFile || process.cwd() + "/package.json"; 16 | var data = fs.readFileSync(confFile).toString(); 17 | conf.pkg = JSON.parse(data).blueprint; 18 | }, 19 | get:function(item) { 20 | return conf.pkg[item]; 21 | } 22 | }; 23 | 24 | /* EOF */ -------------------------------------------------------------------------------- /lib/blueprint/loader.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | Automatically load|require 5 | /models and /controllers 6 | 7 | - blueprint.load("models", "/models"); 8 | - blueprint.load("controllers", "/controllers"); 9 | 10 | */ 11 | 12 | var fs = require("fs"); 13 | var path = require("path"); 14 | 15 | function models(directory) { 16 | directory = process.cwd() + (directory || "/app/models"); 17 | fs.readdir(path.normalize(directory), function(error, files) { 18 | if (error) { 19 | if (new RegExp(/ENOENT/).test(error.message)) { 20 | throw new Error("blueprint looks in /app/models/ by default, try 'blueprint --help'"); 21 | } else { 22 | throw new Error(error); 23 | } 24 | } else { 25 | files.forEach(function(file) { 26 | if (file.search(new RegExp(/DS_Store/)) !== -1) { 27 | return; 28 | }; 29 | var model = file.replace(".js", ""); 30 | global[model] = new require(directory+"/"+model); 31 | }); 32 | } 33 | }); 34 | }; 35 | 36 | function controllers(directory, app) { 37 | directory = process.cwd() + directory || "/app/controllers"; 38 | fs.readdir(path.normalize(directory), function(error, files) { 39 | if (error) { 40 | if (new RegExp(/ENOENT/).test(error.message)) { 41 | throw new Error("blueprint looks in /app/controllers/ by default, try 'blueprint --help'"); 42 | } else { 43 | throw new Error(error); 44 | } 45 | } else { 46 | files.forEach(function(file) { 47 | if (file.search(new RegExp(/DS_Store/)) !== -1) { 48 | return; 49 | }; 50 | var controller = file.replace(".js", ""); 51 | require(directory+"/"+controller)(app); 52 | }); 53 | }; 54 | }); 55 | }; 56 | 57 | var load = exports.load = function(which, directory, app) { 58 | if (which === "models") { 59 | models(directory); 60 | } else if (which === "controllers") { 61 | controllers(directory, app); 62 | } else { 63 | throw new Error("usage: blueprint --help"); 64 | }; 65 | }; 66 | 67 | /* EOF */ -------------------------------------------------------------------------------- /lib/blueprint/router.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | blueprint.Router 5 | 6 | http.ServerResponse.send(json/test); 7 | http.ServerResponse.prototype.writeVersionHeader(); 8 | http.ServerResponse.redirect(route, statusCode) 9 | 10 | */ 11 | 12 | var fs = require("fs"); 13 | var http = require("http"); 14 | var path = require("path"); 15 | var mime = require("mime"); 16 | var parse = require("url").parse; 17 | 18 | var zone = require("./zone"); 19 | 20 | http.ServerResponse.prototype.writeVersionHeader = function() { 21 | this.setHeader("X-Powered-By", "blueprint/0.3.3"); 22 | }; 23 | 24 | http.ServerResponse.prototype.send = function(message, code, type) { 25 | type = type || "text/html"; 26 | code = code || 200; 27 | if (typeof(message) === "object") { 28 | message = JSON.stringify(message); 29 | type = "application/json"; 30 | }; 31 | this.writeVersionHeader(); 32 | this.writeHead(code, { "Content-Type": type }); 33 | this.end(message); 34 | }; 35 | 36 | http.ServerResponse.prototype.redirect = function(url, code) { 37 | code = code || 302; 38 | this.writeHead(code, { "Location" : url }); 39 | this.end(); 40 | }; 41 | 42 | /* 43 | 44 | blueprint.Router 45 | blueprint.Router.params 46 | blueprint.Router.routes 47 | 48 | addRoute 49 | - GET 50 | - PUT 51 | - POST 52 | - DELETE 53 | 54 | */ 55 | 56 | var routes = {}; 57 | 58 | function addRoute(path, method, auth, fn) { 59 | routes[path] = {}; 60 | routes[path]["auth"] = auth; 61 | routes[path][method] = fn; 62 | }; 63 | 64 | /* 65 | 66 | middleware 67 | - logger 68 | - static file serving 69 | - 404 70 | 71 | */ 72 | 73 | var logger = function(request, response) { 74 | var host = request.headers.host; 75 | var path = parse(request.url).pathname; 76 | var method = request.method; 77 | var currentTime = new Date(); 78 | var month = currentTime.getMonth() + 1; 79 | var day = currentTime.getDate(); 80 | var year = currentTime.getFullYear(); 81 | var hours = currentTime.getHours(); 82 | var minutes = currentTime.getMinutes(); 83 | var date = month + "/" + day + "/" + year + ":"; 84 | var time = hours + ":" + minutes; 85 | var fullPath = "http://" + host + path; 86 | var msg = date + time + ' "' + method.toUpperCase() + '" ' + fullPath; 87 | console.log("> blueprint: " + msg); 88 | }; 89 | 90 | var staticHandler = function(request, response) { 91 | var staticFile = path.normalize(process.cwd() + "/public" + request.url); 92 | fs.stat(staticFile, function(error, results) { 93 | if (error) { 94 | response.redirect("/404.html", 404); 95 | } else { 96 | var type = mime.lookup(staticFile); 97 | response.writeHead(200, { 98 | "Content-Type": type, 99 | "Content-Length": results.size 100 | }); 101 | fs.createReadStream(staticFile, {"bufferSize" : 32 * 1024 }).pipe(response); 102 | }; 103 | }); 104 | }; 105 | 106 | var handle = function(request, response) { 107 | var pathname = parse(request.url).pathname; 108 | if (routes[pathname] && routes[pathname][request.method]) { 109 | logger(request, response); 110 | if (routes[pathname["auth"]] === true) { 111 | zone(request, response); 112 | }; 113 | var handle = routes[pathname][request.method]; 114 | handle.apply(this, arguments); 115 | } else { 116 | staticHandler(request, response); 117 | }; 118 | }; 119 | 120 | /* 121 | 122 | create an http.Server 123 | server uses Router by default 124 | - handle 125 | 126 | */ 127 | 128 | ["get", "put", "post", "delete"].forEach(function(protocol) { 129 | http.Server.prototype[protocol] = function(path, auth, callback) { 130 | addRoute(path, protocol.toString().toUpperCase(), auth, callback); 131 | }; 132 | }); 133 | 134 | var createServer = exports.createServer = function() { 135 | var Server = http.createServer(); 136 | Server.on("request", function theHandler(request, response) { 137 | handle(request, response); 138 | }); 139 | return Server; 140 | }; 141 | 142 | /* EOF */ -------------------------------------------------------------------------------- /lib/blueprint/scaffold.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | blueprint.scaffold(); 5 | blueprint.scaffold.usage(); 6 | blueprint.scaffold.generate(); 7 | 8 | */ 9 | 10 | var path = require("path"); 11 | var wrench = require("wrench"); 12 | 13 | var scaffold = exports.scaffold = function(command) { 14 | console.log(command); 15 | } 16 | 17 | scaffold.usage = function() { 18 | var help = "blueprint scaffold myRoute myModelItem\n"; 19 | var help = "USAGE:\n"+ 20 | "\thelp Display usage information\n"+ 21 | "\tinit Initialize a blueprint app\n"+ 22 | "\tgenerate [mvc-item] Generate a scaffold\n"+ 23 | "\troutes Display alls application routes\n"; 24 | console.log(help); 25 | }; 26 | 27 | scaffold.init = function(name) { 28 | var source = path.normalize(process.cwd() + "/templates"); 29 | var destination = path.normalize(process.cwd()+"/"+name); 30 | wrench.copyDirSyncRecursive(source, destination); 31 | } 32 | 33 | /* EOF */ -------------------------------------------------------------------------------- /lib/blueprint/zone.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | 4 | blueprint.zone(request, response); 5 | 6 | */ 7 | 8 | var zone = exports.zone = function(request, response) { 9 | if (!request.session || request.session.auth === true) { 10 | response.redirect("/", 302); 11 | }; 12 | }; 13 | 14 | /* EOF */ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"blueprint", 3 | "description":"Blueprint for a Startup. Middleware, & MVC routing over Node.js & Mongoose", 4 | "version":"0.4.3", 5 | "author":"Ingk Labs ", 6 | "maintainers":[ 7 | { "name":"Edward Hotchkiss", "email":"e@ingk.com" }, 8 | { "name":"Max Gfeller", "email":"max.gfeller@gmail.com" } 9 | ], 10 | "repository":{ 11 | "type":"git", 12 | "url":"https://github.com/ingklabs/blueprint" 13 | }, 14 | "dependencies":{ 15 | "mongoose":"2.3.12", 16 | "wrench":"1.3.2", 17 | "ejs":"0.4.3", 18 | "mime":"1.2.4", 19 | "qs":"0.3.2" 20 | }, 21 | "devDependencies":{ 22 | "vows":"0.5.13", 23 | "should":"0.3.2", 24 | "request":"" 25 | }, 26 | "scripts": { 27 | "test": "vows test/*.test.js --spec" 28 | }, 29 | "licenses":[{ 30 | "type":"MIT", 31 | "url":"http://github.com/ingklabs/blueprint/LICENSE" 32 | }], 33 | "main":"./lib/blueprint.js", 34 | "engines":{ 35 | "node":">= 0.4.11" 36 | } 37 | } -------------------------------------------------------------------------------- /templates/app.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | 4 | blog 5 | https://github.com/ingklabs/blueprint/ 6 | 7 | */ 8 | 9 | // load dependencies 10 | var mongoose = require("mongoose"); 11 | var blueprint = require("../lib/blueprint"); 12 | 13 | // http.createServer() 14 | var app = blueprint.createServer(); 15 | 16 | // load package.json 17 | blueprint.conf.use(); 18 | 19 | /* 20 | totally optional 21 | load|require /models 22 | load|require /controllers 23 | */ 24 | 25 | blueprint.load("models", "/app/models/"); 26 | blueprint.load("controllers", "/app/controllers/", app); 27 | 28 | // connect to mongodb via mongoose 29 | mongoose.connect(blueprint.conf.get("mongodb")); 30 | 31 | // mongoose listeners 32 | mongoose.connection.on("error", function(error) { 33 | throw new Error(error); 34 | }); 35 | 36 | // http.Server.listen() 37 | app.listen(8000); 38 | 39 | /* EOF */ -------------------------------------------------------------------------------- /templates/app/controllers/items.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | default route 5 | fetch all "Item"'s 6 | 7 | */ 8 | 9 | module.exports = controllers = function(app) { 10 | app.get("/", false, function(request, response) { 11 | Item.find({}, function(error, items) { 12 | if (error) { 13 | console.error(error); 14 | } else { 15 | response.send(items); 16 | } 17 | }); 18 | }); 19 | }; -------------------------------------------------------------------------------- /templates/app/models/Item.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | /app/models/Item 5 | 6 | */ 7 | 8 | var mongoose = require("mongoose"); 9 | var Schema = mongoose.Schema; 10 | var ObjectId = Schema.ObjectId; 11 | 12 | var ItemSchema = new Schema({ 13 | id : ObjectId, 14 | title : { type : String }, 15 | content : { type : String }, 16 | created_at : { type : Date, default: Date.now } 17 | }); 18 | 19 | var Item = mongoose.model("Item", ItemSchema); 20 | 21 | /* 22 | Setup a small mongodb doc 23 | */ 24 | 25 | /*Item.find({}, function(error, items) { 26 | if (error) { 27 | console.error(error); 28 | } else { 29 | items.forEach(function(item) { 30 | item.remove(); 31 | }); 32 | }; 33 | }); 34 | 35 | var itemA = new Item({ 36 | title : "Item A", 37 | content : "This is item A's content!" 38 | }); 39 | 40 | var itemB = new Item({ 41 | title : "Item B", 42 | content : "This is item B's content, excited yet?" 43 | }); 44 | 45 | itemA.save(); 46 | itemB.save(); 47 | */ 48 | 49 | module.exports = Item; 50 | 51 | /* EOF */ -------------------------------------------------------------------------------- /templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"yourappname", 3 | "description":"", 4 | "version":"0.0.0", 5 | "dependencies":{ 6 | "blueprint":"0.4.0", 7 | "mongoose":"2.3.12" 8 | }, 9 | "engines":{ 10 | "node":">= 0.4.11" 11 | }, 12 | "blueprint":{ 13 | "mongodb":"mongodb://localhost/test" 14 | } 15 | } -------------------------------------------------------------------------------- /templates/public/404.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hankejh/blueprint/330faa8827d095c27d2ff7ea59ea6a8263318c28/templates/public/404.html -------------------------------------------------------------------------------- /templates/public/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | body { 8 | font-size: 12px; 9 | line-height: 18px; 10 | color: #000; 11 | font-family: "Lucida Grande", sans-serif; 12 | } 13 | 14 | .clearfix:after { 15 | content: "."; 16 | display: block; 17 | clear: both; 18 | visibility: hidden; 19 | line-height: 0; 20 | height: 0; 21 | } 22 | 23 | @font-face { 24 | font-family: "CustomDidot"; 25 | src: url(../fonts/Didot.ttf) format("truetype"); 26 | } 27 | 28 | a, a:visited, a:hover { 29 | color: #000; 30 | text-decoration: none; 31 | } 32 | 33 | #github { 34 | position: absolute; 35 | top: 0; 36 | right: 0; 37 | } 38 | 39 | #github a { 40 | text-decoration: none; 41 | } 42 | 43 | #github a img { 44 | border: none; 45 | } 46 | 47 | #container { 48 | width: 600px; 49 | position: relative; 50 | margin: 0 auto 0 auto; 51 | margin-top: 120px; 52 | } 53 | 54 | h1 { 55 | font-family: "CustomDidot", "Helvetica Neue", Helvetica, Tahoma; 56 | font-size: 36px; 57 | font-weight: normal; 58 | text-transform: uppercase; 59 | } 60 | 61 | #header h1 { 62 | padding-bottom: 20px; 63 | color: #000; 64 | text-transform: uppercase; 65 | } 66 | 67 | #header h1 { 68 | color: #777; 69 | font-style: italic; 70 | } 71 | 72 | #header h1 a { 73 | color: #000; 74 | border-bottom: 1px solid #000; 75 | font-style: none; 76 | } 77 | 78 | .post { 79 | margin: 50px 0 50px 0; 80 | } 81 | 82 | .post h1 { 83 | font-size: 24px; 84 | line-height: 30px; 85 | margin-bottom: 7px; 86 | text-transform: uppercase; 87 | } 88 | 89 | .post p { 90 | color: #000; 91 | } 92 | 93 | .post p.meta { 94 | margin: 10px 0 10px 0; 95 | } 96 | 97 | #footer { 98 | } 99 | 100 | #footer a { 101 | color: #000; 102 | border-bottom: 1px solid #000; 103 | } 104 | 105 | /* EOF */ -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | 2 | var vows = require("vows"); 3 | var assert = require("assert"); 4 | var should = require("should"); 5 | var request = require("request"); 6 | 7 | var blueprint = require("../lib/blueprint"); 8 | 9 | vows.describe("General Module Tests").addBatch({ 10 | "when requiring blueprint":{ 11 | topic:function(){ 12 | return blueprint; 13 | }, 14 | "blueprint should be an object":function(topic){ 15 | topic.should.be.a("object"); 16 | } 17 | }, 18 | "when creating an index route with a response":{ 19 | topic:function(){ 20 | var app = blueprint.createServer(); 21 | app.get("/", false, function(request, response) { 22 | response.send({message:"don't taze me bro!"}); 23 | }); 24 | app.listen(9000); 25 | request("http://localhost:9000/", this.callback); 26 | }, 27 | "we should be able to make a request and get back text/html, a 200 response code":function(error, response, body){ 28 | assert.equal(error, null); 29 | assert.equal(response.statusCode, 200); 30 | assert.equal(response.headers["content-type"], "application/json"); 31 | } 32 | }, 33 | "when we serve up a non existing route":{ 34 | topic:function(){ 35 | request("http://localhost:9000/fake-stuff/", this.callback); 36 | }, 37 | "we should be able to make a request and get back text/html, with a 404 response code":function(error, response, body){ 38 | assert.equal(error, null); 39 | assert.equal(response.statusCode, 404); 40 | } 41 | } 42 | }).export(module); 43 | 44 | /* EOF */ -------------------------------------------------------------------------------- /test/scaffold.test.js: -------------------------------------------------------------------------------- 1 | 2 | var vows = require("vows"); 3 | var assert = require("assert"); 4 | var should = require("should"); 5 | var request = require("request"); 6 | 7 | var blueprint = require("../lib/blueprint"); 8 | 9 | vows.describe("Scaffold Tests").addBatch({ 10 | "when requiring blueprint.scaffold":{ 11 | topic:function(){ 12 | return blueprint.scaffold; 13 | }, 14 | "blueprint.scaffold should be a function":function(topic){ 15 | topic.should.be.a("function"); 16 | } 17 | } 18 | }).export(module); 19 | 20 | /* EOF */ --------------------------------------------------------------------------------