├── .babelrc ├── .build └── index.html ├── .gitignore ├── .jshintignore ├── .jshintrc ├── LICENSE ├── README.md ├── client ├── actions │ └── index.js ├── components │ └── DumbComponent.js ├── constants │ └── index.js ├── containers │ └── SmartComponent.js ├── index.html ├── index.js ├── public │ ├── javascripts │ │ └── boilerplate.js │ └── stylesheets │ │ └── boilerplate.css ├── reducers │ └── index.js └── store │ └── configureStore.js ├── package-lock.json ├── package.json ├── server ├── boot │ └── authentication.js ├── component-config.json ├── config.json ├── datasources.json ├── environment.js ├── middleware.json ├── middleware.production.json ├── model-config.json └── server.js ├── webpack.config.development.js └── webpack.config.production.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015" 5 | ] 6 | } -------------------------------------------------------------------------------- /.build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Development 5 | 6 | 7 |
8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | *.dat 3 | *.iml 4 | *.log 5 | *.out 6 | *.pid 7 | *.seed 8 | *.sublime-* 9 | *.swo 10 | *.swp 11 | *.tgz 12 | *.xml 13 | *.save 14 | .DS_Store 15 | .idea 16 | .project 17 | .strong-pm 18 | coverage 19 | node_modules 20 | npm-debug.log 21 | .build/dist 22 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | /client/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "eqeqeq": true, 7 | "eqnull": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": "nofunc", 11 | "newcap": true, 12 | "nonew": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": false, 18 | "trailing": true, 19 | "sub": true, 20 | "maxlen": 80 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Tony Ngan 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # loopback-redux-react-boilerplate 2 | 3 | A boilerplate for a Redux-React application using LoopBack 4 | 5 | ### Get Started 6 | - **Clone this repository or use npm** 7 | ```bash 8 | $ git clone https://github.com/tngan/loopback-redux-react-boilerplate.git 9 | ``` 10 | 11 | - **Install dependencies specified in package.json** 12 | ```bash 13 | # npm 14 | $ npm install 15 | # yarn 16 | $ yarn install 17 | ``` 18 | - **Start the server (default port is set to 3000)** 19 | ```bash 20 | # npm 21 | $ npm start 22 | # yarn 23 | $ yarn start 24 | ``` 25 | 26 | ### Scripts 27 | - **npm run deploy** or **yarn deploy**: Bundles the application into `.build/dist`. 28 | 29 | - **npm run start_prod** or **yarn start_prod**: Starts production server, make sure you have already deployed the application. 30 | 31 | - **npm run clean** or **yarn clean**: Removes the bundled files. 32 | 33 | ### Built-in example 34 | A simple 'Hello World' Redux-React application is included in this boilerplate. You can find those files under `/client`. 35 | 36 | Hot reloading is only applied in development mode. In production mode, the code base is pre-compiled and placed under `.build/dist`. 37 | 38 | ### License 39 | 40 | [MIT](LICENSE) 41 | 42 | ### Copyright 43 | 44 | Copyright (C) 2016 Tony Ngan, released under the MIT License. 45 | -------------------------------------------------------------------------------- /client/actions/index.js: -------------------------------------------------------------------------------- 1 | // Define an action type, it's used for reducer switching 2 | export const GET_STARTED = 'GET_STARTED'; 3 | 4 | // Define the corresponding action creator, must return an object 5 | export function getStarted() { 6 | return { 7 | type: GET_STARTED 8 | }; 9 | } -------------------------------------------------------------------------------- /client/components/DumbComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import '../public/stylesheets/boilerplate.css'; 3 | 4 | class DumbComponent extends Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | render() { 9 | return ( 10 |

11 | { this.props.welcomeText || '' } 12 |

