├── .gitignore ├── client ├── static │ ├── app.css │ └── index.html ├── store.js ├── index.js ├── main-component.js ├── new-paula.js ├── reducer.js └── actions.js ├── .babelrc ├── webpack.config.js ├── README ├── .eslintrc.js ├── server.js ├── LICENSE └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | *.log 4 | -------------------------------------------------------------------------------- /client/static/app.css: -------------------------------------------------------------------------------- 1 | /* Put CSS rules here */ 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "plugins": ["transform-object-rest-spread"] 4 | } 5 | -------------------------------------------------------------------------------- /client/store.js: -------------------------------------------------------------------------------- 1 | import {createStore, applyMiddleware} from 'redux'; 2 | import thunk from 'redux-thunk'; //optional 3 | import reducer from './reducer'; 4 | 5 | export default createStore(reducer, applyMiddleware(thunk)); 6 | -------------------------------------------------------------------------------- /client/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Full Stack App 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: "./client/index.js", 3 | output: { 4 | path: "./build", 5 | filename: "app.js" 6 | }, 7 | devtool: process.env.NODE_ENV === 'production' ? 'source-map' : 'inline-source-map', 8 | module: { 9 | loaders: [{ 10 | test: /\.js$/, 11 | exclude: /(node_modules)/, 12 | loader: 'babel', 13 | }] 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import MainComponent from './main-component'; 4 | import store from './store'; 5 | import {Provider} from 'react-redux'; 6 | 7 | document.addEventListener('DOMContentLoaded', () => 8 | ReactDOM.render( 9 | 10 | , document.getElementById('app')) 11 | ); 12 | -------------------------------------------------------------------------------- /client/main-component.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from 'react-redux'; 3 | import NewPaula from './new-paula'; 4 | 5 | class MainComponent extends React.Component { 6 | render() { 7 | return
8 |

React/Redux demo

9 |

This is where your content goes. Paula is {this.props.paula}.

