├── .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 ;
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 |
--------------------------------------------------------------------------------