Skip to content

Latest commit

 

History

History
744 lines (559 loc) · 14.5 KB

tutorial.md

File metadata and controls

744 lines (559 loc) · 14.5 KB

Tutorial

Setup

  1. Make directory lehman-notes
  1. Init package.json
> npm init —yes
> npm install --save-exact --save-dev prettier
  1. 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"
  1. Eslint npm install --save-exact --save-dev eslint eslint-config-prettier - ./node_modules/.bin/eslint --init -> Use popular style guide -> Standard - update eslintrc.json to:
{
  "extends": ["standard", "prettier"]
}
  1. Commit to repo

Simple Server

  1. npm install --save-exact express

  2. 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')
})
  1. Run npm start
  2. Run npm run install —save-exact express-handlebars
  3. 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')
})
  1. 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>
  1. create views/home.hbs with:
<h1>Hello World!</h1>
  1. npm install —save-exact —save-dev nodemon

  2. Add package.json script with: "local": "nodemon server.js"

  3. Commit to repo

Make it Pretty

  1. Create public/css/stylesheet.css :
.pretty {
  background-color: blue;
  color: white;
}
  1. Update server.js under view engine setup:
app.use(express.static('public'))
  1. Update views/layouts/layout.hbs to load our stylesheet inside the head tag:
  <link rel="stylesheet" href="/css/stylesheet.css"/>
  1. Add the class to views/home.hbs:
<h1 class="pretty">Hello World!</h1>
  1. 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"/>
  1. 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>
  1. Commit to repo

Setting up the database

Create and Delete DB

  1. npm install --save-exact --save-dev knex pg

  2. Create db/index.js:

const connectionString = process.env.DATABASE_URL

module.exports = {
  connectionParams: connectionString,
}
  1. npm install —save-exact dotenv

  2. Create .env file:

DATABASE_URL=postgres://postgres:postgres@localhost:5432/lehman_notes_dev
  1. 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()
  1. Add new script in package.json: ”db:create”: “node db/scripts/create.js”

  2. 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()
  1. Add script to package.json: "db:destroy": "node db/scripts/destroy.js"

  2. Commit to repo

Migrations

  1. Create knexfile.js in root:
require('dotenv').config()
const { connectionParams } = require('./db')

module.exports = {
  client: 'pg',
  connection: connectionParams,
  pool: { min: 0, max: 7 },
}
  1. Add script to package.json: "db:migrate:make": "knex migrate:make"

  2. Run npm run db:migrate:make createNotes”

  3. 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`)
}
  1. Add scripts to package.json:
"db:migrate": "knex migrate:latest",
"db:rollback": "knex migrate:rollback"
  1. Commit to repo

Seeds

  1. Update package.json "db:seed:make": "knex seed:make"

  2. Run npm run db:seed:make notes which will create a file under seeds/notes.js with a skeleton of a seed

  3. 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',
  },
]
  1. Update seed function to:
exports.seed = async function(knex) {
  await knex('notes').del()
  await knex('notes').insert(seedData)
}
  1. Add script to package.json: "db:seed": "knex seed:run”

  2. Commit to repo

Get Notes

  1. In db/index.js
    • import on top of file: const { Pool } = require('pg')
    • add
const pool = new Pool({
  connectionString,
})

function query(text, params) {
  return pool.query(text, params)
}
- and add to export
module.exports = {
  connectionParams: connectionString,
  query,
}
  1. 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,
}
  1. 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 })
})
  1. 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>
  1. Add link to same file above the table
<a class="btn btn-success float-right mr-5" href="/notes/new">New Note</a>
  1. COMMIT

Create New Note with TDD

  1. In db/notes.js
    • add async function createNote(title, body) {}
    • export
module.exports = {
  all: getNotes,
  create: createNote,
}
  1. npm install —save-dev —save-exact jest eslint-plugin-jest

  2. Update .eslintrc.json” by adding

    • "plugin:jest/recommended” to extends
    • add
  "env": {
    "jest/globals": true
  }
  1. Add package.json task: "test": "jest --forceExit”

  2. 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!`)
  1. Delete test db by updatingdb/scripts/destroy.js
await knex.raw(`DROP DATABASE IF EXISTS ${dbName}_test`)
console.log(`Database ${dbName} and ${dbName}_test deleted successfully!`)
  1. To test, run npm run db:destroy and npm run db:create

  2. Add to package.json under scripts:

  "jest": {
    "globalSetup": "./test/setup.js",
    "globalTeardown": "./test/teardown.js"
  },
  1. 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
  1. 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
  1. 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)
})
  1. Run npm test which will fail

  2. 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]
}
  1. Run npm test which should pass now

  2. 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>
  1. npm install --save-exact body-parser

  2. Require it on top of file const bodyParser = require('body-parser') and then add middleware under static public

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
  1. 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}`)
})
  1. Commit

Update Existing Note

  1. 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>
  1. 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,
}
  1. 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 })
})
  1. 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,
}
  1. 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}}
  1. npm install —save-exact method-override

  2. 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}`)
})
  1. Add a link to update from views/notes under {{this.title}}
        <td>
          <a class="btn btn-primary" href="/notes/{{this.id}}">Update</a>
        </td>
  1. Commit

Delete a note

  1. 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,
}
  1. Update server.js
app.delete('/notes/:id', async function(req, res) {
  const { id } = req.params
  await notes.delete(id)
  res.redirect('/notes')
})
  1. 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>

Deployment - Heroku

  • 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