- Make directory
lehman-notes
- Init package.json
> npm init —yes
> npm install --save-exact --save-dev prettier
-
Setup git repository
-
Make repo on github
-
`git init`
-
.gitignore
-
npm install —saveExact --save-dev prettier husky pretty-quick
-
Create prettier config
.prettierrc
:
-
{
"singleQuote": true,
"semi": false,
"trailingComma": "es5"
}
- Add to
package.json
scripts
section:"precommit": "pretty-quick --staged"
- Eslint
npm install --save-exact --save-dev eslint eslint-config-prettier
-./node_modules/.bin/eslint --init
-> Use popular style guide -> Standard - updateeslintrc.json
to:
{
"extends": ["standard", "prettier"]
}
- Commit to repo
-
npm install --save-exact express
-
create
server.js
with
const express = require('express')
const app = express()
const port = process.env.PORT || 3000
app.listen(port, function() {
console.log('App is running on port 3000')
})
- Run
npm start
- Run
npm run install —save-exact express-handlebars
- Update
server.js
const hbs = require('express-handlebars')
.....
app.engine('hbs', hbs({ extname: 'hbs', defaultLayout: 'layout' }))
app.set('views', './views')
app.set('view engine', 'hbs')
app.get('/', function(req, res) {
res.render('home')
})
- create
views/layouts/layout.hbs
with:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lehman Notes</title>
</head>
<body>
{{{body}}}
</body>
</html>
- create
views/home.hbs
with:
<h1>Hello World!</h1>
-
npm install —save-exact —save-dev nodemon
-
Add
package.json
script with:"local": "nodemon server.js"
-
Commit to repo
- Create
public/css/stylesheet.css
:
.pretty {
background-color: blue;
color: white;
}
- Update
server.js
under view engine setup:
app.use(express.static('public'))
- Update
views/layouts/layout.hbs
to load our stylesheet inside the head tag:
<link rel="stylesheet" href="/css/stylesheet.css"/>
- Add the class to
views/home.hbs
:
<h1 class="pretty">Hello World!</h1>
- Add bootstrap.css above our local one to
views/layouts/layout.hbs
:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"/>
- Add a header to layout
<body class="bg-light">
<header class="d-flex justify-content-center navbar navbar-default border-bottom bg-white">
<a class="navbar-brand" href="/">Lehman Notes</a>
</header>
<main class="container mt-4">
{{{body}}}
</main>
</body>
- Commit to repo
-
npm install --save-exact --save-dev knex pg
-
Create
db/index.js
:
const connectionString = process.env.DATABASE_URL
module.exports = {
connectionParams: connectionString,
}
-
npm install —save-exact dotenv
-
Create
.env
file:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/lehman_notes_dev
- Create
db/scripts/create.js
require('dotenv').config()
const url = require('url')
const { connectionParams } = require('../index')
const dbName = connectionParams.split('/')[3]
const dbParams = url.parse(connectionParams)
const knex = require('knex')({
client: 'pg',
connection: `${dbParams.protocol}//${dbParams.auth}@${dbParams.host}`,
})
async function createDb() {
try {
await knex.raw(`CREATE DATABASE ${dbName}`)
console.log(`Database ${dbName} created successfully!`)
} catch (e) {
console.log(e)
} finally {
knex.destroy()
}
}
createDb()
-
Add new script in
package.json
:”db:create”: “node db/scripts/create.js”
-
Create
db/scripts/destroy.js
require('dotenv').config()
const url = require('url')
const { connectionParams } = require('../index')
const dbName = connectionParams.split('/')[3]
const dbParams = url.parse(connectionParams)
const knex = require('knex')({
client: 'pg',
connection: `${dbParams.protocol}//${dbParams.auth}@${dbParams.host}`,
})
async function destroyDb() {
try {
await knex.raw(`DROP DATABASE IF EXISTS ${dbName}`)
console.log(`Database ${dbName} deleted successfully!`)
} catch (e) {
console.log(e)
} finally {
knex.destroy()
}
}
destroyDb()
-
Add script to
package.json
:"db:destroy": "node db/scripts/destroy.js"
-
Commit to repo
- Create
knexfile.js
in root:
require('dotenv').config()
const { connectionParams } = require('./db')
module.exports = {
client: 'pg',
connection: connectionParams,
pool: { min: 0, max: 7 },
}
-
Add script to
package.json
:"db:migrate:make": "knex migrate:make"
-
Run
npm run db:migrate:make createNotes”
-
Go to
migrations/createNotes
and update to:
exports.up = function(knex) {
return knex.schema.raw(`
CREATE TABLE notes(
id SERIAL PRIMARY KEY,
title text NOT NULL,
body text NOT NULL,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`)
}
exports.down = function(knex) {
return knex.schema.raw(`DROP TABLE IF EXISTS notes`)
}
- Add scripts to
package.json
:
"db:migrate": "knex migrate:latest",
"db:rollback": "knex migrate:rollback"
- Commit to repo
-
Update
package.json
"db:seed:make": "knex seed:make"
-
Run
npm run db:seed:make notes
which will create a file underseeds/notes.js
with a skeleton of a seed -
Copy paste seedData:
const seedData = [
{
title:
'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
body:
'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto',
},
{
title: 'qui est esse',
body:
'est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla',
},
{
title: 'ea molestias quasi exercitationem repellat qui ipsa sit aut',
body:
'et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut',
},
{
title: 'eum et est occaecati',
body:
'ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit',
},
{
title: 'nesciunt quas odio',
body:
'repudiandae veniam quaerat sunt sed\nalias aut fugiat sit autem sed est\nvoluptatem omnis possimus esse voluptatibus quis\nest aut tenetur dolor neque',
},
]
- Update seed function to:
exports.seed = async function(knex) {
await knex('notes').del()
await knex('notes').insert(seedData)
}
-
Add script to
package.json
:"db:seed": "knex seed:run”
-
Commit to repo
- In
db/index.js
- import on top of file:
const { Pool } = require('pg')
- add
- import on top of file:
const pool = new Pool({
connectionString,
})
function query(text, params) {
return pool.query(text, params)
}
- and add to export
module.exports = {
connectionParams: connectionString,
query,
}
- Create
db/notes.js
const db = require('./index')
async function getNotes() {
const { rows } = await db.query('SELECT * FROM notes')
return rows
}
module.exports = {
all: getNotes,
}
-
Update
server.js
-
on top of file
require('dotenv').config()
-
Import notes modules
const notes = require(‘./db/notes’)
-
Create route
-
app.get('/notes', async function(req, res) {
const allNotes = await notes.all()
res.render('/notes/index', { notes: allNotes })
})
- Create
views/notes/index.hbs
<table class="table table-hover">
<thead>
<th class="border-0">Title</th>
</thead>
<tbody>
{{#each notes}}
<tr>
<td>{{this.title}}</td>
</tr>
{{/each}}
</tbody>
</table>
- Add link to same file above the table
<a class="btn btn-success float-right mr-5" href="/notes/new">New Note</a>
- COMMIT
- In
db/notes.js
- add
async function createNote(title, body) {}
- export
- add
module.exports = {
all: getNotes,
create: createNote,
}
-
npm install —save-dev —save-exact jest eslint-plugin-jest
-
Update
.eslintrc.json”
by adding"plugin:jest/recommended”
toextends
- add
"env": {
"jest/globals": true
}
-
Add
package.json
task:"test": "jest --forceExit”
-
Create test db by updating
db/scripts/create.js
await knex.raw(`CREATE DATABASE ${dbName}_test`)
console.log(`Database ${dbName} and ${dbName}_test created successfully!`)
- Delete test db by updating
db/scripts/destroy.js
await knex.raw(`DROP DATABASE IF EXISTS ${dbName}_test`)
console.log(`Database ${dbName} and ${dbName}_test deleted successfully!`)
-
To test, run
npm run db:destroy
andnpm run db:create
-
Add to
package.json
under scripts:
"jest": {
"globalSetup": "./test/setup.js",
"globalTeardown": "./test/teardown.js"
},
- Create
test/setup.js
process.env.DATABASE_URL =
'postgres://postgres:postgres@localhost:5432/lehman_notes_dev_test'
const knex = require('knex')({
client: 'pg',
connection: process.env.DATABASE_URL,
})
async function runMigrations() {
await knex.migrate.latest()
}
module.exports = runMigrations
- Create
test/teardown.js
process.env.DATABASE_URL =
'postgres://postgres:postgres@localhost:5432/lehman_notes_dev_test'
const knex = require('knex')({
client: 'pg',
connection: process.env.DATABASE_URL,
})
async function rollback() {
await knex.migrate.rollback()
}
module.exports = rollback
- Create
test/notes.js
const notes = require('./../../db/notes')
test('creating a record from the db', async function() {
const allBefore = await notes.all()
expect(allBefore).toHaveLength(0)
await notes.create(
'some title',
"this is the body of the note\n and it's multiline"
)
const allAfter = await notes.all()
expect(allAfter).toHaveLength(1)
})
-
Run
npm test
which will fail -
Update
db/notes.js
async function createNote(title, body) {
const { rows } = await db.query(
'INSERT INTO notes(title, body) VALUES ($1, $2) RETURNING id',
[title, body]
)
return rows[0]
}
-
Run
npm test
which should pass now -
Create `views/notes/form.hbs’
<form method="POST" action="/notes">
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control" id="title" name="title" placeholder="Awesome Note Title" required>
</div>
<div class="form-group">
<label for="body">Note</label>
<textarea class="form-control" id="body" name="body" rows="3" required></textarea>
</div>
<input type="submit" value="Save" class="btn btn-primary mt-4"/>
</div>
</form>
-
npm install --save-exact body-parser
-
Require it on top of file
const bodyParser = require('body-parser')
and then add middleware understatic public
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
- Create route
app.post("/notes", async function(req, res) {
const { title, body } = req.body
const { id } = await notes.create(title, body)
res.redirect(`/notes/${id}`)
})
- Commit
- Update
views/notes/form.hbs
- update input fields to render values from db
<input type="text" class="form-control" id="title" name="title" placeholder="Awesome Note Title" value="{{this.title}}" required>
....
<textarea class="form-control" id="body" name="body" rows="3" required>{{this.body}}</textarea>
- Update
db/notes.js
async function getNote(id) {
const { rows } = await db.query('SELECT * FROM notes where id=$1', [id])
return rows[0]
}
...
module.exports = {
all: getNotes,
create: createNote,
get: getNote,
}
- Update
server.js
app.get('/notes/:id', async function(req, res) {
const { id, title, body } = await notes.get(req.params.id)
res.render(`notes/form`, { id, title, body })
})
- Update
db/notes.js
:
async function updateNote(id, title, body) {
await db.query('UPDATE notes SET title=$1, body=$2 WHERE id=$3', [
title,
body,
id,
])
}
...
module.exports = {
all: getNotes,
create: createNote,
get: getNote,
update: updateNote,
}
- Update
views/notes/form.hbs
onto
{{#if this.id}}
<form method="POST" action="/notes/{{this.id}}?_method=PUT"/>
{{else}}
<form method="POST" action="/notes">
{{/if}}
-
npm install —save-exact method-override
-
Import it in
server.js
const methodOverride = require('method-override’)
…
app.use(methodOverride('_method’))
...
app.put('/notes/:id', async function(req, res) {
const { id } = req.params
const { title, body } = req.body
await notes.update(id, title, body)
res.redirect(`/notes/${id}`)
})
- Add a link to update from
views/notes
under{{this.title}}
<td>
<a class="btn btn-primary" href="/notes/{{this.id}}">Update</a>
</td>
- Commit
- Update
db/notes.js
async function deleteNote(id) {
await db.query('DELETE FROM notes WHERE notes.id = $1', [id])
}
...
module.exports = {
all: getNotes,
create: createNote,
get: getNote,
update: updateNote,
delete: deleteNote,
}
- Update
server.js
app.delete('/notes/:id', async function(req, res) {
const { id } = req.params
await notes.delete(id)
res.redirect('/notes')
})
- Update
views/notes/index.hbs
under update button
<td>
<form method="POST" action="/notes/{{this.id}}?_method=DELETE">
<input class="btn btn-danger" type="submit" value="Delete" />
</form>
<td>
- Create a project from your heroku account
- Create pipeline
- Enable automatic deploys
- Add add-on Postgres
- Install/update heroku cli
- Add npm and node versions to engines block in package.json
- RUN: heroku run npm run db:migrate -a lehman-notes-part2
- Seed if you want