├── .gitignore ├── client ├── js │ └── main.js └── css │ └── main.css ├── server ├── views │ ├── index.html │ ├── error.html │ └── layout.html ├── config.json ├── models │ └── index.js ├── routes │ └── index.js ├── app.js └── bin │ └── www ├── .sequelizerc ├── package.json ├── README.md └── gulpfile.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | npm-debug.log 4 | blog.md 5 | -------------------------------------------------------------------------------- /client/js/main.js: -------------------------------------------------------------------------------- 1 | // add scripts 2 | 3 | $(document).on('ready', function() { 4 | console.log('sanity check!'); 5 | }); 6 | -------------------------------------------------------------------------------- /client/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /server/views/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block title %}{% endblock %} 4 | 5 | 6 | {% block content %} 7 | 8 |
9 | 10 |

{{ title }}

11 |

Welcome to {{ title }}

12 | 13 |
14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | 'config': path.resolve('./server', 'config.json'), 5 | 'migrations-path': path.resolve('./server', 'migrations'), 6 | 'models-path': path.resolve('./server', 'models'), 7 | 'seeders-path': path.resolve('./server', 'seeders') 8 | } 9 | -------------------------------------------------------------------------------- /server/views/error.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block title %}{% endblock %} 4 | 5 | 6 | {% block content %} 7 | 8 |
9 | 10 |

{{ message }}

11 |

{{ error.status }}