10 | 11 |
; 12 | } 13 | } 14 | 15 | export default connect( 16 | //Select your state -> props mappings here 17 | ({paula}) => ({paula}) 18 | )(MainComponent); 19 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Basic full-stack JavaScript app template 2 | 3 | To build: npm run build 4 | 5 | To start the server: npm run start 6 | 7 | To start in dev mode, with everything watched: npm run watch 8 | 9 | TODO: Use makefiles to cut down on some of the work (eg 'npm run start' 10 | currently runs 'npm run build' unconditionally). 11 | 12 | The intention of this template is that you, the author of your app, 13 | should claim it as your own. Change all the URLs and authorship marks in 14 | package.json to be your own :) 15 | -------------------------------------------------------------------------------- /client/new-paula.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from 'react-redux'; 3 | import * as actions from './actions'; 4 | 5 | export default connect()(class NewPaula extends React.Component { 6 | update(e) { 7 | e.preventDefault(); 8 | this.props.dispatch(actions.replace_paula(this.refs.paula.value)); 9 | this.props.dispatch(actions.fetch_hello()); 10 | } 11 | 12 | render() { 13 | return
14 | 15 | 16 |
; 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "ecmaFeatures": { 11 | "experimentalObjectRestSpread": true, 12 | "jsx": true 13 | }, 14 | "sourceType": "module" 15 | }, 16 | "plugins": [ 17 | "react" 18 | ], 19 | "rules": { 20 | "linebreak-style": [ 21 | "error", 22 | "unix" 23 | ], 24 | "semi": [ 25 | "error", 26 | "always" 27 | ] 28 | } 29 | }; -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | //NOTE: ES6 'import' statements cannot be used in the server - just use ES5 2 | //syntax instead ('require'). Other ES6 features should all be available. 3 | const express = require('express'); 4 | 5 | const HOST = process.env.HOST; 6 | const PORT = process.env.PORT || 8080; 7 | 8 | const app = express(); 9 | app.use(express.static("build")); 10 | 11 | app.get("/hello", (req, res) => { 12 | res.json({message: "Hello, world!"}); 13 | }); 14 | 15 | function run_server() { 16 | app.listen(PORT, HOST, err => { 17 | if (err) return console.error(err); 18 | console.log(`Listening on port ${PORT}`); 19 | }); 20 | } 21 | 22 | if (require.main === module) run_server(); 23 | -------------------------------------------------------------------------------- /client/reducer.js: -------------------------------------------------------------------------------- 1 | //import * as actions from './actions'; 2 | 3 | const initial_state = { 4 | spam: 'ham', 5 | paula: 'brillant [sic]', 6 | }; 7 | 8 | export default function reducer(state=initial_state, action={}) { 9 | switch (action.type) { 10 | case 'SPAMIFY_SPAM': 11 | return {...state, spam: 'spam'}; 12 | case 'HAMIFY_SPAM': 13 | return {...state, spam: 'ham'}; 14 | case 'REPLACE_PAULA': 15 | return {...state, paula: action.paula}; 16 | case 'FETCH_HELLO_SUCCESS': 17 | console.log("The server says:", action.message); 18 | break; 19 | case 'REPORT_FAILURE': 20 | console.error("Failure in " + action.what + ":"); 21 | console.error(action.error); 22 | break; 23 | default: break; 24 | } 25 | return state; 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Chris Angelico 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client/actions.js: -------------------------------------------------------------------------------- 1 | export const spamify_spam = () => ({ 2 | type: 'SPAMIFY_SPAM', 3 | }) 4 | 5 | export const hamify_spam = () => ({ 6 | type: 'HAMIFY_SPAM', 7 | }) 8 | 9 | export const replace_paula = (new_paula) => ({ 10 | type: 'REPLACE_PAULA', 11 | paula: new_paula, 12 | }) 13 | 14 | //"Second-half" actions don't have to be exported (they're only called from 15 | //here). It doesn't hurt to export them, though, if you prefer consistency. 16 | const fetch_hello_success = message => ({ 17 | type: 'FETCH_HELLO_SUCCESS', 18 | message 19 | }) 20 | 21 | //A single generic failure message can be used for all network failures, 22 | //unless you specifically need to do something when a particular one fails. 23 | const report_failure = (what, error) => ({ 24 | type: 'REPORT_FAILURE', 25 | what, error 26 | }) 27 | 28 | export const fetch_hello = () => dispatch => { 29 | return fetch("/hello").then(response => { 30 | if (!response.ok) throw(new Error(response.statusText)); 31 | return response.json(); 32 | }).then(data => 33 | dispatch(fetch_hello_success(data.message)) 34 | ).catch(error => 35 | dispatch(report_failure("fetch_hello", error)) 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "full-stack-app", 3 | "version": "0.0.0", 4 | "description": "Template full-stack JavaScript app", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "npm run build && node server.js", 8 | "build": "mkdir -p build && cp client/static/* build/ && webpack", 9 | "watch": "npm run build && run-p watch:*", 10 | "watch:copy": "chokidar \"client/static/*\" -c \"cp {path} build/\"", 11 | "watch:js": "webpack --watch", 12 | "watch:server": "nodemon -w server.js server.js -x \"npm run\"", 13 | "server.js": "npm start", 14 | "test": "echo \"Error: no test specified\" && exit 1" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/Rosuav/full-stack-app.git" 19 | }, 20 | "author": "Chris Angelico (cangelico@thinkful.com)", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/Rosuav/full-stack-app/issues" 24 | }, 25 | "homepage": "https://github.com/Rosuav/full-stack-app#readme", 26 | "dependencies": { 27 | "babel-cli": "^6.18.0", 28 | "babel-core": "^6.18.2", 29 | "babel-loader": "^6.2.7", 30 | "babel-plugin-transform-object-rest-spread": "^6.20.2", 31 | "babel-preset-es2015": "^6.18.0", 32 | "babel-preset-react": "^6.16.0", 33 | "express": "^4.14.0", 34 | "isomorphic-fetch": "^2.2.1", 35 | "react": "^15.3.2", 36 | "react-dom": "^15.3.2", 37 | "react-redux": "^4.4.6", 38 | "redux": "^3.6.0", 39 | "redux-thunk": "^2.1.0", 40 | "webpack": "^1.13.3" 41 | }, 42 | "devDependencies": { 43 | "chokidar-cli": "^1.2.0", 44 | "eslint": "^3.17.1", 45 | "eslint-plugin-react": "^6.10.0", 46 | "nodemon": "^1.11.0", 47 | "npm-run-all": "^3.1.1" 48 | } 49 | } 50 | --------------------------------------------------------------------------------