13 | ); 14 | } 15 | } 16 | 17 | export default DumbComponent; -------------------------------------------------------------------------------- /client/constants/index.js: -------------------------------------------------------------------------------- 1 | /* Put all your constants here, all in BLOCK_LETTER 2 | * 3 | * Export the constant directly as follow: 4 | * 5 | * export const MY_CONSTANT = 'val'; 6 | * export const MY_CONSTANT_1 = 'val1'; 7 | * 8 | * In any files, import those constants when you need 9 | * 10 | * import { MY_CONSTANT, MY_CONSTANT_1 } from './constants'; 11 | * 12 | */ 13 | export const WELCOME_TEXT = 'Thanks for using loopback-redux-react-boilerplate'; -------------------------------------------------------------------------------- /client/containers/SmartComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { getStarted } from '../actions'; 4 | import DumbComponent from '../components/DumbComponent'; 5 | 6 | class SmartComponent extends Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | componentWillMount() { 11 | this.props.dispatch(getStarted()); 12 | } 13 | render() { 14 | return ( 15 |
16 | 17 |
18 | ); 19 | } 20 | } 21 | 22 | export default connect(state => state)(SmartComponent); -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Development 5 | 6 | 7 |
8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { Provider } from 'react-redux'; 6 | import SmartComponent from './containers/SmartComponent'; 7 | import configureStore from './store/configureStore'; 8 | 9 | const store = configureStore(); 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); -------------------------------------------------------------------------------- /client/public/javascripts/boilerplate.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tngan/loopback-redux-react-boilerplate/62453e1fe897897293d3ff3409905ce4be520c8a/client/public/javascripts/boilerplate.js -------------------------------------------------------------------------------- /client/public/stylesheets/boilerplate.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This stylesheet can be imported to any dumb components under /components 3 | * Remarks: It's suggested those Smart components under /containers not to apply their own style 4 | * 5 | * import '../public/stylesheets/boilerplate.css' 6 | * 7 | * It's required css-loader in webpack.config.js, and it's already included in package.json. 8 | * You can also use your own loader by modifying the setting in webpack.config.js 9 | * 10 | */ 11 | p.text { 12 | color: #DCAA45; 13 | } -------------------------------------------------------------------------------- /client/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { GET_STARTED } from '../actions'; 2 | import { WELCOME_TEXT } from '../constants'; 3 | 4 | function rootReducer (state = {}, action) { 5 | switch(action.type) { 6 | case GET_STARTED: 7 | return { welcomeText: WELCOME_TEXT }; 8 | default: 9 | return state; 10 | } 11 | } 12 | 13 | export default rootReducer; 14 | 15 | /* 16 | * Redux suggests use multiple reducers instead of creating multiple stores, 17 | * if more than one reducer is needed, use combineReducer from 'redux' module. 18 | * 19 | * import { composeReducers } from 'redux' 20 | * 21 | * export default combineReducers({ 22 | * rootReducer 23 | * subReducer 24 | * }); 25 | * 26 | */ -------------------------------------------------------------------------------- /client/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import homeApp from '../reducers'; 3 | 4 | export default function configureStore(initialState) { 5 | const store = createStore(homeApp); 6 | if (module.hot) { 7 | // Enable Webpack hot module replacement for reducers 8 | module.hot.accept('../reducers', () => { 9 | const nextRootReducer = require('../reducers'); 10 | store.replaceReducer(nextRootReducer); 11 | }); 12 | } 13 | return store; 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loopback-redux-react-boilerplate", 3 | "version": "1.0.2", 4 | "main": "server/server.js", 5 | "scripts": { 6 | "start": "node .", 7 | "start_prod": "NODE_ENV=production node .", 8 | "deploy": "NODE_ENV=production webpack -p --config webpack.config.production.js --progress", 9 | "clean": "rm -rf .build/dist" 10 | }, 11 | "keywords": [ 12 | "react", 13 | "redux", 14 | "boilerplate", 15 | "loopback", 16 | "webpack", 17 | "react-transform" 18 | ], 19 | "dependencies": { 20 | "ajv": "^5.2.0", 21 | "babel-cli": "^6.24.1", 22 | "babel-polyfill": "^6.23.0", 23 | "babel-preset-es2015": "^6.24.1", 24 | "babel-preset-react": "^6.24.1", 25 | "compression": "^1.6.2", 26 | "cors": "^2.8.3", 27 | "history": "^4.6.3", 28 | "humps": "^2.0.1", 29 | "isomorphic-fetch": "^2.2.1", 30 | "json-loader": "^0.5.4", 31 | "json5": "^0.5.1", 32 | "loopback": "^3.8.0", 33 | "loopback-boot": "^2.25.0", 34 | "loopback-component-explorer": "^4.2.0", 35 | "react": "^15.6.1", 36 | "react-dom": "^15.6.1", 37 | "react-redux": "^5.0.5", 38 | "redux": "^3.7.1", 39 | "serve-favicon": "^2.4.3", 40 | "strong-error-handler": "^2.1.0" 41 | }, 42 | "devDependencies": { 43 | "babel-core": "^6.25.0", 44 | "babel-loader": "^7.1.1", 45 | "concurrently": "^3.5.0", 46 | "css-loader": "^0.28.4", 47 | "redux-devtools": "^3.4.0", 48 | "style-loader": "^0.18.2", 49 | "webpack": "^3.1.0", 50 | "webpack-dev-middleware": "^1.11.0", 51 | "webpack-hot-middleware": "^2.18.2" 52 | }, 53 | "repository": { 54 | "type": "git", 55 | "url": "https://github.com/tngan/loopback-redux-react-boilerplate" 56 | }, 57 | "description": "A boilerplate for a Redux-React application using LoopBack" 58 | } 59 | -------------------------------------------------------------------------------- /server/boot/authentication.js: -------------------------------------------------------------------------------- 1 | module.exports = function enableAuthentication(server) { 2 | // enable authentication 3 | server.enableAuth(); 4 | }; 5 | -------------------------------------------------------------------------------- /server/component-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "loopback-component-explorer": { 3 | "mountPath": "/explorer" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "restApiRoot": "/api", 3 | "host": "0.0.0.0", 4 | "port": 3000, 5 | "remoting": { 6 | "context": false, 7 | "rest": { 8 | "normalizeHttpPath": false, 9 | "xml": false, 10 | "handleErrors": false 11 | }, 12 | "json": { 13 | "strict": false, 14 | "limit": "100kb" 15 | }, 16 | "urlencoded": { 17 | "extended": true, 18 | "limit": "100kb" 19 | }, 20 | "cors": false 21 | }, 22 | "legacyExplorer": false 23 | } 24 | -------------------------------------------------------------------------------- /server/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": { 3 | "name": "db", 4 | "connector": "memory" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /server/environment.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | DEVELOPMENT: 'development', 3 | PRODUCTION: 'production' 4 | }; -------------------------------------------------------------------------------- /server/middleware.json: -------------------------------------------------------------------------------- 1 | { 2 | "initial:before": { 3 | "loopback#favicon": {} 4 | }, 5 | "initial": { 6 | "compression": {}, 7 | "cors": { 8 | "params": { 9 | "origin": true, 10 | "credentials": true, 11 | "maxAge": 86400 12 | } 13 | } 14 | }, 15 | "session": { 16 | }, 17 | "auth": { 18 | }, 19 | "parse": { 20 | }, 21 | "routes": { 22 | "loopback#rest": { 23 | "paths": ["${restApiRoot}"] 24 | } 25 | }, 26 | "files": { 27 | "loopback#static": { 28 | "params": "$!../client" 29 | } 30 | }, 31 | "final": { 32 | "loopback#urlNotFound": {} 33 | }, 34 | "final:after": { 35 | "strong-error-handler": { 36 | "params": { 37 | "debug": false, 38 | "log": true 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /server/middleware.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "loopback#static": { 4 | "params": "$!../.build" 5 | } 6 | }, 7 | "final:after": { 8 | "strong-error-handler": { 9 | "params": { 10 | "debug": false, 11 | "log": true 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server/model-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "sources": [ 4 | "loopback/common/models", 5 | "loopback/server/models", 6 | "../common/models", 7 | "./models" 8 | ], 9 | "mixins": [ 10 | "loopback/common/mixins", 11 | "loopback/server/mixins", 12 | "../common/mixins", 13 | "./mixins" 14 | ] 15 | }, 16 | "User": { 17 | "dataSource": "db" 18 | }, 19 | "AccessToken": { 20 | "dataSource": "db", 21 | "public": false 22 | }, 23 | "ACL": { 24 | "dataSource": "db", 25 | "public": false 26 | }, 27 | "RoleMapping": { 28 | "dataSource": "db", 29 | "public": false 30 | }, 31 | "Role": { 32 | "dataSource": "db", 33 | "public": false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | var loopback = require('loopback'); 2 | var boot = require('loopback-boot'); 3 | var app = module.exports = loopback(); 4 | var webpack = require('webpack'); 5 | var env = require('./environment'); 6 | var mode = process.env.NODE_ENV || env.DEVELOPMENT; 7 | var webpackDevMiddleware = require('webpack-dev-middleware'); 8 | var webpackHotMiddleware = require('webpack-hot-middleware'); 9 | var config = require(`../webpack.config.${mode}`); 10 | var compiler = webpack(config); 11 | 12 | if(mode === env.DEVELOPMENT) { 13 | // only need in development 14 | app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })); 15 | } 16 | app.use(webpackHotMiddleware(compiler)); 17 | 18 | boot(app, __dirname); 19 | 20 | app.start = function() { 21 | // start the web server 22 | return app.listen(function() { 23 | app.emit('started'); 24 | var baseUrl = app.get('url').replace(/\/$/, ''); 25 | console.log('Web server listening at: %s', baseUrl); 26 | if (app.get('loopback-component-explorer')) { 27 | var explorerPath = app.get('loopback-component-explorer').mountPath; 28 | console.log('Browse your REST API at %s%s', baseUrl, explorerPath); 29 | } 30 | }); 31 | }; 32 | 33 | if (require.main === module) { 34 | app.start(); 35 | } 36 | -------------------------------------------------------------------------------- /webpack.config.development.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | devtool: 'eval', 6 | entry: [ 7 | 'webpack-hot-middleware/client', 8 | './client/index' 9 | ], 10 | output: { 11 | path: __dirname, 12 | filename: 'bundle.js', 13 | publicPath: '/dist/' 14 | }, 15 | plugins: [ 16 | new webpack.HotModuleReplacementPlugin(), 17 | new webpack.NoEmitOnErrorsPlugin() 18 | ], 19 | module: { 20 | loaders: [{ 21 | test: /\.js$/, 22 | loaders: ['babel-loader'], 23 | exclude: /node_modules/, 24 | include: path.join(__dirname, 'client') 25 | }, 26 | { 27 | test: /\.css$/, 28 | use: [ 29 | { loader: "style-loader" }, 30 | { loader: "css-loader" } 31 | ] 32 | }] 33 | } 34 | }; -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: [ 6 | './client/index' 7 | ], 8 | output: { 9 | path: path.join(__dirname, '.build/dist'), 10 | filename: 'bundle.js', 11 | publicPath: '/.build' 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.js$/, 16 | loaders: ['babel-loader'], 17 | exclude: /node_modules/, 18 | include: path.join(__dirname, 'client') 19 | }, 20 | { 21 | test: /\.css$/, 22 | use: [ 23 | { loader: "style-loader" }, 24 | { loader: "css-loader" } 25 | ] 26 | }] 27 | }, 28 | plugins: [ 29 | new webpack.DefinePlugin({ 30 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 31 | }), 32 | ] 33 | }; --------------------------------------------------------------------------------