Skip to content

Instantly share code, notes, and snippets.

@matt-winzer
Last active December 10, 2019 19:02
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save matt-winzer/532faab154b1233237a2f227b22f86cd to your computer and use it in GitHub Desktop.
Save matt-winzer/532faab154b1233237a2f227b22f86cd to your computer and use it in GitHub Desktop.

Session 1 - Project Intro and Scaffolding

Objectives

  • Discuss project goals and outcomes
  • Scaffold font-end
    • Create React App
    • SCSS Modules
    • Linter
  • Scaffold back-end
    • Node/Express/PostgreSQL
    • Linter
    • Sequelize!!!
    • MVC architecture

Resources

Project Outline

  • Full stack E-Commerce application
  • Pages
    • Home
    • Products list
    • Product detail
    • Purchase (stripe)
    • User profile
      • Order history

Notes

Express Error Handlers

// The following 2 `app.use`'s MUST follow ALL your routes/middleware
app.use(notFound)
app.use(errorHandler)

function notFound(req, res, next) {
  res.status(404).send({error: 'Not found!', status: 404, url: req.originalUrl})
}

// eslint-disable-next-line
function errorHandler(err, req, res, next) {
  console.error('ERROR', err)
  const stack =  process.env.NODE_ENV !== 'production' ? err.stack : undefined
  res.status(500).send({error: err.message, stack, url: req.originalUrl})
}

Session 1 Stretch Goals

  • Add and configure eslint to your client application: instructions
  • Create the Categories table (if you didn’t during class) using sequelize migrations
    • Use the lesson video as a guide
  • Create the Products table using sequelize migrations
  • Add associations between the two tables
  • Use the notes below as a guide to creating foreign keys and associations

Sample Foreign Key

Product Migration file

category_id: {
        type: Sequelize.INTEGER,
        references: {
          model: 'Categories',		// NOTE: this is the table name
          key: 'id',
          as: 'category_id'
        },
        allowNull: false
      }

Sample Association

Category Model file

Category.associate = function(models) {
    // associations can be defined here
    Category.hasMany(models.Product, {
      foreignKey: 'category_id'
    })
  };

Product Model file

Product.associate = function(models) {
    // associations can be defined here
    Product.belongsTo(models.Category, {
      foreignKey: 'category_id'
    })
  };

Entity Relationship Diagram

ERD

React Linting

NOTE: I initially screwed up these directions for those of you using create react app. I have added a video below walking through how to fix and install correctly. If you are starting fresh, you can follow the section below and/or watch the final few minutes of the video.

With Create React App

Applications bootstrapped with the create-react-app tool already have eslint intalled under the hood. The configuration is hidden from us, but you can actually add your own eslint rules quite easily.

  1. Create an .eslintrc.js file in the /src folder of your client.
  2. Add rules so that you are extending the react-app base configuration.
  3. Customize your rules as needed (for indentation, semicolons, etc.)

Here is an example .eslintrc.js file that you can use as a template:

module.exports = {
  'extends': 'react-app',
  'rules': {
      'indent': [
          'error',
          2
      ],
      'linebreak-style': [
          'error',
          'unix'
      ],
      'quotes': [
          'error',
          'single'
      ],
      'semi': [
          'error',
          'never'
      ]
  }
}

Without Create React App

Install

In your client repo, install the eslint and eslint-plugin-react packages as dev dependencies:

$ npm install --save-dev eslint eslint-plugin-react

Initialization

  • Start the eslint configuration wizard
$ npx eslint —-init
  • Make sure to answer the following questions in this way (the rest are up to your style)
    • What type of modules does your project use? Javascript modules (import/export)
    • Which framework does your project use? React
    • Where does your code run? Browser

Configuration

  • In the eslintrc file that the wizard generates, modify the extends section to include the react plugin, ex:
"extends": [
    "eslint:recommended",
    "plugin:react/recommended"
],
  • If you like using fat arrow functions for implicitly binding class methods, add the following line near the top of your eslintrc file:
"parser": "babel-eslint",
  • NOTE: create-react-app applications come with babel-eslint already installed. If you are not using CRA you must install it manually as a dev dependency:
$ npm install --save-dev babel-eslint

Session 2 - Routing, Components, Fetching Products

Objectives

  • Review Session 1 Stretch Goals
    • Create Products table
    • Associations
    • Seed Products table
    • Setup React linter
  • Setup React router
  • Create major components
    • Navigation
    • Home page
    • Products list page
    • Card component
  • STRETCH: Create Product detail page
  • STRETCH: Server refactor (route & controller files)
  • STRETCH: SCSS variables & modules

Resources

Sample Data

Products:

