├── .env_example ├── .gitignore ├── README.MD ├── app.js ├── bin └── www ├── db └── knex.js ├── knexfile.js ├── migrations ├── example.txt └── example_withForeignKey.txt ├── package.json ├── public └── stylesheets │ └── style.css ├── routes ├── index.js └── users.js ├── seeds └── example.txt └── views ├── error.hbs ├── index.hbs └── layout.hbs /.env_example: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgres URL here 2 | 3 | 4 | Rename this to .env only 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 27 | node_modules 28 | 29 | # Debug log from npm 30 | npm-debug.log 31 | .env 32 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Boilerplate for a knex CRUD app 2 | 3 | ## _important:_ The commands for all these steps are at the bottom if you don't know them. 4 | 5 | 1. cd into clone && run npm install. 6 | 2. Create a local db. 7 | 3. Change knexfile.js to reflect your local db name. 8 | 4. Create a heroku app && postgresql addon. 9 | 5. put your heroku-postgresql url in the .env_example and **rename file** right away to .env. 10 | 6. Run knex commands to make migrations && seeds. 11 | 7. Migrate latest! 12 | 8. Don't forget to migrate to heroku. 13 | 9. Enjoy! 14 | 15 | # Steps on how I setup this CRUD project 16 | 17 | ##### Create Express App 18 | - `express --hbs name_of_app` creates new express app in a directory called name_of_app 19 | * `--hbs` adds handlebars 20 | * assumes you have [express-generator](https://www.npmjs.com/package/express-generator) package installed globally (`npm install -g express-generator`) 21 | - `cd name_of_app && npm i` 22 | * `npm i` installs all dependencies denoted by package.json 23 | 24 | ##### Set Up Knex 25 | - `npm i -S pg knex` installs pg & knex, & adds both to the dependencies in package.json 26 | * in 'index.js' file, add `var pg = require('pg');` 27 | * [pg](https://www.npmjs.com/package/pg) is a module that lets you connect to psql; must be installed on a per project basis 28 | * assumes you have [knex](https://www.npmjs.com/package/knex) package installed globally (`npm i knex -g`) 29 | - `knex init` creates 'knexfile.js' in root directory 30 | - In root directory, add a folder called 'db' & create 'knex.js' inside it 31 | * in index.js file, add `var knex = require('../db/knex');` 32 | * refer to 'db/knex.js' in this repo for how to define your environment configurations 33 | 34 | ##### Initialize Git & Set Up .env File 35 | - `git init` initializes git repo 36 | - `echo node_modules > .gitignore` to add node modules to a .gitignore file OR if you have [gitignore](https://www.npmjs.com/package/gitignore) installed globally, use command `gitignore node` 37 | - `npm i -S dotenv` installs [dotenv](https://www.npmjs.com/package/dotenv) module & adds to dependencies in package.json, which loads environment variables from a .env file into process.env 38 | - `touch .env` in root directory to create empty .env file 39 | * add 1 key-value pair per line; when assigning multiple values to one key, separate with `:` 40 | * this is where we define `DATABASE_URL=yourURL` later 41 | * add `require('dotenv').config();` to top of 'knexfile.js' (so that we can access the `DATABASE_URL` variable through `process.env` when we define our production environment connection). also add it to 'app.js' (if you're adding other variables that your app will refer to, so it will be available to all routes). 42 | - `echo .env >> .gitignore` adds .env file to .gitignore so git doesn't track it 43 | * Why? Because we are going to set the `DATABASE_URL` variable equal to our heroku URL, which contains a password that we don't want others to see 44 | 45 | ##### Set Up Heroku App & Database 46 | - `createdb name_of_app` creates psql database locally 47 | - `heroku apps:create name-of-app` creates a heroku app (Name must start with a letter and can only contain lowercase letters, numbers, and dashes.) 48 | - `heroku addons:create heroku-postgresql --app name_of_app` adds postgresql database to the app 49 | - `heroku config` returns the URL 50 | * add URL to .env file `DATABASE_URL=yourURL?ssl=true` 51 | - create a repo on github & `git remote add origin ssh_of_repo` to add the remote 52 | - `git add/commit/push` to github 53 | - if you want to open a psql shell :shell: to the heroku database & run sql commands in the terminal: `heroku pg:psql --app name_of_app` 54 | 55 | ##### Migrating & Seeding 56 | - `knex migrate:make table_name` to create a new migration 57 | * this creates a folder called 'migrations' with a new 'timestamp_table_name.js' file 58 | * Each time you change the table you will need to rerun this command & change the contents of the js file 59 | - `knex migrate:latest` runs knex migration locally (whatever development environment is set to) 60 | - `knex seed:make seed_name` to create a new seed :seedling: 61 | * this creates a folder called 'seeds' with a new 'timestamp_seed_name.js' file 62 | - `knex seed:run` to run seed the local database 63 | 64 | If deploying to heroku, 65 | - `knex migrate:latest --env production` runs knex migration locally against heroku database in the cloud 66 | - `knex seed:run --env production` runs knex locally & puts seed data in heroku database 67 | - `git push heroku master` to push to heroku :tada::balloon::confetti_ball: 68 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var users = require('./routes/users'); 10 | require('dotenv').config(); 11 | 12 | var app = express(); 13 | 14 | // view engine setup 15 | app.set('views', path.join(__dirname, 'views')); 16 | app.set('view engine', 'hbs'); 17 | 18 | // uncomment after placing your favicon in /public 19 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 20 | app.use(logger('dev')); 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | app.use(express.static(path.join(__dirname, 'public'))); 25 | 26 | app.use('/', routes); 27 | app.use('/users', users); 28 | 29 | // catch 404 and forward to error handler 30 | app.use(function(req, res, next) { 31 | var err = new Error('Not Found'); 32 | err.status = 404; 33 | next(err); 34 | }); 35 | 36 | // error handlers 37 | 38 | // development error handler 39 | // will print stacktrace 40 | if (app.get('env') === 'development') { 41 | app.use(function(err, req, res, next) { 42 | res.status(err.status || 500); 43 | res.render('error', { 44 | message: err.message, 45 | error: err 46 | }); 47 | }); 48 | } 49 | 50 | // production error handler 51 | // no stacktraces leaked to user 52 | app.use(function(err, req, res, next) { 53 | res.status(err.status || 500); 54 | res.render('error', { 55 | message: err.message, 56 | error: {} 57 | }); 58 | }); 59 | 60 | 61 | module.exports = app; 62 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('intro_to_knex: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 | } 91 | -------------------------------------------------------------------------------- /db/knex.js: -------------------------------------------------------------------------------- 1 | var environment = process.env.NODE_ENV || 'development'; 2 | var config = require('../knexfile')[environment]; 3 | var knex = require('knex')(config); 4 | 5 | module.exports = knex; 6 | -------------------------------------------------------------------------------- /knexfile.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | module.exports = { 4 | development: { 5 | client: 'pg', 6 | connection: 'postgres://localhost/into_to_knex' 7 | }, 8 | production: { 9 | client: 'pg', 10 | connection: process.env.DATABASE_URL + '?ssl=true' 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/example.txt: -------------------------------------------------------------------------------- 1 | exports.up = function(knex, Promise) { 2 | return knex.schema.createTable('locations', function(table) { 3 | table.increments(); 4 | table.string('name'); 5 | table.string('address'); 6 | }); 7 | }; 8 | 9 | exports.down = function(knex, Promise) { 10 | return knex.schema.dropTable('locations'); 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/example_withForeignKey.txt: -------------------------------------------------------------------------------- 1 | exports.up = function(knex, Promise) { 2 | return knex.schema.createTable('meetups', function(table) { 3 | table.increments(); 4 | table.string('name'); 5 | table.text('description'); 6 | table.integer('location_id').references('locations.id'); 7 | }); 8 | }; 9 | 10 | exports.down = function(knex, Promise) { 11 | return knex.schema.dropTable('meetups'); 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro_to_knex", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.13.2", 10 | "cookie-parser": "~1.3.5", 11 | "debug": "~2.2.0", 12 | "dotenv": "^2.0.0", 13 | "express": "~4.13.1", 14 | "hbs": "~3.1.0", 15 | "knex": "^0.11.5", 16 | "morgan": "~1.6.1", 17 | "pg": "^5.1.0", 18 | "serve-favicon": "~2.3.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var knex = require('../db/knex'); 4 | 5 | /* GET home page. */ 6 | router.get('/', function(req, res, next) { 7 | res.render('index', { title: 'Express' }); 8 | }); 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res, next) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /seeds/example.txt: -------------------------------------------------------------------------------- 1 | This is an example for how to seed 2 | join tables! 3 | 4 | exports.seed = function(knex, Promise) { 5 | return Promise.join( 6 | knex('memberships').del(), 7 | knex('meetups').del(), 8 | knex('locations').del(), 9 | knex('users').del() 10 | ) 11 | .then(function() { 12 | return Promise.join( 13 | knex('locations').insert({ 14 | name: 'Galvanize', 15 | address: 'Platte St, Denver' 16 | }).returning('id'), 17 | knex('locations').insert({ 18 | name: 'Pivotal Labs', 19 | address: '17th St, Boulder' 20 | }).returning('id'), 21 | knex('locations').insert({ 22 | name: 'Google', 23 | address: 'Pearl St, Boulder' 24 | }).returning('id') 25 | ); 26 | }) 27 | .then(function(ids) { 28 | var galvanizeId = ids[0][0], 29 | pivotalId = ids[1][0], 30 | googleId = ids[2][0]; 31 | 32 | return Promise.join( 33 | knex('meetups').insert({ 34 | name: 'NodeJS', 35 | description: 'Learn all the scripts!', 36 | location_id: galvanizeId, 37 | }).returning('id'), 38 | knex('meetups').insert({ 39 | name: 'Ruby', 40 | description: 'What a gem!', 41 | location_id: galvanizeId, 42 | }).returning('id'), 43 | knex('meetups').insert({ 44 | name: 'PHP', 45 | description: 'And oldy but a goodie!', 46 | location_id: googleId, 47 | }).returning('id'), 48 | knex('meetups').insert({ 49 | name: 'Python', 50 | description: 'Get your data on!', 51 | location_id: googleId, 52 | }).returning('id') 53 | ); 54 | }) 55 | .then(function(ids) { 56 | var nodeId = ids[0][0], 57 | rubyId = ids[1][0], 58 | phpId = ids[2][0], 59 | pythonId = ids[3][0]; 60 | 61 | return Promise.join( 62 | knex('users').insert({ 63 | name: 'Joe' 64 | }).returning('id'), 65 | knex('users').insert({ 66 | name: 'Sue' 67 | }).returning('id'), 68 | knex('users').insert({ 69 | name: 'Tim' 70 | }).returning('id'), 71 | knex('users').insert({ 72 | name: 'Kim' 73 | }).returning('id') 74 | ).then(function(userIds) { 75 | return { 76 | users: { 77 | joeId: userIds[0][0], 78 | sueId: userIds[1][0], 79 | timId: userIds[2][0], 80 | kimId: userIds[3][0], 81 | }, 82 | meetups: { 83 | nodeId: nodeId, 84 | rubyId: rubyId, 85 | phpId: phpId, 86 | pythonId: pythonId, 87 | } 88 | }; 89 | }); 90 | }) 91 | .then(function(data) { 92 | return Promise.join( 93 | knex('memberships').insert({ 94 | user_id: data.users.joeId, 95 | meetup_id: data.meetups.pythonId 96 | }), 97 | knex('memberships').insert({ 98 | user_id: data.users.joeId, 99 | meetup_id: data.meetups.phpId 100 | }), 101 | knex('memberships').insert({ 102 | user_id: data.users.sueId, 103 | meetup_id: data.meetups.pythonId 104 | }), 105 | knex('memberships').insert({ 106 | user_id: data.users.timId, 107 | meetup_id: data.meetups.rubyId 108 | }), 109 | knex('memberships').insert({ 110 | user_id: data.users.timId, 111 | meetup_id: data.meetups.nodeId 112 | }), 113 | knex('memberships').insert({ 114 | user_id: data.users.kimId, 115 | meetup_id: data.meetups.pythonId 116 | }) 117 | ); 118 | }); 119 | }; 120 | -------------------------------------------------------------------------------- /views/error.hbs: -------------------------------------------------------------------------------- 1 |

{{message}}

2 |

{{error.status}}

3 |
{{error.stack}}
4 | -------------------------------------------------------------------------------- /views/index.hbs: -------------------------------------------------------------------------------- 1 |

{{title}}

2 |

Welcome to {{title}}

3 | -------------------------------------------------------------------------------- /views/layout.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 | {{{body}}} 9 | 10 | 11 | --------------------------------------------------------------------------------