12 |
{{ error.stack }}
13 | 14 |
15 | 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "username": "update me", 4 | "password": "update me", 5 | "database": "todos", 6 | "host": "127.0.0.1", 7 | "port": "5432", 8 | "dialect": "postgres" 9 | }, 10 | "test": { 11 | "username": "update me", 12 | "password": "update me", 13 | "database": "update_me", 14 | "host": "update me", 15 | "dialect": "update me" 16 | }, 17 | "production": { 18 | "username": "update me", 19 | "password": "update me", 20 | "database": "update me", 21 | "host": "update me", 22 | "dialect": "update me" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/views/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ title }} 6 | 7 | 8 | 9 | 10 | {% block content %} 11 | {% endblock %} 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_example", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./server/bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.13.2", 10 | "cookie-parser": "~1.3.5", 11 | "debug": "~2.2.0", 12 | "express": "~4.13.1", 13 | "morgan": "~1.6.1", 14 | "pg": "^4.4.3", 15 | "pg-hstore": "^2.3.2", 16 | "sequelize": "^3.12.2", 17 | "sequelize-cli": "^2.1.0", 18 | "serve-favicon": "~2.3.0", 19 | "swig": "^1.4.2" 20 | }, 21 | "devDependencies": { 22 | "browser-sync": "2.9.6", 23 | "gulp": "^3.9.0", 24 | "gulp-jshint": "^1.11.2", 25 | "gulp-nodemon": "^2.0.4", 26 | "jshint-stylish": "^2.0.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node, Postgres, and Sequelize 2 | 3 | ## Want to learn how to build this project? 4 | 5 | Check out the [blog post](http://mherman.org/blog/2015/10/22/node-postgres-sequelize). 6 | 7 | ## Want to use this project? 8 | 9 | 1. Fork/Clone 10 | 1. Install dependencies - `npm install` 11 | 1. Create a local Postgres databases - `todos` - and then update *server/config.json* 12 | 1. Add a "migrations" folder to the "server" folder 13 | 1. Create two new migrations: 14 | 15 | ```sh 16 | $ node_modules/.bin/sequelize model:create --name Todo --attributes "title:string, complete:boolean,UserId:integer" 17 | $ node_modules/.bin/sequelize model:create --name User --attributes "email:string" 18 | ``` 19 | 20 | 1. Update the migration files in "server/models" (if necessary) and then sync the database: 21 | 22 | ```sh 23 | $ node_modules/.bin/sequelize db:migrate 24 | ``` 25 | 26 | 1. Run the development server - `gulp` 27 | -------------------------------------------------------------------------------- /server/models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var Sequelize = require('sequelize'); 6 | var basename = path.basename(module.filename); 7 | var env = process.env.NODE_ENV || 'development'; 8 | var config = require(__dirname + '/../config.json')[env]; 9 | var db = {}; 10 | 11 | if (config.use_env_variable) { 12 | var sequelize = new Sequelize(process.env[config.use_env_variable]); 13 | } else { 14 | var sequelize = new Sequelize(config.database, config.username, config.password, config); 15 | } 16 | 17 | fs 18 | .readdirSync(__dirname) 19 | .filter(function(file) { 20 | return (file.indexOf('.') !== 0) && (file !== basename); 21 | }) 22 | .forEach(function(file) { 23 | if (file.slice(-3) !== '.js') return; 24 | var model = sequelize['import'](path.join(__dirname, file)); 25 | db[model.name] = model; 26 | }); 27 | 28 | Object.keys(db).forEach(function(modelName) { 29 | if (db[modelName].associate) { 30 | db[modelName].associate(db); 31 | } 32 | }); 33 | 34 | db.sequelize = sequelize; 35 | db.Sequelize = Sequelize; 36 | 37 | module.exports = db; 38 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | var gulp = require('gulp'); 6 | var jshint = require('gulp-jshint'); 7 | var browserSync = require('browser-sync'); 8 | var reload = browserSync.reload; 9 | var nodemon = require('gulp-nodemon'); 10 | 11 | 12 | /** 13 | * Config 14 | */ 15 | 16 | var paths = { 17 | styles: [ 18 | './client/css/*.css', 19 | ], 20 | scripts: [ 21 | './client/js/*.js', 22 | ], 23 | server: './server/bin/www' 24 | }; 25 | 26 | var nodemonConfig = { 27 | script: paths.server, 28 | ext: 'html js css', 29 | ignore: ['node_modules'] 30 | }; 31 | 32 | 33 | /** 34 | * Gulp Tasks 35 | */ 36 | 37 | gulp.task('lint', function() { 38 | return gulp.src(paths.scripts) 39 | .pipe(jshint()) 40 | .pipe(jshint.reporter('jshint-stylish')); 41 | }); 42 | 43 | gulp.task('browser-sync', ['nodemon'], function(done) { 44 | browserSync({ 45 | proxy: "localhost:3000", // local node app address 46 | port: 5000, // use *different* port than above 47 | notify: true 48 | }, done); 49 | }); 50 | 51 | gulp.task('nodemon', function (cb) { 52 | var called = false; 53 | return nodemon(nodemonConfig) 54 | .on('start', function () { 55 | if (!called) { 56 | called = true; 57 | cb(); 58 | } 59 | }) 60 | .on('restart', function () { 61 | setTimeout(function () { 62 | reload({ stream: false }); 63 | }, 1000); 64 | }); 65 | }); 66 | 67 | gulp.task('watch', function() { 68 | gulp.watch(paths.scripts, ['lint']); 69 | }); 70 | 71 | gulp.task('default', ['browser-sync', 'watch'], function(){}); 72 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var models = require('../models/index'); 4 | 5 | 6 | router.get('/', function(req, res, next) { 7 | res.render('index', { title: 'Express' }); 8 | }); 9 | 10 | router.post('/users', function(req, res) { 11 | models.User.create({ 12 | email: req.body.email 13 | }).then(function(user) { 14 | res.json(user); 15 | }); 16 | }); 17 | 18 | // get all todos 19 | router.get('/todos', function(req, res) { 20 | models.Todo.findAll({}).then(function(todos) { 21 | res.json(todos); 22 | }); 23 | }); 24 | 25 | // get single todo 26 | router.get('/todo/:id', function(req, res) { 27 | models.Todo.find({ 28 | where: { 29 | id: req.params.id 30 | } 31 | }).then(function(todo) { 32 | res.json(todo); 33 | }); 34 | }); 35 | 36 | // add new todo 37 | router.post('/todos', function(req, res) { 38 | models.Todo.create({ 39 | title: req.body.title, 40 | UserId: req.body.user_id 41 | }).then(function(todo) { 42 | res.json(todo); 43 | }); 44 | }); 45 | 46 | // update single todo 47 | router.put('/todo/:id', function(req, res) { 48 | models.Todo.find({ 49 | where: { 50 | id: req.params.id 51 | } 52 | }).then(function(todo) { 53 | if(todo){ 54 | todo.updateAttributes({ 55 | title: req.body.title, 56 | complete: req.body.complete 57 | }).then(function(todo) { 58 | res.send(todo); 59 | }); 60 | } 61 | }); 62 | }); 63 | 64 | // delete a single todo 65 | router.delete('/todo/:id', function(req, res) { 66 | models.Todo.destroy({ 67 | where: { 68 | id: req.params.id 69 | } 70 | }).then(function(todo) { 71 | res.json(todo); 72 | }); 73 | }); 74 | 75 | module.exports = router; 76 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | // *** main dependencies *** // 2 | var express = require('express'); 3 | var path = require('path'); 4 | var favicon = require('serve-favicon'); 5 | var logger = require('morgan'); 6 | var cookieParser = require('cookie-parser'); 7 | var bodyParser = require('body-parser'); 8 | var swig = require('swig'); 9 | 10 | 11 | // *** routes *** // 12 | var routes = require('./routes/index.js'); 13 | 14 | 15 | // *** express instance *** // 16 | var app = express(); 17 | 18 | 19 | // *** view engine *** // 20 | var swig = new swig.Swig(); 21 | app.engine('html', swig.renderFile); 22 | app.set('view engine', 'html'); 23 | 24 | 25 | // *** static directory *** // 26 | app.set('views', path.join(__dirname, 'views')); 27 | 28 | 29 | // *** config middleware *** // 30 | app.use(logger('dev')); 31 | app.use(bodyParser.json()); 32 | app.use(bodyParser.urlencoded({ extended: false })); 33 | app.use(cookieParser()); 34 | app.use(express.static(path.join(__dirname, '../client'))); 35 | 36 | 37 | // *** main routes *** // 38 | app.use('/', routes); 39 | 40 | 41 | // catch 404 and forward to error handler 42 | app.use(function(req, res, next) { 43 | var err = new Error('Not Found'); 44 | err.status = 404; 45 | next(err); 46 | }); 47 | 48 | 49 | // *** error handlers *** // 50 | 51 | // development error handler 52 | // will print stacktrace 53 | if (app.get('env') === 'development') { 54 | app.use(function(err, req, res, next) { 55 | res.status(err.status || 500); 56 | res.render('error', { 57 | message: err.message, 58 | error: err 59 | }); 60 | }); 61 | } 62 | 63 | // production error handler 64 | // no stacktraces leaked to user 65 | app.use(function(err, req, res, next) { 66 | res.status(err.status || 500); 67 | res.render('error', { 68 | message: err.message, 69 | error: {} 70 | }); 71 | }); 72 | 73 | 74 | module.exports = app; 75 | -------------------------------------------------------------------------------- /server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('_example:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | console.log('Listening on ' + bind); 91 | } 92 | --------------------------------------------------------------------------------