[
      {
        "id": 1,
        "name": "Awesome Shirt",
        "description": "Sapiente ut sed labore. Omnis fuga exercitationem explicabo omnis laboriosam. Minima eum consequuntur et illum.",
        "price": 1500,
        "img_url": "http://lorempixel.com/640/480",
        "category_id": 1
      },
      {
        "id": 2,
        "name": "Amazing Shorts",
        "description": "Enim voluptatum excepturi laboriosam quis. Consequuntur perferendis consequatur sed corporis. Itaque rerum aliquam ut tempora perferendis.",
        "price": 2500,
        "img_url": "http://lorempixel.com/640/480",
        "category_id": 1
      },
      {
        "id": 3,
        "name": "Mousepad",
        "description": "Suscipit molestias et nobis quo et voluptatibus voluptatum excepturi. Labore et et. Id aliquam sed sed dolor voluptas nihil accusamus.",
        "price": 1000,
        "img_url": "http://lorempixel.com/640/480",
        "category_id": 2
      },
      {
        "id": 4,
        "name": "10 Pack HDMI Cords",
        "description": "Vero qui sapiente totam quo voluptatum aut iste odit maiores. Est est dicta. Commodi aperiam rerum vel. Voluptatem itaque tenetur odio voluptate exercitationem eum. Ut minima autem quam minus quasi aperiam.",
        "price": 5000,
        "img_url": "http://lorempixel.com/640/480",
        "category_id": 2
      },
      {
        "id": 5,
        "name": "Beautiful Bookshelf",
        "description": "Velit temporibus quisquam voluptas reprehenderit et illo qui. Voluptas ad suscipit harum amet sit veritatis tenetur. Eveniet pariatur non consequatur quia id voluptates ad. Quaerat debitis et architecto dolore.",
        "price": 7500,
        "img_url": "http://lorempixel.com/640/480",
        "category_id": 3
      },
      {
        "id": 6,
        "name": "Da Best Desk",
        "description": "Libero est nisi. Omnis temporibus aliquid ea repudiandae et. Id minima aliquid aliquam eaque sed eos. Sunt blanditiis ducimus praesentium maxime odit. Voluptatum quia non enim unde non illo.",
        "price": 10000,
        "img_url": "http://lorempixel.com/640/480",
        "category_id": 3
      }
]

Components

Card.js

import styles from './card.module.scss'

const Card = ({ children, className }) => {
  return (
    <div className={`${styles.card} ${className}`}>
      {children}
    </div>
  )
}

export default Card

card.module.scss

@import ‘../../styles/variables’;

div.card {
  background-color: $some-color;
  border-radius: 5px;
  padding: 1.5rem;
  box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);

  & > :first-child {
    margin-top: 0;
  }
  
  & > :last-child {
    margin-bottom: 0;
  }
}

Session 4 - Stripe Integration

Objectives

  • Install and configure required packages
    • stripe
    • dotenv
  • Server: handle Stripe checkout request
    • Route for checkout
    • Secret keys in .env
    • Respond with session
  • Client: initiate a Stripe checkout request
    • Static test data
    • Create pages for success and cancel redirects
    • Dynamic live data
  • STRETCH: add a stripe webhook

Resources

Code Snippets

Server

require('dotenv').config()
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY)

Server

app.post('/api/checkout', async (req, res, next) => {
  const lineItems = [{
    name: 'T-shirt',
    description: 'Comfortable cotton t-shirt',
    images: ['http://lorempixel.com/400/200/'],
    amount: 500,
    currency: 'usd',
    quantity: 1,
  }]

  try {
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line_items: lineItems,
      success_url: 'http://localhost:3000/success',
      cancel_url: 'http://localhost:3000/cancel'
    })
    res.json({ session })
  }
  catch (error) {
    res.status(400).json({ error })
  }
})

Client

initiateStripeCheckout = async () => {
  const stripe = window.Stripe('pk_test_U97OqImzWdzzRotfPzXt3Req00UQtQ1rnD')

  try {
    // Initiate checkout session to get session id
    const response = await fetch('http://localhost:4000/api/checkout', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    })
    const data = await response.json()
    const sessionId = data.session.id
    console.log(sessionId)

    // Redirect to checkout
    const result = await stripe.redirectToCheckout({ sessionId })

  } catch (error) {
    console.log('STRIPE ERROR', error)
  }
}

Session 5 - Auth0 Login/Logout

Objectives

  • Auth0 Website:
    • Create Auth0 tenant
    • Create Auth0 application
    • Quickstart: React Tutorial
  • Get application keys
    • Domain
    • Client ID
  • Configure:
    • Callback URLs
    • Logout URLs
    • Allowed Web Origins
  • Client:
    • Install auth0 package: npm install --save @auth0/auth0-spa-js
    • Create react-auth0-wrapper.js: Populate w/code given
    • Add Login/Logout buttons to Navigation component
    • Add auth0Provider in index.js
    • Create auth_config.json in src
      • Add domain and clientID
    • Create profile page/component
      • Create a link in Nav to /profile
  • STRETCH: Add protected route
  • STRETCH: Refactor Navigation bar

Resources

Notes

  • After installing auth0 package, npm audit fix took care of security vulnerabilities

Session 6 - Auth0 Authenticated API Calls

