Full stack web application development with Mongo, Express, React, and Node.
Following examples from Pro MERN Stack by Vasan Subramanian
Chapter 2. Hello World
02-server-less-hello-world
index.html; ReactDOM library renders the componenet within the contentNode.
Babel library is a JSX transformer. (Browser-based compiler used here)
02-express
server.js; Express is a framework, and it gets most of the job done by functions called middleware. A middleware is a function that takes in an HTTP request and response object, plus the next middleware function in the chain.
Express module exports a function that can be used to instantiate an application.
app.use mounts a middleware which takes the parameter, static, to inidicate that it is a dir where static files reside.
Using ES2015 (=== ES6) specification of JavaScript
02-separate-script-file
static/App.jsx; Separate out the JSX script from index.html.
02-transform
src/App.jsx; static/App.js
babel-cli: command line tool that invokes the transformation
babel-preset-react: plugin that handles React JSX transformation
Transformation is done on a build server or development environment, and the resulting JS is pushed out to the production server. Thus, babel modules are saved as devDependencies.
02-automate
package.json(script); "scripts": {
"compile": "babel src --presets react --out-dir static",
"watch": "babel src --presets react --out-dir static -watch",
02-es2015
static/index.html(adding babel-polyfill); package.json(script);
Not all browsers uniformly support ES2015 spec.
Transpiling: conversion from one spec of JS to another. (Babel: from ES2015 to ES5)
Polyfills: things that supplement browser capabilities or global functions to use new features of ES2015 (Promise, array find(), and etc.) -> babel-polyfill = emulates a full ES2015 environment.
Chapter 3. React Components
03-react-classes
src/App.jsx; IssueList extends React.Component
React classe allows creating real components, which can be reused within other components, handle events, and etc. render() method must be present in the class to display the component.
ReactDOM.render(, contentNode) renders an instantiation of the IssueList component.
03-composing-components
src/App.jsx; component composition - splitting UI into smaller independent pieces; IssueFilter, IssueTable, IssueAdd
03-passing-data-props
src/App.jsx; Passing data from a parent component to a child component using properties.
Any data passed in from the parent can be accessed in the child component through this.props.
child(IssueRow): {this.props.issue_title}; parent(IssueTable): IssueRow issue_title="title"
03-passing-data-children
src/App.jsx; Using this.props.children; can nest other componenets at the time the component is instantiated.
03-dynamic-composition
src/App.jsx; IssueList passing the 'issue' array as a property to IssueTable. IssueTable issues={issues}; const issue = this.props.issue;
static/index.html; moved inline styles from App.jsx to index.html
Chapter 4. React State
04-setting-state
src/App.jsx; To make components that respond to user input and other events, React uses a data structure, state, in the component. It is only the change of state that can change the rendered view. React treats the component as a simple state machine.
setState(), initialization done in the constructor.
Declarative programming paradigm. Mutate the model state, and the view rerenders. But you should not modify the state directly.
setTimeout(this.createTestIssue.bind(this), 2000); bind(this) required because we want the context, or the this variable when the function is called, to be this component's instance.
04-async-state-initialization
src/App.jsx; Use of first Lifecycle method hook, componentDidMount() before this.loadData().
04-event-handling
src/App.jsx; createTestIssue method takes no parameters and appends the sample issue to the list of issues in the state.
Also, multiple binds are removed by replacing this.createTestIssue with a permanently bound version in the constructor.
04-communicate-child-to-parent
src/App.jsx; Button and handler moved to IssueAdd component
Form with owner and title fields added to IssueAdd component
Child does not have access to the parent's method. The way to communicate from the child to a parent is by passing callbacks from the parent to the child, which it can call to achieve specific tasks. i.e. pass createIssue as a callback property from IssueTable to IssueAdd.
From the child, you just call the passed in function in your handler to create a new issue.
Removed createTestIssue() and all the code used for creating a test issue.
04-stateless-components
src/App.jsx; IssueRow and IssueTable re-written as functions rather than classes
Functions that take in props and just renders based on it.
Initialized a variable called issueRows, which means we need a full-fledged function with a return value.
State vs. Props
props are immutable where as state are not. Typically, state variables are passed down to child components as props. If any event in the child effects the parent's state, the child calls a method defined in the parent. Access to this method should have been explicitly given by passing it as a callback via props. state values are computed typically inside the render() method.
Parents communicate to children via props; when state changes, the props automatically change.
Children communicate to parents via callbacks.
In a well-designed application, most components are stateless functions of their properties
Chapter 5. Express REST APIs
REST (representational state transfer)
REST is an architectural pattern for APIs.
APIs are resource based. Resource are accessed based on URI, aka endpoint. Typically use two URIs per resource: one for collection (/customers) and one for object (/customers/123). Resources can also form a hierarchy. (/customers/123/orders, /customers/123/orders/43)
To access and manipulate the resources, you use HTTP methods. (GET, POST, PUT, PATCH, DELETE) Understand the safety (GET, HEAD, OPTIONS, etc.) and idempotency (PUT, DELETE, etc.) of the methods.
Express (web application framework)
Express relies on other modules (middleware) to provide the functionality that most applications need.
Routing: takes a client request, matches it against any routes that are present, and executes the handler function that is associated with that route. Route specification consists of an HTTP method, a path specification that matches the request URI, and the route handler.
05-list-api
server.js; List API lists all issues. Not integrated with front-end code yet
Automatic Server Restart using nodemon
app.get('/api/issues', (req, res) => { ... res.json(...) ... }
05-create-api
server.js; Create API supports adding a new issue.
POST method with request body containing the new issue object to be created.
Express does not have an in-built parser that can parse request bodies to convert them into objects.
Thus, body-parser package is used. The body-parser iddleware places the result of parsing in the request's body property.
05-using-list-api
src/App.jsx; Use List API in the application front end and replace the in-memory list of issues.
Browsers support asynchronous API calls (~Ajax calls) natively via the Fetch API.
fetch() takes in the path of the URL to be fetched and returns a promise with the response as the value. The response is then parsed with json() method of the response itself, and it also returns a promise with the value as the parsed data. The parsed data will reflect what we sent from the server.
05-using-create-api
src/App.jsx; Use Create API to modify the createIssue() method. Instead of directly appending new item to the list of issues, it is first sent to the server, and the updated issue returned by the server is used.
fetch() API for POST method takes an options object in the second parameter, which includes method, Content Type header, and the body in JSON representation.
Then the updated issue is returned by the server in JSON format. As for the setting the new state, a new list of issues is created by making a copy of the existing list from the current state (this.state)
05-error-handling
server.js; As for server-side application-level validation, missing required fields and incorrect list values will be handled by simply setting the status using res.status() and sending the error message as the response. In the app.post's handler, validation function is called and in case of error, an object with an appropriate message is sent back.
/src/App.jsx; As for client-side error handling, createIssue() method is modified to detect a non-success HTTP status code. Fetch API does not throw an error for failure HTTP status codes so cannot rely on catch section. The response's property, response.ok, must be checked. If not okay, error.
Chapter 6. Using MongoDB
MongoDB is a document database. (Record is a document or an object) A document is a data structure composed of field and value pairs. Primary key is mandated in MongoDB with the reserved field name, _id.
MongoDB query language is made up of methods to achieve various operations.
06-schema-initialization
scripts/init.mongo.js; create a mongo shell script with initialization statements, setting up the db variable, removing all existing issues, if any, inserting a few samples records, and creating indexes.
06-mongodb-node.js-driver
trymongo.js; Node.js driver(npm mongodb) allows connecting and interacting with MongoDB server.
callbacks paradigm, promises paradigm, generator paradigm, async module
06-read-from-mongodb
server.js; modify List API to read from the database instead of the in-memory list of issues on the server.
setting up MongoDB connection (MongoClient.connect(...).then(...))
updating app.get(...) to read from MongoDB
src/App.jsx; updating id to _id, even List API can return a non-successful HTTP Status code so it is also handled in the front end, loadData() method.
06-write-to-mongodb
server.js; modify Create API to wrote to MongoDB, clean up validations, and remove in-memory array
Chapter 7. Modularization and Webpack
07-server-side-modules
server/issue.js; separating Issue object into its own file and exporting using module.exports
07-using-webpack-manually
/src; Tools such as webpack and browserify provide alternatives to define dependencies as you would in a Node.js application using require or equivalent statements. They automatically figure out not just your own dependent modules, but also third-party libraries. Webpack transforms and watches for changes to files.
Splitting IssueAdd, using export default (single class and the result of import is the class)
07-transform-and-bundle
Using webpack to automatically transform JSX files using babel-loader and bundling them.
webpack.config.js configuration file module is used by webpack to get its parameters.
Classes are separated creating two-level hierarchy of imports: App.jsx imports IssueList.jsx which imports IssueAdd.jsx and IssueFilter.jsx
07-libraries-bundle
For the client-side code, libraries had been included as scripts from a CDN (Content Delivery Network).
webpack is not used to create a bundle that includes these libraries as it can deal with client-side libraries installed via npm.
Browsers can cache scripts when they are not changed so separate bundle configurations (one for application code and another for all the libraries) using webpack built-in plugin, CommonsChunkPlugin.
304 response for vendor bundle means that it is not fetching the bundle if it is already there in the browser's cache.
07-hot-module-replacement
webpack-dev-server watches for client-side code changes and speed up delivery of the bundle by keeping the bundle completely in the memory, not writing it to disk. It also makes the browser wait until the bundle is ready ensuring that if you have changed any client-side code, you are guaranteed to not be served with the previous version.
webpack-dev-server can act as a proxy for API requests and forward them to the Express server. webpack-dev-server configuration is added to webpack. proxy configuration tells webpack-dev-server to look for requests matching '/api/*' and forward them to the Express server.
HMR(hot module replacement) automatically causes a browser refresh whenever there is a change in the client-side bundle. Client-side code that is responsible for dealing with modules is modified to accepts HMR.
Main objective of webpack-dev-server is to prevent inadvertent erros, such as those caused by refreshing the browser even before a bundle operation has completed. This can be damaging beause you may think you have changed the code, but the new code is not what is running on the browser.
07-hmr-using-middleware
Installing HMR as the middleware (webpack-dev-middleware webpack-hot-middleware) - wiring in the HMR pieces within the Express code itself to make it a single server that serves the APIs, and at the same time watches for changes to client-side code, rebundles it, and sends incremental updates to the client.
Need additional entry points so that webpack can built the client-side code nessary for this extra functionality into the bundle. Also need to add a plugin that generates incremental updates rather than entire bundles that can be sent to the client. THe problem with middleware approach is that a change in the server-side code causes the entire front-end bundle to be rebuilt so the author reverted back to the webpack-dev-server approach.
07-debugging
webpack gives you source maps, things that contain your original source code as you typed it in.
Browsers' development tools automatically understand source maps and correlate the two, letting you put breakpoints in your code and converting them to breakpoints in the transformed or transpiled code.
Added source map configuration in webpack config.
07-server-side-es2015
Start using import export style of modularization to keep the coding style and features used uniform across the board. Using babel-preset-es2015-node4, only unsupported ES2015 features are changed. Compiled files are saved in dist dir.
Scripts are added to compile the server, watch for changed files, and use the compiled file from the dist dir. This static compliation requires one more console to run to watch the server.
require hook binds itself to Node.js' require and dynamically compiles any new module that is loaded.
07-eslint
A linter (something that lints) checks for suspicious code that may be a bug. It can also check whether your code adheres to conventions and standards that you want to follow accross your team to make the code predictably readable. ESLint is a flexible linter that lets you define the rules that you want to follow.
ESLint requires that the configuration and the rules to apply be specified in a started file, .eslintrc. ESLint needs to be told that a certain file is being used in a certain environment.
Chapter 8. React Router
Chapter reviews the concept of routing, or handling multiple pages that need to be displayed.
A single-page application has multiple logical pages within the application. It's just that the page load happens only the first time; after that, each of the other views is loaded by manipulating or changing the DOM. Need routing to navigate between different views.
Routing techniques: two ways to connect a page to something that the browser recognizes and indicates that "this is the page that the user is viewing." - 1. Hash-based (using the anchor portion of the URL, everything following the #) 2. Push state aka browser history (new HTML5 API that lets JS handle the page transitions at the same time preventing the browser from reloading the page when the URL changes.)
08-simple-routing
Creaeting two routes: one for the issue list and another (placeholder) for viewing and editing a single issue. Any new front-end packages should be added to the vendor section of the webpack configuration.
React Router works by taking control of the main component that is rendered in the DOM so instead of App component, we need to render a Router component.
08-route-parameters
Path can be a complex string pattern that can match optional segments and even specify parameters like REST API paths. Value of the string available in a property object, params. Use React Reouter provided Redirect component to implement a redirect. Use React Router provided Link component to create links.
08-route-query-string
React Router also parses the query string and makes it available to the coponent in a property object called location, which contains various keys, including path, unparsed query string, and a parsed query object.
Query strings are ideal for specifying a filter to the issue list.
First, the back-end API is changed to handle this with find(filter).
Then, this is integrated into the client-side code. Two ways for auery string to be supplied to a Link.
Lastly, the filter is passed to the REST API call while loading the list of issues in IssueList component, in the call to fetch().
Use componentDidUpdate() to hook into a method that tells us that the route query string has changed so that we can reload the list.
08-programmatic-navigation
Must navigate by changing the browser's URL using code. Use React Router's router object. router.push() to set the filter programmatically. First, inject the router property into the components which need it, using React Router's withRouter method, which wraps a given component documentation under withRouter and RouterContext. A method is added in IssueList to set a new filter, and the router is used to change the URL based on the filter.
08-nested-routes
Create a common decoration (header) for all pages across the application using nesting of routes. Applicatino uses one level of nesting where a main component has a header, the contents section, and a footer. To configure this, nest the routes in the router configuration. Create a new component, App, corresponding to the decorator that holds the header and footer. React Router will pass via props.children the child component resolved as a result of route matching.
IndexRoute component can be used to indicate that a particular component needs to be displayed if only the parent route is matched.
08-browser-history
Using browser history is recommended because 1) URL looks cleaner and easier to read and 2) if you need to do server-side rendering, hash-based method will not work as the hash-anchor is just a location within the page so bots cannot crawl.
Chapter 9. Forms
Creating more flexible filter based on user input, filling in the edit page with a form, and adding ability to delete issues from IssueList page.
09-more-filters-in-list-api
Adding filter for the integer field, effort; Comparison operators need to be specfied as an object with the key being the operator so multiple operators can be added for the same field.
MongoDB is strict about data types so need to convert the query parameters to an integer before setting the effort value in the filter.
09-filter-form
One option to get a user input is to get it the conventional HTML way (i.e. IssueAdd with onClick)
Another way is to supply a value that is a variable, something that is part of React's state. This connects a state variable to the value that is displayed. Form components that have their value bound to the state are called controlled components. If not bound (i.e. IssueAdd), they are uncontrolled.
Modify IssueFilter to render with the filter form. onChange events from each of the inputs are handled by setting the state variable to the event target's value.
09-get-api
Get API to retrieve a single issue and display it in the edit page. To get a single resource, REST dictates that we need an API of the form /api/issues/issue_id. In the handler, get the ID as a parameter in the request object (req.params.id), convert the string ID to MongoDB ObjectID, and then retrieve a single record returning the document as a JSON in the document.
09-edit-page
Create a form to display all the values of the issue with the editable fields as form inputs. Make these controlled components by connecting them to an object in the state called issue. Make a common method called onChange and supply the name property to each input to differentiate between them.
load() method to fetch the issue properties using the Get API, which is called from componentDidMount() and componentDidUpdate()
Fields need to be converted to strings to be edited.
09-ui-components-number-input
To have the form's state to store the fields in their natural data types and all of the data type conversion routines to be shared, resuable UI components are made for the no-string inputs, which emit natural data types in their onChange handlers.
Number Input: First, separate the two states: 1) value of the persisted issue's field and 2) transient value, which is the state while it's being edited by the user. Transient state and other things needed to manage the editing and conversion within a UI component, NumInput. Hook into the event of losing input focus from the input using onBlur() handler. The onblur event occurs when an object loses focus and is most often used with form validation code (e.g. when the user leaves a form field).