├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── index.js └── templates │ ├── Gruntfile.js │ ├── README.md.erb │ ├── _app.js │ ├── _bower.json │ ├── _generator.json │ ├── _package.json │ ├── bowerrc │ ├── editorconfig │ ├── gitignore │ ├── jshintrc │ ├── models │ └── _index.js │ └── public │ ├── _index.html │ ├── css │ └── app.css │ ├── js │ ├── _app.js │ └── home │ │ └── _home-controller.js │ └── views │ └── home │ └── _home.html ├── entity ├── index.js └── templates │ ├── _generator.json │ ├── models │ └── _entity.js │ ├── public │ ├── js │ │ └── entity │ │ │ ├── _entity-controller.js │ │ │ ├── _entity-router.js │ │ │ └── _entity-service.js │ └── views │ │ └── entity │ │ └── _entities.html │ └── routes │ └── _entities.js ├── package.json └── test ├── test-creation.js └── test-load.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | temp/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 4, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.8' 5 | before_install: 6 | - currentfolder=${PWD##*/} 7 | - if [ "$currentfolder" != 'generator-angular-express-sequelize' ]; then cd .. && eval "mv $currentfolder generator-angular-express-sequelize" && cd generator-angular-express-sequelize; fi 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Robert Yokota 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Angular-Express-Sequelize generator 2 | 3 | A [Yeoman](http://yeoman.io) generator for [AngularJS](http://angularjs.org) and [Express](http://expressjs.com) with [Sequelize](http://sequelizejs.com). 4 | 5 | Express is a Javascript-based micro-framework. For AngularJS integration with other micro-frameworks, see https://github.com/rayokota/MicroFrameworkRosettaStone. 6 | 7 | ## Installation 8 | 9 | Install [Git](http://git-scm.com) and [node.js](http://nodejs.org). The development mode also requires [SQLite](http://www.sqlite.org). 10 | 11 | Install Yeoman: 12 | 13 | npm install -g yo 14 | 15 | Install the Angular-Express-Sequelize generator: 16 | 17 | npm install -g generator-angular-express-sequelize 18 | 19 | The above prerequisites can be installed to a VM using the [Angular-Express-Sequelize provisioner](https://github.com/rayokota/provision-angular-express-sequelize). 20 | 21 | ## Creating an Express service 22 | 23 | In a new directory, generate the service: 24 | 25 | yo angular-express-sequelize 26 | 27 | Run the service: 28 | 29 | node app.js 30 | 31 | Your service will run at [http://localhost:3000](http://localhost:3000). 32 | 33 | 34 | ## Creating a persistent entity 35 | 36 | Generate the entity: 37 | 38 | yo angular-express-sequelize:entity [myentity] 39 | 40 | You will be asked to specify attributes for the entity, where each attribute has the following: 41 | 42 | - a name 43 | - a type (String, Integer, Float, Boolean, Date, Enum) 44 | - for a String attribute, an optional minimum and maximum length 45 | - for a numeric attribute, an optional minimum and maximum value 46 | - for a Date attribute, an optional constraint to either past values or future values 47 | - for an Enum attribute, a list of enumerated values 48 | - whether the attribute is required 49 | 50 | Files that are regenerated will appear as conflicts. Allow the generator to overwrite these files as long as no custom changes have been made. 51 | 52 | Run the service: 53 | 54 | node app.js 55 | 56 | A client-side AngularJS application will now be available by running 57 | 58 | grunt server 59 | 60 | The Grunt server will run at [http://localhost:9000](http://localhost:9000). It will proxy REST requests to the Express service running at [http://localhost:3000](http://localhost:3000). 61 | 62 | At this point you should be able to navigate to a page to manage your persistent entities. 63 | 64 | The Grunt server supports hot reloading of client-side HTML/CSS/Javascript file changes. 65 | 66 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'), 3 | path = require('path'), 4 | yeoman = require('yeoman-generator'), 5 | _ = require('lodash'), 6 | _s = require('underscore.string'), 7 | pluralize = require('pluralize'), 8 | asciify = require('asciify'); 9 | 10 | var AngularExpressSequelizeGenerator = module.exports = function AngularExpressSequelizeGenerator(args, options, config) { 11 | yeoman.generators.Base.apply(this, arguments); 12 | 13 | this.on('end', function () { 14 | this.installDependencies({ skipInstall: options['skip-install'] }); 15 | }); 16 | 17 | this.pkg = JSON.parse(this.readFileAsString(path.join(__dirname, '../package.json'))); 18 | }; 19 | 20 | util.inherits(AngularExpressSequelizeGenerator, yeoman.generators.Base); 21 | 22 | AngularExpressSequelizeGenerator.prototype.askFor = function askFor() { 23 | 24 | var cb = this.async(); 25 | 26 | console.log('\n' + 27 | '+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+\n' + 28 | '|a|n|g|u|l|a|r| |e|x|p|r|e|s|s| |s|e|q|u|e|l|i|z|e| |g|e|n|e|r|a|t|o|r|\n' + 29 | '+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+\n' + 30 | '\n'); 31 | 32 | var prompts = [{ 33 | type: 'input', 34 | name: 'baseName', 35 | message: 'What is the name of your application?', 36 | default: 'myapp' 37 | }]; 38 | 39 | this.prompt(prompts, function (props) { 40 | this.baseName = props.baseName; 41 | 42 | cb(); 43 | }.bind(this)); 44 | }; 45 | 46 | AngularExpressSequelizeGenerator.prototype.app = function app() { 47 | 48 | this.entities = []; 49 | this.resources = []; 50 | this.generatorConfig = { 51 | "baseName": this.baseName, 52 | "entities": this.entities, 53 | "resources": this.resources 54 | }; 55 | this.generatorConfigStr = JSON.stringify(this.generatorConfig, null, '\t'); 56 | 57 | this.template('_generator.json', 'generator.json'); 58 | this.template('_package.json', 'package.json'); 59 | this.template('_bower.json', 'bower.json'); 60 | this.template('bowerrc', '.bowerrc'); 61 | this.template('Gruntfile.js', 'Gruntfile.js'); 62 | this.copy('gitignore', '.gitignore'); 63 | 64 | var modelsDir = 'models/' 65 | var publicDir = 'public/' 66 | var routesDir = 'routes/' 67 | var viewsDir = 'views/' 68 | this.mkdir(modelsDir); 69 | this.mkdir(publicDir); 70 | this.mkdir(routesDir); 71 | this.mkdir(viewsDir); 72 | 73 | this.template('_app.js', 'app.js'); 74 | this.template('models/_index.js', modelsDir + 'index.js'); 75 | 76 | var publicCssDir = publicDir + 'css/'; 77 | var publicJsDir = publicDir + 'js/'; 78 | var publicViewDir = publicDir + 'views/'; 79 | this.mkdir(publicCssDir); 80 | this.mkdir(publicJsDir); 81 | this.mkdir(publicViewDir); 82 | this.template('public/_index.html', publicDir + 'index.html'); 83 | this.copy('public/css/app.css', publicCssDir + 'app.css'); 84 | this.template('public/js/_app.js', publicJsDir + 'app.js'); 85 | this.template('public/js/home/_home-controller.js', publicJsDir + 'home/home-controller.js'); 86 | this.template('public/views/home/_home.html', publicViewDir + 'home/home.html'); 87 | }; 88 | 89 | AngularExpressSequelizeGenerator.prototype.projectfiles = function projectfiles() { 90 | this.copy('editorconfig', '.editorconfig'); 91 | this.copy('jshintrc', '.jshintrc'); 92 | }; 93 | -------------------------------------------------------------------------------- /app/templates/Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var proxySnippet = require('grunt-connect-proxy/lib/utils').proxyRequest; 4 | 5 | module.exports = function (grunt) { 6 | require('load-grunt-tasks')(grunt); 7 | require('time-grunt')(grunt); 8 | 9 | grunt.initConfig({ 10 | yeoman: { 11 | // configurable paths 12 | app: require('./bower.json').appPath || 'public', 13 | dist: 'public' 14 | }, 15 | sync: { 16 | dist: { 17 | files: [{ 18 | cwd: '<%%= yeoman.app %>', 19 | dest: '<%%= yeoman.dist %>', 20 | src: '**' 21 | }] 22 | } 23 | }, 24 | watch: { 25 | options: { 26 | livereload: 35729 27 | }, 28 | src: { 29 | files: [ 30 | '<%%= yeoman.app %>/*.html', 31 | '<%%= yeoman.app %>/css/**/*', 32 | '<%%= yeoman.app %>/js/**/*', 33 | '<%%= yeoman.app %>/views/**/*' 34 | ], 35 | //tasks: ['sync:dist'] 36 | } 37 | }, 38 | connect: { 39 | proxies: [ 40 | { 41 | context: '/<%= baseName %>', 42 | host: 'localhost', 43 | port: 3000, 44 | https: false, 45 | changeOrigin: false 46 | } 47 | ], 48 | options: { 49 | port: 9000, 50 | // Change this to '0.0.0.0' to access the server from outside. 51 | hostname: 'localhost', 52 | livereload: 35729 53 | }, 54 | livereload: { 55 | options: { 56 | open: true, 57 | base: [ 58 | '<%%= yeoman.app %>' 59 | ], 60 | middleware: function (connect) { 61 | return [ 62 | proxySnippet, 63 | connect.static(require('path').resolve('public')) 64 | ]; 65 | } 66 | } 67 | }, 68 | /* 69 | dist: { 70 | options: { 71 | base: '<%%= yeoman.dist %>' 72 | } 73 | } 74 | */ 75 | }, 76 | // Put files not handled in other tasks here 77 | copy: { 78 | dist: { 79 | files: [{ 80 | expand: true, 81 | dot: true, 82 | cwd: '<%%= yeoman.app %>', 83 | dest: '<%%= yeoman.dist %>', 84 | src: '**' 85 | }] 86 | }, 87 | }, 88 | // Test settings 89 | karma: { 90 | unit: { 91 | configFile: 'test/config/karma.conf.js', 92 | singleRun: true 93 | } 94 | }, 95 | bowercopy: { 96 | options: { 97 | destPrefix: '<%%= yeoman.app %>' 98 | }, 99 | test: { 100 | files: { 101 | 'test/lib/angular-mocks': 'angular-mocks', 102 | 'test/lib/angular-scenario': 'angular-scenario' 103 | } 104 | } 105 | } 106 | }); 107 | 108 | grunt.registerTask('server', function (target) { 109 | grunt.task.run([ 110 | //'copy:dist', 111 | 'configureProxies', 112 | 'connect:livereload', 113 | 'watch' 114 | ]); 115 | }); 116 | }; 117 | -------------------------------------------------------------------------------- /app/templates/README.md.erb: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | Blah blah 4 | 5 | -------------------------------------------------------------------------------- /app/templates/_app.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , bodyParser = require('body-parser') 3 | , errorHandler = require('errorhandler') 4 | , methodOverride = require('method-override') 5 | , morgan = require('morgan') 6 | , http = require('http') 7 | , path = require('path') 8 | , db = require('./models') 9 | <% _.each(entities, function (entity) { %> 10 | , <%= pluralize(entity.name) %> = require('./routes/<%= pluralize(entity.name) %>')<% }); %> 11 | 12 | var app = express() 13 | 14 | // all environments 15 | app.set('port', process.env.PORT || 3000) 16 | app.set('views', __dirname + '/views') 17 | app.set('view engine', 'jade') 18 | app.use(morgan('dev')) 19 | app.use(bodyParser()) 20 | app.use(methodOverride()) 21 | app.use(express.static(path.join(__dirname, 'public'))) 22 | 23 | // development only 24 | if ('development' === app.get('env')) { 25 | app.use(errorHandler()) 26 | } 27 | 28 | <% _.each(entities, function (entity) { %> 29 | app.get('/<%= baseName %>/<%= pluralize(entity.name) %>', <%= pluralize(entity.name) %>.findAll) 30 | app.get('/<%= baseName %>/<%= pluralize(entity.name) %>/:id', <%= pluralize(entity.name) %>.find) 31 | app.post('/<%= baseName %>/<%= pluralize(entity.name) %>', <%= pluralize(entity.name) %>.create) 32 | app.put('/<%= baseName %>/<%= pluralize(entity.name) %>/:id', <%= pluralize(entity.name) %>.update) 33 | app.del('/<%= baseName %>/<%= pluralize(entity.name) %>/:id', <%= pluralize(entity.name) %>.destroy) 34 | <% }); %> 35 | 36 | db 37 | .sequelize 38 | .sync() 39 | .complete(function(err) { 40 | if (err) { 41 | throw err 42 | } else { 43 | http.createServer(app).listen(app.get('port'), function() { 44 | console.log('Express server listening on port ' + app.get('port')) 45 | }) 46 | } 47 | }) 48 | -------------------------------------------------------------------------------- /app/templates/_bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= _.camelize(baseName) %>", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular-bootstrap": "~0.10.0", 6 | "angular-resource": "~1.2.13", 7 | "angular-route": "~1.2.13", 8 | "angular-ui-date": "~0.0.3", 9 | "angular": "~1.2.13", 10 | "bootstrap-css": "~3.0.0", 11 | "jquery": "~2.1.0", 12 | "lodash": "~2.4.1" 13 | }, 14 | "devDependencies": { 15 | "angular-mocks": "~1.2.13", 16 | "angular-scenario": "~1.2.13" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/templates/_generator.json: -------------------------------------------------------------------------------- 1 | <%= generatorConfigStr %> 2 | -------------------------------------------------------------------------------- /app/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= _.slugify(baseName) %>", 3 | "version": "0.0.0", 4 | "description": "Description for <%= baseName %>", 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "express": "4.1.1", 10 | "body-parser": "~1.0.0", 11 | "errorhandler": "~1.0.0", 12 | "method-override": "~1.0.0", 13 | "morgan": "~1.0.0", 14 | "sequelize": "~2.0.0-beta.2", 15 | "sqlite3": "~2.1.5", 16 | "lodash": "~2.4.1" 17 | }, 18 | "devDependencies": { 19 | "grunt": "~0.4.2", 20 | "grunt-autoprefixer": "~0.4.2", 21 | "grunt-bowercopy": "~0.4.1", 22 | "grunt-bower-install": "~0.7.0", 23 | "grunt-concurrent": "~0.4.2", 24 | "grunt-connect-proxy": "~0.2.0", 25 | "grunt-contrib-clean": "~0.5.0", 26 | "grunt-contrib-concat": "~0.3.0", 27 | "grunt-contrib-connect": "~0.5.0", 28 | "grunt-contrib-copy": "~0.4.1", 29 | "grunt-contrib-cssmin": "~0.7.0", 30 | "grunt-contrib-htmlmin": "~0.1.3", 31 | "grunt-contrib-imagemin": "~0.4.0", 32 | "grunt-contrib-jshint": "~0.7.2", 33 | "grunt-contrib-uglify": "~0.2.7", 34 | "grunt-contrib-watch": "~0.5.3", 35 | "grunt-karma": "~0.6.2", 36 | "grunt-modernizr": "~0.4.1", 37 | "grunt-ngmin": "~0.0.3", 38 | "grunt-rev": "~0.1.0", 39 | "grunt-svgmin": "~0.3.0", 40 | "grunt-sync": "~0.0.5", 41 | "grunt-usemin": "~2.0.2", 42 | "load-grunt-tasks": "~0.2.0", 43 | "time-grunt": "0.2.3", 44 | "karma" : "~0.10.8", 45 | "karma-junit-reporter" : "~0.1.0", 46 | "karma-jasmine" : "~0.1.0", 47 | "karma-ng-scenario" : "~0.1.0" 48 | }, 49 | "engines": { 50 | "node": ">=0.8.15" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/templates/bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/lib", 3 | "json": "bower.json" 4 | } 5 | -------------------------------------------------------------------------------- /app/templates/editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /app/templates/gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *~ 4 | node_modules 5 | -------------------------------------------------------------------------------- /app/templates/jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 4, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true 21 | } 22 | -------------------------------------------------------------------------------- /app/templates/models/_index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , path = require('path') 3 | , Sequelize = require('sequelize') 4 | , lodash = require('lodash') 5 | , sequelize = new Sequelize('sequelize_test', 'root', null, { 6 | dialect: "sqlite", // or 'sqlite', 'postgres', 'mariadb' 7 | storage: "/tmp/my.db", 8 | }) 9 | , db = {} 10 | 11 | fs 12 | .readdirSync(__dirname) 13 | .filter(function(file) { 14 | return ((file.indexOf('.') !== 0) && (file !== 'index.js') && (file.slice(-3) == '.js')) 15 | }) 16 | .forEach(function(file) { 17 | var model = sequelize.import(path.join(__dirname, file)) 18 | db[model.name] = model 19 | }) 20 | 21 | Object.keys(db).forEach(function(modelName) { 22 | if (db[modelName].hasOwnProperty('associate')) { 23 | db[modelName].associate(db) 24 | } 25 | }) 26 | 27 | module.exports = lodash.extend({ 28 | sequelize: sequelize, 29 | Sequelize: Sequelize 30 | }, db) 31 | -------------------------------------------------------------------------------- /app/templates/public/_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |This is your homepage, ready for editing
6 | 7 |8 | If you like this Angular-Express-Sequelize generator, please give us a star at Github! 9 |
10 |ID | 74 | <% _.each(attrs, function (attr) { %> 75 |<%= attr.attrName %> | 76 | <% }); %> 77 ||
---|---|---|
{{<%= name %>.id}} | 82 | <% _.each(attrs, function (attr) { %> 83 |{{<%= name %>.<%= attr.attrName %> <% if (attr.attrType === 'Date') { %> | date:'yyyy-MM-dd'<% } %>}} | 84 | <% }); %> 85 |86 | 91 | 96 | | 97 |