Objectives

  • Auth0 Dashboard:
    • Create an API
    • Name, Identifier, Signing Algorithm
  • Server:
    • Install additional dependencies: npm install express-jwt jwks-rsa
    • Configure dependencies & middleware in server.js
    • Create protected endpoint
  • Client:
    • Add API audience to: auth_config.js
    • Add API audience to: index.js
    • Create test component: ExternalApi.js
    • Create PrivateRoute HOC
    • Protect ExternalApi.js using PrivateRoute in Router
    • Update Navigation to include link to protected route
  • STRETCH: Make /profile a protected route

Resources

Session 7 - React Testing w/Jest and Enzyme

Objectives

  • Discuss importance of testing
    • Unit tests
    • Integration tests
    • End-to-End tests
  • Review ‘anatomy’ of a test
  • Setup Jest & Enzyme
  • Write unit tests with Jest & Enzyme
    • Shallow renders
    • Component instances
    • Jest mock function calls (spies)
    • API calls (componentDidMount)
  • Group tests using describe

Resources

Snippets

setupTests.js

import Enzyme from ‘enzyme’
import Adapter from ‘enzyme-adapter-react-16

Enzyme.configure({ adapter: new Adapter() })

Mocking Auth0

import * as Auth0 from '../../react-auth0-wrapper'

jest.spyOn(Auth0, ‘useAuth0’).mockImplementation(() => {
    return {
      isAuthenticated: false
    }
  })

Mocking lifecycle methods

const spyDidMount = jest.spyOn(ProductsList.prototype, ‘componentDidMount’)

Mocking fetch requests

window.fetch = jest.fn().mockImplementation(() => {
    return Promise.resolve({
      status: 200,
      json: () => {
        return Promise.resolve({
          products: [{ id: 1, name: ‘thing’}, { id: 2, name: ‘thing2’}]
        })
      }
    })
  })

Handling async issues (one way)

it('tests something but must wait for async', (done) => {
  process.nextTick(() => {
    // Updates to state should be reflected in here
    // Write expectations...
    done()
  })
})

Session 8 - React Testing Continued

Objectives

  • Write unit tests with Jest & Enzyme
    • Shallow renders
    • Component instances
    • HTML Element instances
    • Jest mock function calls (spies)
    • API calls (componentDidMount)
  • Group tests using describe
  • Keep tests DRY
    • Use beforeEach
    • Use afterEach and afterAll for cleanup & resetting

Resources

Snippets

Mocking Auth0

import * as Auth0 from '../../react-auth0-wrapper'

jest.spyOn(Auth0, ‘useAuth0’).mockImplementation(() => {
    return {
      isAuthenticated: false
    }
  })

Mocking lifecycle methods

const spyDidMount = jest.spyOn(ProductsList.prototype, ‘componentDidMount’)

Mocking fetch requests

window.fetch = jest.fn().mockImplementation(() => {
    return Promise.resolve({
      status: 200,
      json: () => {
        return Promise.resolve({
          products: [{ id: 1, name: ‘thing’}, { id: 2, name: ‘thing2’}]
        })
      }
    })
  })

Handling async issues (one way)

it('tests something but must wait for async', (done) => {
  process.nextTick(() => {
    // Updates to state should be reflected in here
    // Write expectations...
    done()
  })
})

Session 9 - Containerization w/Docker

Objectives

  • Describe what Docker is
  • Describe how Docker works
  • Use Docker images & containers
    • Get comfortable with important Docker CLI commands
  • Write Docker files
    • ‘Recipes’ for our application
    • Build Docker images from Docker files
  • STRETCH: Use Docker volumes for development
  • STRETCH: Use Docker network for multi-container communication
  • STRETCH: Use Docker compose for multi-container apps

Resources

Docker CLI Commands

System

View system wide info about docker installation:

docker info

Images

Run an image (create a container):

docker run <image-name>
  • if image isn’t cached locally it will be downloaded from docker hub

Run an image and give it a name:

docker run --name <name> <image-name>

View images cached on local machine:

docker images

View all images (including ones used as dependencies):

docker images -a

Delete an image

docker rmi <image-name>

Pull an image from docker hub:

docker pull <image-name> : <version>

View build/command history of an image:

docker history <image-name>

Containers

View all containers on local machine (running or not):

docker ps -a

View currently running containers:

docker ps

Stop a currently running container:

docker stop <container-name>

Start a container on local machine:

docker start <container-name>

Remove a container from local machine:

docker rm <container-name>
  • Container must be stopped

Force remove a container:

docker rm -f <container-name>
  • Will remove even if running

Building Images

Build from a docker file in current directory

docker build -t <tag-name> .

Longer Commands

Sample full run command:

docker run -d -p 1000:3000 —name slytherin_rulez nodeserver

Add a flag to run command to have an auto-stopping container: --rm

docker run -d -p 1000:3000 —name slytherin_rulez --rm nodeserver

Networks

View docker networks:

docker network ls

View connections to specific network:

docker network inspect <network-name>

Mongo Example

Start mongo container:

docker run -d --name mongo mongo:latest
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment