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