Title Page Copyright and Credits Building Enterprise JavaScript Applications Dedication Packt Upsell Why subscribe? PacktPub.com Contributors About the author About the reviewer Packt is searching for authors like you Preface Who this book is for What this book covers Section 1 – Theory and practice Section 2 – Developing our backend API Section 3 – Developing our frontend UI Section 4 – Infrastructure and automation Section 5 – Important JavaScript concepts and syntax What is not covered To get the most out of this book Download the example code files Conventions used Get in touch Reviews The Importance of Good Code Technical debt What is technical debt? Causes of technical debt The debt spiral Consequences of technical debt Technical debt leads to low morale Consequences of low morale Repaying technical debt through refactoring Preventing technical debt Informing the decision makers The triple constraint The fallacy of the triple constraint Refuse to develop Don't be a hero Defining processes Test-Driven Development Understanding the TDD process Fixing bugs Benefits of TDD Avoiding manual tests Tests as specification Tests as documentation Short development cycles Difficulties with TDD adoption When not to use TDD Summary The State of JavaScript Evolution of the web application Just-in-time (JIT) compilers Single page applications (SPAs) Isomorphic JavaScript applications Benefits of Node.js Context switching Switching between projects Switching between languages The business perspective Shared code Summary Managing Version History with Git Setting up Git Creating a new repository Configuring Git Configuring a user Learning the basics Committing to history Understanding file states in Git The three tracked states Staging our changes Quick recap Branching and merging Git branches Branching models The Driessen model Creating a development branch Creating feature branches Naming sub-branches Merging branches Examining more realistic examples Keeping the dev Branch Bug-Free Keeping our history clean Keeping our history clean with git rebase Using merge and rebase together Releasing code Semantic versioning Creating a release branch Tagging releases Hotfixes Working with others Creating a remote repository Pulling and pushing Cloning a repository Conducting peer review through pull requests Summary Setting Up Development Tools What is Node.js? Terminology Modules The dawn of modules The birth of Node.js modules Adoption of the CommonJS standard Fulfilling the encapsulation requirement Standardizing module formats Installing Node Using nvm to install Node Documenting Node versions Starting projects with npm Using yarn instead of npm Package version locking Offline cache Speed Installing yarn Getting familiar with the yarn CLI npm and yarn, together Creating an HTTP server Our HTTP server in detail Transpiling ES6 with Babel Babel is a transpiler...and more! Different faces of Babel @babel/cli @babel/register Using @babel/register for tests @babel/node @babel/core @babel/polyfill Adding Babel CLI and polyfill Using Babel CLI to transpile our code Plugins and presets The env preset Separating source and distribution code Importing the Babel polyfill Consolidating commands with npm scripts Ensuring cross-platform compatibility Automating development using nodemon Linting with ESLint Installing ESLint Linting our code Adding lint script to package.json Installing the ESLint extension Adding pre-commit hooks Committing our code into Git Using .gitignore to ignore files Summary Writing End-to-End Tests Understanding different types of test Structuring our test suite with the testing pyramid When implementing a new feature, write your E2E tests first Following a TDD workflow Gathering business requirements Formalizing requirements through documentation Refining requirements into specification Writing tests as specification Test-driven development Writing manual tests Exploratory testing Maintenance Gathering requirements Setting Up E2E tests with Cucumber Features, scenarios, and steps Gherkin keywords Specifying our feature Writing our first scenario Laying out our step definitions Running our scenarios Implementing step definitions Calling our endpoint Asserting results Using a debugger for Node.js debugging Using Chrome DevTools Using ndb Using the Visual Studio Code debugger Retaining line numbers Examining the req object Making work-in-progress (WIP) commits Asserting the correct response status code You ain't gonna need it (YAGNI) Asserting the correct response payload Asserting the correct response payload content Refactoring Isolating contexts for each scenario Making failure more informative Removing hardcoded values Validating data type Refactoring our tests Using scenario outlines Combining duplicate step definitions Refactoring our application Choosing a framework Migrating our API to Express (Re)defining routes Using body-parser middleware Run E2E test Moving common logic into middleware Validating our payload Checking for required fields Checking property type Checking the payload property's format Refactoring our step definitions Testing the success scenario Summary Storing Data in Elasticsearch Introduction to Elasticsearch Elasticsearch versus other distributed document store Installing Java and Elasticsearch Installing Java Installing and starting Elasticsearch Understanding key concepts in Elasticsearch Elasticsearch is a JSON document store Document vs. relationship data storage Understanding indices, types, documents, and versions Querying Elasticsearch from E2E tests Indexing documents to Elasticsearch Cleaning up after our tests Deleting our test user Improving our testing experience Running tests in a test database Separating development and testing servers Making a standalone E2E test script The shebang interpreter directive Ensuring Elasticsearch is running Running the test API server in the background  Checking our API server is ready Checking API status using netstat/ss Cleaning up the background process Running our tests Summary Modularizing Our Code Modularizing our code Modularizing our middleware Modularizing our request handlers The single responsibility principle Decoupling our validation logic Creating the ValidationError interface Modularizing our validation logic Creating engines Adding a user profile Writing a specification as a test Schema-based validation Types of schema Picking an object schema and validation library Interoperability Expressiveness Creating our profile schema Rejecting additional properties Dynamic mapping in Elasticsearch Adding specificity to a sub-schema Adding a title and description Specifying a meta-schema Specifying a unique ID Creating a schema for the Create User request payload Picking a JSON Schema validation library Validating against JSON Schema with Ajv Generating validation error messages Generalizing functions Updating the npm build script Testing the success scenario Resetting our test index Summary Writing Unit/Integration Tests Picking a testing framework Installing Mocha Structuring our test files Writing our first unit test Describing the expected behavior Overriding ESLint for test files Understanding arrow functions in Mocha Specifying ESLint environments Running our unit tests Running unit tests as an npm script Completing our first unit test suite Unit testing ValidationError Unit testing middleware Asserting deep equality Asserting function calls with spies Simulating behavior with stubs Testing all middleware functions Unit testing the request handler Stubbing create Dependency injection Monkey patching Dependency injection versus monkey patching Modularity Readability Reliance on third-party tools Following the dependency injection pattern Promises and Mocha Dealing with rejected promises Completing the unit tests Unit testing our engine Integration testing our engine Adding test coverage Reading a test coverage report Improving test coverage Code coverage versus test quality You don't have to test everything, all the time Unifying test coverage Ignoring files Finishing up Summary Designing Our API What it means to be RESTful What is REST? What REST is not Should my API be RESTful? Designing our API Consistent Common consistency Sending the correct HTTP status code Using HTTP methods Using ISO formats Local consistency Naming convention Consistent data exchange format Error response payload Transversal consistency Domain consistency Perennial consistency Breaking changes in APIs Future-proofing your URL Future-proofing your data structure Versioning Intuitive URLs for humans Favor verbosity and explicitness Keep It Simple Stupid (KISS) Completing our API Summary Deploying Our Application on a VPS Obtaining an IP address Managed DNS Setting up a Virtual Private Server (VPS) Creating a VPS instance Choosing an image Choosing a size Picking a data center region Selecting additional options Naming your server Connecting to the VPS Setting up user accounts Creating a new user Adding a user to the sudo group Setting up public key authentication Checking for existing SSH key(s) Creating an SSH key Adding the SSH key to the remote server Using ssh-copy-id Providing extra security Disable password-based authentication Disable root login Firewall Configuring the time zone Running our API Keeping our API alive with PM2 Killing a process Keeping PM2 alive Running our API on port 80 Privileged ports Possible solutions Running as root De-escalating privileges Setting capabilities Using authbind Using iptables Using reverse proxy What's a proxy? What's a reverse proxy? Setting up NGINX Configuring NGINX Understanding NGINX's configuration file Configuring the HTTP module Splitting nginx.conf into multiple files From IP to domain Buying a domain Understanding DNS Updating the domain nameserver Building our zone file NS records A and AAAA Start of Authority (SOA) Updating NGINX Summary Continuous Integration Continuous Integration (CI) Picking a CI server Integrating with Travis CI Configuring Travis CI Specifying the language Setting up databases Setting environment variables Activating our project Examining Travis CI results Continuous Integration with Jenkins Introduction to Jenkins Freestyle projects Pipeline Setting up a new Jenkins server Creating the jenkins user Configuring time Installing Java Installing Jenkins Installing NGINX as a reverse proxy Configuring the firewall Updating our DNS records Configuring Jenkins Composing a Jenkinsfile The Pipeline DSL syntax Declarative versus scripted pipelines The declarative pipeline The scripted pipeline Setting up the environment Installing Docker Integration with GitHub Providing access to the repository The Personal Access (OAuth) Token Using the GitHub plugin Setting up GitHub service hooks manually Creating a new folder Creating a new pipeline Running the first build Summary Security – Authentication and Authorization What is Authentication? Introduction to password-based authentication Hashing passwords Cryptographic hash functions Picking a cryptographic hashing algorithm Hash stretching Hash stretching algorithms Preventing brute-force attacks against a single user Protecting against brute-force attacks Reverse lookup table attacks Protecting against reverse lookup table attacks Implementing password-base authentication Updating existing E2E tests Generating a random digest Picking a bcrypt library Using the bcryptjs library Validating a digest Updating an existing implementation Retrieving the salt Implementing the Retrieve Salt endpoint Implementing a Retrieve Salt engine Generating a salt for non-existent users Writing E2E tests Implementation Login Writing tests Implementing Login Keeping users authenticated JSON web tokens (JWTs) Anatomy of a JWT Header Payload and claims Registered claim names Public claim names Private claim names Example claim Signature Asymmetric signature generation Symmetric signature generation Picking an algorithm A note on encryption Terminology and summary Responding with a token Adding E2E Tests Implementation Multiline environment variables Generating the token Attaching the token HTTP cookies Cross-Site Scripting (XSS) Cross-Site Request Forgery (XSRF) HTTP headers The Authorization header Writing tests Features and scenarios Implementation step definitions Verifying the digest in the request Next steps Preventing man-in-the-middle (MITM) attacks Encrypting digests Block cipher Exploring the Secure Remote Password (SRP) protocol Summary Documenting Our API Overview of OpenAPI and Swagger Picking an API specification language Swagger vs OpenAPI Swagger Toolchain Swagger Editor Swagger UI Swagger Inspector Swagger codegen Defining an API specification with OpenAPI Learning YAML An overview of the root fields Specifying the GET /salt endpoint Specifying parameters Specifying responses Specifying the Create User endpoint Specifying the request body Defining common components Specifying the Retrieve User endpoint Specifying the Replace Profile endpoint Specifying the rest of the endpoints Generating documentation with Swagger UI Adding the Swagger UI to our repository Using our specification in the Swagger UI Exposing swagger.yaml from our API Enabling CORS Same-origin policy Cross-Origin Resource Sharing (CORS) Final touches Replacing the specification URL Removing the header Deployment Summary Creating UI with React Picking a front-end framework/library Vanilla JavaScript vs. frameworks Choosing a framework/library Popularity/community Features Virtual DOM JSX Post-React Flexibility Performance Cross-platform Hybrid applications with Ionic Native UI with React Native and Weex Learning curve Conclusion Getting started with React What is React? Components Virtual DOM How Virtual DOM improves performance React is declarative React summary Starting a new repository Adding some boilerplate Creating our first component JSX Transpiling JSX Defining React components Functional and class components Pure components Maintaining the state and listening for events Handling events setState and immutability Rendering the state Submitting forms Uncontrolled form elements Resolving CORS issues Disabling the Button component Controlled form elements Modularizing React Client-side modules Module bundling Browserify Webpack Rollup Parcel Asynchronous module loading AMD and Require.js Universal Module Definition SystemJS and the Loader specification jspm Module bundler versus module loader HTTP/2 Webpack Modularizing our components Entry/output Loaders Plugins Copying files Final steps Summary E2E Testing in React Testing strategies Automated UI testing Unit testing Logical units Component units Browser testing Writing E2E tests with Gherkin, Cucumber, and Selenium Adding test script Specifying a feature Adding IDs to elements Selenium WebDriver API Using Selenium WebDriver Headless browsers Browser drivers Setup and teardown Implementing step definitions Navigating to a page Typing into input Asserting a result Running the tests Adding multiple testing browsers Running our backend API Dynamic string substitution with Webpack Serving the API from a submodule Defining the happy scenario Generating random data Making step definitions more generic Clicking Waiting Render components based on state Routing with React Router Basics Router Route matching Supporting the History API Navigation TDD Login Writing tests Implementing Login Over to you Summary Managing States with Redux State management tools Redux MobX Redux versus MobX Converting to Redux Creating the store Lifting the state up Dispatching actions Updating the state with the Reducer Connecting with React Redux Wrapping with the Provider component Connecting to the Redux store mapStateToProps mapDispatchToProps Decoupling Redux from components Summary Migrating to Docker Problems with manual deployment Introduction to Docker What are containers? Workflow How does Docker solve our issues? Mechanics of Docker What is a Docker container? Control groups Namespaces LXC and Docker Virtual Machines Containers versus Virtual Machines What is a Docker image? Images are layered Running a container Setting up the Docker Toolchain Adding the Docker package repository Installing Docker Docker Engine, Daemon, and Client Running Elasticsearch on Docker  Running a container Understanding the docker run option Identifying a container by name  Setting environment variables Running as daemon Network port mapping 0.0.0.0 Updating our test script Dockerizing our backend API Overview of a Dockerfile Writing our Dockerfile Picking a base image Copying project files Building our application Specifying the executable Building our image Running our image Persisting data Following best practices Shell versus exec forms Allowing Unix signaling Running as a non-root user Taking advantage of the cache Caveats Using a lighter image Removing obsolete files Multi-stage builds Security Summary Robust Infrastructure with Kubernetes High availability Measuring availability Following the industry standard Eliminating single points of failure (SPOF) Load balancing versus failover Load balancing DNS load balancing Layer 4/7 load balancers Layer 4 load balancers Layer 7 load balancing High reliability Testing for reliability High throughput High scalability Clusters and microservices Microservices Clusters Cluster management Cluster-level tools Discovery service Scheduler Global configuration store Provisioning tools Picking a cluster management tool Control Planes and components Master components kube-apiserver kube-control-manager Node components Container runtime kubelet kube-proxy Kubernetes objects The four basic objects High-level objects Controllers Setting up the local development environment Checking hardware requirements Cleaning our environment Disabling swap memory Installing kubectl Installing Minikube Installing a Hypervisor or Docker Machine Creating our cluster Setting environment variables for the local cluster Running minikube start Updating the context Resetting the cluster Creating our first Pod Running Pods with kubelet Running Pods with kubectl run Understanding high-level Kubernetes objects Declarative over imperative Deleting deployment Creating a deployment manifest A note on labels Running pods declaratively with kubectl apply Kubernetes Object management hierarchy Configuring Elasticsearch cluster Networking for distributed databases Configuring Elasticsearch's Zen discovery Attaching hostnames to Pods Working with StatefulSets Ordinal index Working with services Linking StatefulSet to a service Updating Zen Discovery configuration Validating Zen Discovery Deploying on cloud provider Creating a new remote cluster Switching contexts Configuring nodes for Elasticsearch Running commands on multiple servers Using pssh Using init containers Running the Elasticsearch service Validating Zen Discovery on the remote cluster Persisting data Introducing Kubernetes Volumes Defining Volumes Problems with manually-managed Volumes Introducing PersistentVolume (PV) Consuming PVs with PersistentVolumeClaim (PVC) Deleting a PersistentVolumeClaim Deleting a PersistentVolume Problems with manually provisioning PersistentVolume Dynamic volume provisioning with StorageClass Defining a StorageClass Using the csi-digitalocean provisioner Provisioning PersistentVolume to StatefulSet Configuring permissions on a bind-mounted directory Visualizing Kubernetes Objects using the Web UI Dashboard Launching the Web UI Dashboard locally Launching the Web UI Dashboard on a remote cluster Deploying the backend API Publishing our image to Docker Hub Creating a Deployment Discovering Services using kube-dns/CoreDNS Running Our backend Deployment Creating a backend Service Exposing services through Ingress Deploying the NGINX Ingress Controller Deploying the Ingress resource Updating DNS records Summary Other Books You May Enjoy Leave a review - let other readers know what you think