├── .gitignore ├── README.md ├── package.json ├── public ├── 448c34a56d699c29117adc64c43affeb.woff2 ├── 89889688147bd7575d6327160d64e760.svg ├── bundle.js ├── e18bbf611f2a2e43afc071aa2f4e1512.ttf ├── f4769f9bdb7466be65088239c12046d1.eot ├── fa2772327f55d8198301fdb8bcfc8158.woff └── index.html ├── server.js ├── src ├── about │ └── about.html ├── config.js ├── css │ ├── master.scss │ └── todos.scss ├── factories │ └── todo-factory.js ├── index.js ├── server │ ├── db │ │ └── db.js │ ├── routes.js │ └── todos │ │ └── routes.js └── todos │ ├── todos.html │ └── todos.js ├── webpack.config.js └── webpack.prod.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Super MEAN Stack 2 | > An Angular + Node/Express + Webpack + MongoDB + SASS + Babel/ES6 + ui-router + Bootstrap starter kit 3 | 4 | *Note: This repository contains some minor updates that are not in the YouTube tutorial video.* 5 | 6 | ## Live App Demo 7 | 8 | Please don't abuse--although I realize I have no control over this :). 9 | 10 | http://super-mean-stack.herokuapp.com/ 11 | 12 | ![Alt text](http://i288.photobucket.com/albums/ll175/michaelcheng429/Screen%20Shot%202016-02-02%20at%2011.10.48%20PM_zpsznagbxtb.png) 13 | 14 | ## Setup 15 | 16 | #### Option 1: Main Repository 17 | 1. `git clone git@github.com:michaelcheng429/super-mean-stack.git` 18 | 2. `cd super-mean-stack` 19 | 3. `npm install` 20 | 21 | #### Option 2: Yeoman Generator 22 | 1. `npm install -g yo` 23 | 2. `npm i -g generator-super-mean-stack` 24 | 25 | ## Usage 26 | 27 | ### Development 28 | 1. Make sure MongoDB is running (`mongod` from MongoDB bin directory) 29 | 2. `npm run dev` 30 | 31 | ### Production/Deployment (e.g., to Heroku) 32 | 33 | Make sure Heroku Toolbelt is installed (https://devcenter.heroku.com/articles/getting-started-with-nodejs#set-up). 34 | 35 | 1. `npm run build` 36 | 2. `git init` 37 | 3. `heroku create {name-of-app}` 38 | 4. `git push heroku master` 39 | 5. `heroku addons:create mongolab:sandbox` 40 | 41 | #### Troubleshooting 42 | 1. Use Node version 5.5.0, which is the version used to create this app. You can use `n` (https://github.com/tj/n) or `nvm` (https://github.com/creationix/nvm) to change Node versions easily. 43 | 44 | ## YouTube Tutorial Video 45 | 46 | https://youtu.be/6Sbau-oE37w 47 | 48 | ## Todos 49 | 1. Add testing 50 | 2. ~~Add deployment instructions~~ 51 | 3. Add comments to code 52 | 53 | ## Contributing 54 | Contributions/improvements are welcome! 55 | 56 | 1. Fork the repo 57 | 2. Make a pull request from you local repo -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mean-todo-app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "NODE_PATH=$NODE_PATH:./src node server", 8 | "dev": "npm start & webpack-dev-server --progress --colors", 9 | "build": "webpack --progress --colors --config webpack.prod.config.js" 10 | }, 11 | "keywords": [], 12 | "author": "Michael Cheng (http://michaelcheng.io)", 13 | "license": "ISC", 14 | "dependencies": { 15 | "angular": "^1.4.9", 16 | "angular-ui-router": "^0.2.17", 17 | "body-parser": "^1.14.2", 18 | "bootstrap-sass": "^3.3.6", 19 | "express": "^4.13.4", 20 | "jquery": "^2.2.0", 21 | "kerberos": "0.0.18", 22 | "lodash": "^4.1.0", 23 | "mongoose": "^4.4.0" 24 | }, 25 | "devDependencies": { 26 | "autoprefixer-loader": "^3.2.0", 27 | "babel-loader": "^6.2.1", 28 | "babel-preset-es2015": "^6.3.13", 29 | "bootstrap-loader": "^1.0.7", 30 | "css-loader": "^0.23.1", 31 | "file-loader": "^0.8.5", 32 | "imports-loader": "^0.6.5", 33 | "kerberos": "0.0.18", 34 | "node-sass": "^3.4.2", 35 | "raw-loader": "^0.5.1", 36 | "resolve-url-loader": "^1.4.3", 37 | "sass-loader": "^3.1.2", 38 | "style-loader": "^0.13.0", 39 | "url-loader": "^0.5.7", 40 | "webpack": "^1.12.12", 41 | "webpack-dev-server": "^1.14.1" 42 | }, 43 | "engines": { 44 | "node": "5.5.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /public/448c34a56d699c29117adc64c43affeb.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelcheng924/super-mean-stack/00d0ce51b6e06df10021c7f33b28d1f78a71b342/public/448c34a56d699c29117adc64c43affeb.woff2 -------------------------------------------------------------------------------- /public/89889688147bd7575d6327160d64e760.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /public/e18bbf611f2a2e43afc071aa2f4e1512.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelcheng924/super-mean-stack/00d0ce51b6e06df10021c7f33b28d1f78a71b342/public/e18bbf611f2a2e43afc071aa2f4e1512.ttf -------------------------------------------------------------------------------- /public/f4769f9bdb7466be65088239c12046d1.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelcheng924/super-mean-stack/00d0ce51b6e06df10021c7f33b28d1f78a71b342/public/f4769f9bdb7466be65088239c12046d1.eot -------------------------------------------------------------------------------- /public/fa2772327f55d8198301fdb8bcfc8158.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelcheng924/super-mean-stack/00d0ce51b6e06df10021c7f33b28d1f78a71b342/public/fa2772327f55d8198301fdb8bcfc8158.woff -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MEAN ToDo App 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | var path = require('path'); 4 | var bodyParser = require('body-parser'); 5 | var routes = require('server/routes'); 6 | 7 | var PORT = process.env.PORT || 3000; 8 | 9 | app.use(bodyParser.json()); 10 | 11 | // Used for production build 12 | app.use(express.static(path.join(__dirname, 'public'))); 13 | 14 | routes(app); 15 | 16 | app.all('/*', function(req, res) { 17 | res.sendFile(path.join(__dirname, 'public/index.html')); 18 | }); 19 | 20 | app.listen(PORT, function() { 21 | console.log('Server running on ' + PORT); 22 | }); 23 | -------------------------------------------------------------------------------- /src/about/about.html: -------------------------------------------------------------------------------- 1 |
2 | ToDos 3 |

