├── .babelrc ├── .gitignore ├── README.md ├── dist └── bundle.js ├── index.html ├── package.json ├── src ├── app │ ├── components │ │ ├── Dashboard.jsx │ │ ├── Login.jsx │ │ ├── Main.jsx │ │ ├── Navigation.jsx │ │ ├── Signup.jsx │ │ ├── TaskDetail.jsx │ │ ├── TaskList.jsx │ │ ├── TaskListItem.jsx │ │ └── UsernameDisplay.jsx │ ├── index.jsx │ └── store │ │ ├── history.js │ │ ├── index.js │ │ ├── mutations.js │ │ ├── reducer.js │ │ ├── sagas.js │ │ └── sagas.mock.js └── server │ ├── authenticate.js │ ├── communicate-db.js │ ├── connect-db.js │ ├── defaultState.js │ ├── index.js │ ├── initialize-db.js │ ├── server.js │ ├── server.mock.js │ ├── server.spec.js │ └── utility.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env",{ 4 | "targets":{ 5 | "node":"current" 6 | } 7 | }], 8 | "@babel/preset-react" 9 | ] 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | #dist/bundle.js 4 | .log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Full Stack React Express Application 2 | 3 | ## Introduction 4 | This repository contains a simple Full Stack Express / React application. It is intended to demonstrate as wide an array of features as possible while still keeping the app simple and easy to understand. 5 | 6 | This application accompanies the course on Pluralsight Building a Full Stack Application with Express and React [Note: Add link]. 7 | 8 | This application consists of Front End component (located in the `app` directory) that is built with Redux and React. It also has a Back End component (located in the `server` directory) that uses Express to manage 9 | 10 | ## Installation 11 | First, install the programs required to run the application: 12 | 13 | - [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) 14 | - [Node.js](https://nodejs.org/en/download/) 15 | - [Mongo.DB](https://docs.mongodb.com/manual/installation/) 16 | 17 | Next, clone this repository and install dependencies: 18 | 19 | ``` 20 | git clone git@github.com:danielstern/express-react-fullstack.git 21 | ``` 22 | 23 | ``` 24 | npm install 25 | ``` 26 | 27 | Also, make sure MongoDB is running by navigating to the installation directory and running (in cmd or terminal), replacing the path with your chosen Mongo directory: 28 | 29 | ``` 30 | C:\Data\bin\mongod.exe 31 | ``` 32 | 33 | Now, start the development environment with the following command: 34 | 35 | ``` 36 | npm run dev 37 | ``` 38 | 39 | The application should open automatically. 40 | 41 | ## Troubleshooting 42 | Problem: The application won't start! 43 | 44 | Try: 45 | 1. Run `npm install` again 46 | 2. Update your version of `Node.js` to the latest 47 | 3. Clone the finished repo and start from there 48 | 49 | Problem: I'm getting weird error XYZ! 50 | 51 | Try: 52 | 1. Cancel `npm run dev` (with ctrl-C on windows) and run it again 53 | 2. If there error mentions any particular file, visit that file and make sure you didn't make any common errors (capitalization of property names, forgetting to destructure paramaters with curly brackets) 54 | 3. Still no luck? Clone the finished repo and prune away parts of it until you are at the point you left off. 55 | 56 | ## Challenge Task Solutions 57 | 58 | ### Connected Username Component 59 | 1. Create a [connected username component](https://github.com/danielstern/express-react-fullstack/blob/master/src/app/components/UsernameDisplay.jsx) which matches user data with an ID provided as a prop. 60 | 2. Update the server-side state assembly process to include the usernames (but not passwords or any sensitive data) of any users which will be relevant to the current session. 61 | 62 | ### Sign Up 63 | This version of the application is found at the [Add Sign Up Branch](https://github.com/danielstern/express-react-fullstack/tree/add-signup/src/app/components). 64 | 65 | 1. Add a link to the sign up page from the login page. 66 | 2. Create a Sign Up route, which is almost identical to the Login route. 67 | 3. Add a saga to communicate requests from the Login Route to the server. 68 | 4. Add a route to the server which creates new users in the database. 69 | 70 | ### Security 71 | Coming February 2019. 72 | -------------------------------------------------------------------------------- /dist/bundle.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=58)}([function(e,t,n){"use strict";e.exports=n(26)},function(e,t,n){e.exports=n(33)()},function(e,t,n){"use strict";e.exports=function(){}},function(e,t,n){"use strict";e.exports=function(e,t,n,r,o,i,a,u){if(!e){var l;if(void 0===t)l=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,o,i,a,u],s=0;(l=new Error(t.replace(/%s/g,function(){return c[s++]}))).name="Invariant Violation"}throw l.framesToPop=1,l}}},function(e,t,n){"use strict";var r=n(16),o=n(39),i=Object.prototype.toString;function a(e){return"[object Array]"===i.call(e)}function u(e){return null!==e&&"object"==typeof e}function l(e){return"[object Function]"===i.call(e)}function c(e,t){if(null!==e&&void 0!==e)if("object"!=typeof e&&(e=[e]),a(e))for(var n=0,r=e.length;n=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};r.forEach(["delete","get","head"],function(e){u.headers[e]={}}),r.forEach(["post","put","patch"],function(e){u.headers[e]=r.merge(i)}),e.exports=u}).call(this,n(41))},function(e,t,n){"use strict";(function(e,r){var o,i=n(24);o="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==e?e:r;var a=Object(i.a)(o);t.a=a}).call(this,n(10),n(35)(e))},function(e,t,n){"use strict"; 2 | /* 3 | object-assign 4 | (c) Sindre Sorhus 5 | @license MIT 6 | */var r=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map(function(e){return t[e]}).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach(function(e){r[e]=e}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,a,u=function(e){if(null===e||void 0===e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),l=1;l>>((3&t)<<3)&255;return o}}},function(e,t){for(var n=[],r=0;r<256;++r)n[r]=(r+256).toString(16).substr(1);e.exports=function(e,t){var r=t||0,o=n;return[o[e[r++]],o[e[r++]],o[e[r++]],o[e[r++]],"-",o[e[r++]],o[e[r++]],"-",o[e[r++]],o[e[r++]],"-",o[e[r++]],o[e[r++]],"-",o[e[r++]],o[e[r++]],o[e[r++]],o[e[r++]],o[e[r++]],o[e[r++]]].join("")}},function(e,t,n){"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r=t.length?n(new u(h,w,new a(void 0,e[w]))):s(e[w],t[w],n,r,h,w,d);for(;w=0?(s(e[o],t[o],n,r,h,o,d),k=l(k,a)):s(e[o],void 0,n,r,h,o,d)}),k.forEach(function(e){s(void 0,t[e],n,r,h,e,d)})}d.length=d.length-1}else e!==t&&("number"===y&&isNaN(e)&&isNaN(t)||n(new o(h,e,t)))}function f(e,t,n,r){return r=r||[],s(e,t,function(e){e&&r.push(e)},n),r.length?r:void 0}function p(e,t,n){if(e&&t&&n&&n.kind){for(var r=e,o=-1,i=n.path?n.path.length-1:0;++o0&&void 0!==arguments[0]?arguments[0]:{},t=Object.assign({},C,e),n=t.logger,r=t.stateTransformer,o=t.errorTransformer,i=t.predicate,a=t.logErrors,u=t.diffPredicate;if(void 0===n)return function(){return function(e){return function(t){return e(t)}}};if(e.getState&&e.dispatch)return console.error("[redux-logger] redux-logger not installed. Make sure to pass logger instance as middleware:\n// Logger with default options\nimport { logger } from 'redux-logger'\nconst store = createStore(\n reducer,\n applyMiddleware(logger)\n)\n// Or you can create your own logger with custom options http://bit.ly/redux-logger-options\nimport createLogger from 'redux-logger'\nconst logger = createLogger({\n // ...options\n});\nconst store = createStore(\n reducer,\n applyMiddleware(logger)\n)\n"),function(){return function(e){return function(t){return e(t)}}};var l=[];return function(e){var n=e.getState;return function(e){return function(c){if("function"==typeof i&&!i(n,c))return e(c);var s={};l.push(s),s.started=E.now(),s.startedTime=new Date,s.prevState=r(n()),s.action=c;var f=void 0;if(a)try{f=e(c)}catch(e){s.error=o(e)}else f=e(c);s.took=E.now()-s.started,s.nextState=r(n());var p=t.diff&&"function"==typeof u?u(n,c):t.diff;if(m(l,Object.assign({},t,{diff:p})),l.length=0,s.error)throw s.error;return f}}}}var v,g,b=function(e,t){return function(e,t){return new Array(t+1).join(e)}("0",t-e.toString().length)+e},w=function(e){return b(e.getHours(),2)+":"+b(e.getMinutes(),2)+":"+b(e.getSeconds(),2)+"."+b(e.getMilliseconds(),3)},E="undefined"!=typeof performance&&null!==performance&&"function"==typeof performance.now?performance:Date,x="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},k=function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:{},t=e.dispatch,n=e.getState;return"function"==typeof t||"function"==typeof n?y()({dispatch:t,getState:n}):void console.error("\n[redux-logger v3] BREAKING CHANGE\n[redux-logger v3] Since 3.0.0 redux-logger exports by default logger with default settings.\n[redux-logger v3] Change\n[redux-logger v3] import createLogger from 'redux-logger'\n[redux-logger v3] to\n[redux-logger v3] import { createLogger } from 'redux-logger'\n")};t.defaults=C,t.createLogger=y,t.logger=_,t.default=_,Object.defineProperty(t,"__esModule",{value:!0})}(t)}).call(this,n(10))},function(e,t,n){"use strict"; 7 | /** @license React v16.4.2 8 | * react.production.min.js 9 | * 10 | * Copyright (c) 2013-present, Facebook, Inc. 11 | * 12 | * This source code is licensed under the MIT license found in the 13 | * LICENSE file in the root directory of this source tree. 14 | */var r=n(13),o=n(27),i=n(28),a=n(29),u="function"==typeof Symbol&&Symbol.for,l=u?Symbol.for("react.element"):60103,c=u?Symbol.for("react.portal"):60106,s=u?Symbol.for("react.fragment"):60107,f=u?Symbol.for("react.strict_mode"):60108,p=u?Symbol.for("react.profiler"):60114,d=u?Symbol.for("react.provider"):60109,h=u?Symbol.for("react.context"):60110,m=u?Symbol.for("react.async_mode"):60111,y=u?Symbol.for("react.forward_ref"):60112;u&&Symbol.for("react.timeout");var v="function"==typeof Symbol&&Symbol.iterator;function g(e){for(var t=arguments.length-1,n="https://reactjs.org/docs/error-decoder.html?invariant="+e,r=0;rN.length&&N.push(e)}function A(e,t,n,r){var o=typeof e;"undefined"!==o&&"boolean"!==o||(e=null);var i=!1;if(null===e)i=!0;else switch(o){case"string":case"number":i=!0;break;case"object":switch(e.$$typeof){case l:case c:i=!0}}if(i)return n(r,e,""===t?"."+D(e,0):t),1;if(i=0,t=""===t?".":t+":",Array.isArray(e))for(var a=0;athis.eventPool.length&&this.eventPool.push(e)}function he(e){e.eventPool=[],e.getPooled=pe,e.release=de}o(fe.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=ce)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=ce)},persist:function(){this.isPersistent=ce},isPersistent:se,destructor:function(){var e,t=this.constructor.Interface;for(e in t)this[e]=null;this.nativeEvent=this._targetInst=this.dispatchConfig=null,this.isPropagationStopped=this.isDefaultPrevented=se,this._dispatchInstances=this._dispatchListeners=null}}),fe.Interface={type:null,target:null,currentTarget:function(){return null},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null},fe.extend=function(e){function t(){}function n(){return r.apply(this,arguments)}var r=this;t.prototype=r.prototype;var i=new t;return o(i,n.prototype),n.prototype=i,n.prototype.constructor=n,n.Interface=o({},r.Interface,e),n.extend=r.extend,he(n),n},he(fe);var me=fe.extend({data:null}),ye=fe.extend({data:null}),ve=[9,13,27,32],ge=Y&&"CompositionEvent"in window,be=null;Y&&"documentMode"in document&&(be=document.documentMode);var we=Y&&"TextEvent"in window&&!be,Ee=Y&&(!ge||be&&8=be),xe=String.fromCharCode(32),ke={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["compositionend","keypress","textInput","paste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"blur compositionend keydown keypress keyup mousedown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:"blur compositionstart keydown keypress keyup mousedown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"blur compositionupdate keydown keypress keyup mousedown".split(" ")}},Te=!1;function Se(e,t){switch(e){case"keyup":return-1!==ve.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"blur":return!0;default:return!1}}function Ce(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var _e=!1;var Oe={eventTypes:ke,extractEvents:function(e,t,n,r){var o=void 0,i=void 0;if(ge)e:{switch(e){case"compositionstart":o=ke.compositionStart;break e;case"compositionend":o=ke.compositionEnd;break e;case"compositionupdate":o=ke.compositionUpdate;break e}o=void 0}else _e?Se(e,n)&&(o=ke.compositionEnd):"keydown"===e&&229===n.keyCode&&(o=ke.compositionStart);return o?(Ee&&"ko"!==n.locale&&(_e||o!==ke.compositionStart?o===ke.compositionEnd&&_e&&(i=le()):(ae="value"in(ie=r)?ie.value:ie.textContent,_e=!0)),o=me.getPooled(o,t,n,r),i?o.data=i:null!==(i=Ce(n))&&(o.data=i),K(o),i=o):i=null,(e=we?function(e,t){switch(e){case"compositionend":return Ce(t);case"keypress":return 32!==t.which?null:(Te=!0,xe);case"textInput":return(e=t.data)===xe&&Te?null:e;default:return null}}(e,n):function(e,t){if(_e)return"compositionend"===e||!ge&&Se(e,t)?(e=le(),ue=ae=ie=null,_e=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1