About Page

4 | 5 |

The only purpose of this page is to demonstrate Angular routing.

6 |
7 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import uiRouter from 'angular-ui-router'; 3 | import todoFactory from 'factories/todo-factory'; 4 | import todosController from 'todos/todos'; 5 | 6 | const app = angular.module('app', [uiRouter, todoFactory.name]); 7 | 8 | app.config(($stateProvider, $urlRouterProvider, $locationProvider) => { 9 | $urlRouterProvider.otherwise('/'); 10 | 11 | $stateProvider 12 | .state('todos', { 13 | url: '/', 14 | template: require('todos/todos.html'), 15 | controller: todosController 16 | }) 17 | .state('about', { 18 | url: '/about', 19 | template: require('about/about.html') 20 | }); 21 | 22 | $locationProvider.html5Mode(true); 23 | }); 24 | 25 | export default app; 26 | -------------------------------------------------------------------------------- /src/css/master.scss: -------------------------------------------------------------------------------- 1 | @import 'todos.scss'; 2 | 3 | .nav-link { 4 | float: right; 5 | margin-top: 20px; 6 | } 7 | -------------------------------------------------------------------------------- /src/css/todos.scss: -------------------------------------------------------------------------------- 1 | .todos { 2 | &__create-input { 3 | margin: 0 auto 5px; 4 | width: 300px; 5 | } 6 | 7 | &__create-button { 8 | display: block; 9 | margin: 0 auto 20px; 10 | } 11 | 12 | &__task { 13 | &--completed { 14 | text-decoration: line-through; 15 | } 16 | } 17 | 18 | &__update-input { 19 | margin: 0 auto; 20 | width: 300px; 21 | } 22 | } 23 | 24 | th, td { 25 | font-size: 20px; 26 | text-align: center; 27 | } 28 | -------------------------------------------------------------------------------- /src/factories/todo-factory.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import angular from 'angular'; 3 | 4 | const todoFactory = angular.module('app.todoFactory', []) 5 | 6 | .factory('todoFactory', ($http) => { 7 | function getTasks($scope) { 8 | $http.get('/todos').success(response => { 9 | $scope.todos = response.todos; 10 | }); 11 | } 12 | 13 | function createTask($scope, params) { 14 | if (!$scope.createTaskInput) { return; } 15 | 16 | $http.post('/todos', { 17 | task: $scope.createTaskInput, 18 | isCompleted: false, 19 | isEditing: false 20 | }).success(response => { 21 | getTasks($scope); 22 | $scope.createTaskInput = ''; 23 | }); 24 | 25 | // params.createHasInput = false; 26 | // $scope.createTaskInput = ''; 27 | } 28 | 29 | function updateTask($scope, todo) { 30 | $http.put(`/todos/${todo._id}`, { task: todo.updatedTask }).success(response => { 31 | getTasks($scope); 32 | todo.isEditing = false; 33 | }); 34 | 35 | // todo.task = todo.updatedTask; 36 | // todo.isEditing = false; 37 | } 38 | 39 | function deleteTask($scope, todoToDelete) { 40 | $http.delete(`/todos/${todoToDelete._id}`).success(response => { 41 | getTasks($scope); 42 | }); 43 | 44 | // _.remove($scope.todos, todo => todo.task === todoToDelete.task); 45 | } 46 | 47 | function watchCreateTaskInput(params, $scope, val) { 48 | const createHasInput = params.createHasInput; 49 | 50 | if (!val && createHasInput) { 51 | $scope.todos.pop(); 52 | params.createHasInput = false; 53 | } else if (val && !createHasInput) { 54 | $scope.todos.push({ task: val, isCompleted: false }); 55 | params.createHasInput = true; 56 | } else if (val && createHasInput) { 57 | $scope.todos[$scope.todos.length - 1].task = val; 58 | } 59 | } 60 | 61 | return { 62 | getTasks, 63 | createTask, 64 | updateTask, 65 | deleteTask, 66 | watchCreateTaskInput 67 | }; 68 | }); 69 | 70 | export default todoFactory; 71 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import appModule from 'config'; 3 | import 'css/master.scss'; 4 | 5 | angular.bootstrap(document, [appModule.name]); 6 | -------------------------------------------------------------------------------- /src/server/db/db.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | mongoose.connect(process.env.MONGOLAB_URI || 'mongodb://localhost/todos'); 3 | 4 | var Todo = mongoose.model('Todo', { 5 | task: String, 6 | isCompleted: Boolean, 7 | isEditing: Boolean 8 | }); 9 | 10 | module.exports.Todo = Todo; 11 | -------------------------------------------------------------------------------- /src/server/routes.js: -------------------------------------------------------------------------------- 1 | var todosRoutes = require('server/todos/routes'); 2 | 3 | module.exports = function routes(app) { 4 | app.use('/todos', todosRoutes); 5 | }; -------------------------------------------------------------------------------- /src/server/todos/routes.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Todo = require('server/db/db').Todo; 3 | var express = require('express'); 4 | var router = express.Router(); 5 | 6 | router.get('/', function(req, res) { 7 | Todo.find(function(err, results) { 8 | if (err) { console.log(err); } 9 | 10 | res.send({ todos: results }); 11 | }); 12 | }); 13 | 14 | router.post('/', function(req, res) { 15 | var todo = new Todo(req.body); 16 | todo.save(function(err) { 17 | if (err) { console.log(err); } 18 | 19 | res.send('ToDo saved'); 20 | }); 21 | }); 22 | 23 | router.put('/:id', function(req, res) { 24 | var id = req.params.id; 25 | Todo.update({ _id: mongoose.Types.ObjectId(id) }, { 26 | $set: { task: req.body.task } 27 | }, function(err) { 28 | if (err) { console.log(err); } 29 | 30 | res.send('ToDo updated'); 31 | }); 32 | }); 33 | 34 | router.delete('/:id', function(req, res) { 35 | var id = req.params.id; 36 | Todo.remove({ _id: mongoose.Types.ObjectId(id) }, function(err) { 37 | if (err) { console.log(err); } 38 | 39 | res.send('ToDo deleted'); 40 | }); 41 | }); 42 | 43 | module.exports = router; 44 | -------------------------------------------------------------------------------- /src/todos/todos.html: -------------------------------------------------------------------------------- 1 |
2 | About 3 |

ToDo App

4 | 5 |
6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 36 | 59 | 60 |
Completed?TaskActions
18 | 21 | 23 | 26 | {{todo.task}} 27 | 28 | 29 |
30 | 34 |
35 |
37 | 42 | 47 | 48 | 53 | 58 |
61 |
62 | -------------------------------------------------------------------------------- /src/todos/todos.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default function($scope, todoFactory) { 4 | let params = { 5 | createHasInput: false 6 | }; 7 | 8 | // $scope.todos = [ 9 | // { 10 | // task: 'do dishes', 11 | // isCompleted: false, 12 | // isEditing: false 13 | // }, 14 | // { 15 | // task: 'walk the dog', 16 | // isCompleted: true, 17 | // isEditing: false 18 | // } 19 | // ]; 20 | 21 | todoFactory.getTasks($scope); 22 | 23 | $scope.onCompletedClick = todo => { 24 | todo.isCompleted = !todo.isCompleted; 25 | }; 26 | 27 | $scope.onEditClick = todo => { 28 | todo.isEditing = true; 29 | todo.updatedTask = todo.task; 30 | }; 31 | 32 | $scope.onCancelClick = todo => { 33 | todo.isEditing = false; 34 | }; 35 | 36 | const { createTask, updateTask, deleteTask, watchCreateTaskInput } = todoFactory; 37 | 38 | $scope.createTask = _.partial(createTask, $scope, params); 39 | $scope.updateTask = _.partial(updateTask, $scope); 40 | $scope.deleteTask = _.partial(deleteTask, $scope); 41 | $scope.$watch('createTaskInput', _.partial(watchCreateTaskInput, params, $scope)); 42 | } 43 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | devtool: 'inline-source-map', 6 | entry: [ 7 | 'webpack-dev-server/client?http://127.0.0.1:8080/', 8 | 'webpack/hot/only-dev-server', 9 | 'bootstrap-loader', 10 | './src' 11 | ], 12 | output: { 13 | path: path.join(__dirname, 'public'), 14 | filename: 'bundle.js' 15 | }, 16 | resolve: { 17 | modulesDirectories: ['node_modules', 'src'], 18 | extension: ['', '.js', '.scss'] 19 | }, 20 | module: { 21 | loaders: [ 22 | { 23 | test: /\.js$/, 24 | exclude: /node_modules/, 25 | loader: 'babel', 26 | query: { 27 | presets: ['es2015'] 28 | } 29 | }, 30 | { 31 | test: /\.html$/, 32 | loader: 'raw' 33 | }, 34 | { 35 | test: /\.scss$/, 36 | loaders: [ 37 | 'style', 38 | 'css', 39 | 'autoprefixer?browsers=last 3 versions', 40 | 'sass?outputStyle=expanded' 41 | ] 42 | }, 43 | { 44 | test: /\.(woff2?|ttf|eot|svg)$/, 45 | loader: 'url?limit=10000' 46 | }, 47 | { 48 | test: /bootstrap-sass\/assets\/javascripts\//, 49 | loader: 'imports?jQuery=jquery' 50 | } 51 | ] 52 | }, 53 | plugins: [ 54 | new webpack.HotModuleReplacementPlugin(), 55 | new webpack.NoErrorsPlugin() 56 | ], 57 | devServer: { 58 | hot: true, 59 | proxy: { 60 | '*': 'http://localhost:3000' 61 | } 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | entry: [ 6 | 'bootstrap-loader', 7 | './src' 8 | ], 9 | output: { 10 | path: path.join(__dirname, 'public'), 11 | filename: 'bundle.js' 12 | }, 13 | resolve: { 14 | modulesDirectories: ['node_modules', 'src'], 15 | extension: ['', '.js', '.scss'] 16 | }, 17 | module: { 18 | loaders: [ 19 | { 20 | test: /\.js$/, 21 | exclude: /node_modules/, 22 | loader: 'babel', 23 | query: { 24 | presets: ['es2015'] 25 | } 26 | }, 27 | { 28 | test: /\.html$/, 29 | loader: 'raw' 30 | }, 31 | { 32 | test: /\.scss$/, 33 | loaders: [ 34 | 'style', 35 | 'css', 36 | 'autoprefixer?browsers=last 3 versions', 37 | 'sass?outputStyle=expanded' 38 | ] 39 | }, 40 | { 41 | test: /\.(woff2?|ttf|eot|svg)$/, 42 | loader: 'url?limit=10000' 43 | }, 44 | { 45 | test: /bootstrap-sass\/assets\/javascripts\//, 46 | loader: 'imports?jQuery=jquery' 47 | } 48 | ] 49 | } 50 | }; 51 | --------------------------------------------------------------------------------