├── .circleci └── config.yml ├── .dockerignore ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .production ├── .sequelizerc └── Dockerfile ├── .sequelizerc ├── .stylelintrc ├── Dockerfile ├── LICENSE ├── README.MD ├── api ├── .babelrc ├── .eslintrc ├── components │ └── Html.jsx ├── controllers │ └── exampleController.js ├── db.config.js ├── index.js ├── migrations │ └── 20190417092622-initial.js ├── models │ ├── Book.js │ ├── User.js │ ├── index.js │ └── sequelize.js ├── tests │ ├── .eslintrc │ └── models.test.js └── utils │ └── Router.js ├── app ├── .babelrc ├── .eslintrc ├── App.css ├── App.jsx ├── Routes.jsx ├── actions │ ├── example.js │ └── index.js ├── components │ └── ErrorBoundary.jsx ├── createStore.js ├── i18n │ └── index.js ├── images │ └── StarterKitTheTribe.png ├── index.jsx ├── reducers │ ├── example.js │ └── index.js ├── routes │ ├── Home.jsx │ └── Home.scss └── tests │ ├── .eslintrc │ └── App.test.jsx ├── babel.config.js ├── cucumber.js ├── docker-compose.yml ├── docker ├── docker-entrypoint.sh ├── docker-is-script.js └── selenium │ └── Dockerfile ├── features ├── .eslintrc ├── HelloWorld.feature ├── pages │ ├── Base.js │ ├── Factory.js │ └── Home.js ├── step_definitions │ └── HelloWorld-steps.js └── support │ ├── hooks.js │ └── world.js ├── jest.config.js ├── package.json ├── public ├── favicon.png └── locales │ ├── en │ ├── buttons.json │ └── translation.json │ └── fr │ ├── buttons.json │ └── translation.json ├── tools ├── .eslintrc ├── build.js ├── bundle.js ├── clean.js ├── copy.js ├── lib │ ├── WebpackPackagePlugin.js │ ├── fileTransformer.js │ └── fs.js ├── run.js ├── start.js └── webpack.config.js └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | docker: circleci/docker@1.0.0 5 | saucelabs: saucelabs/sauce-connect@1.0.1 6 | 7 | jobs: 8 | yarn-install: 9 | docker: 10 | - image: circleci/node:12.16.0 11 | steps: 12 | - checkout 13 | - restore_cache: 14 | keys: 15 | - yarn-install-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }} 16 | - yarn-install-{{ checksum "package.json" }} 17 | - yarn-install 18 | paths: 19 | - node_modules 20 | - run: 21 | command: yarn install --frozen-lockfile 22 | - save_cache: 23 | key: yarn-install-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }} 24 | paths: 25 | - node_modules 26 | - persist_to_workspace: 27 | root: ~/project 28 | paths: 29 | - node_modules 30 | 31 | lint: 32 | docker: 33 | - image: circleci/node:12.16.0 34 | steps: 35 | - checkout 36 | - attach_workspace: 37 | at: ~/project 38 | - run: yarn lint:js 39 | - run: yarn lint:css 40 | 41 | build: 42 | docker: 43 | - image: circleci/node:12.16.0 44 | steps: 45 | - checkout 46 | - attach_workspace: 47 | at: ~/project 48 | - run: 49 | name: Build app 50 | command: yarn build 51 | - persist_to_workspace: 52 | root: ~/project 53 | paths: 54 | - build 55 | 56 | unit-tests: 57 | docker: 58 | - image: circleci/node:12.16.0 59 | steps: 60 | - checkout 61 | - attach_workspace: 62 | at: ~/project 63 | - run: 64 | name: Run tests 65 | command: yarn test 66 | 67 | functional-tests: 68 | docker: 69 | - image: circleci/node:12.16.0 70 | environment: 71 | DATABASE_HOST: localhost 72 | DATABASE_USER: thetribe 73 | DATABASE_NAME: thetribe 74 | DATABASE_PASSWORD: 424242 75 | SAUCELABS_HOST: localhost 76 | - image: circleci/postgres:10.7 77 | environment: 78 | POSTGRES_USER: thetribe 79 | POSTGRES_PASSWORD: 424242 80 | POSTGRES_DB: thetribe 81 | steps: 82 | - checkout 83 | - attach_workspace: 84 | at: ~/project 85 | - run: 86 | name: Wait for database 87 | command: dockerize -wait tcp://localhost:5432 -timeout 1m 88 | - run: 89 | name: Run migrations 90 | command: yarn sequelize db:migrate 91 | - run: 92 | name: Start application 93 | command: yarn start 94 | background: true 95 | - run: 96 | name: Setup host 97 | command: echo '127.0.0.1 app.local' | sudo tee -a /etc/hosts 98 | - run: 99 | name: Wait for app 100 | command: 'dockerize -wait http://app.local:3000 -wait-http-header "Accept: */*" -timeout 1m' 101 | - saucelabs/install 102 | - saucelabs/open_tunnel: 103 | tunnel_identifier: $CIRCLE_PROJECT_REPONAME-$CIRCLE_BUILD_NUM 104 | - run: 105 | name: Run tests 106 | command: | 107 | export SAUCELABS_TUNNEL_IDENTIFIER=$CIRCLE_PROJECT_REPONAME-$CIRCLE_BUILD_NUM 108 | 109 | yarn test:func:sauce:chrome 110 | yarn test:func:sauce:firefox 111 | #yarn test:func:sauce:ie 112 | #yarn test:func:sauce:safari 113 | - saucelabs/close_tunnel 114 | 115 | # Example of Sentry release & sourcemap upload job 116 | # TODO while bootstrapping, setup your projet name, uncomment the job and uncomment 117 | # it's call in workflows 118 | # Note: You may need multiple sentry jobs if you have an app and an api for example 119 | # 120 | # sentry-release: 121 | # docker: 122 | # - image: getsentry/sentry-cli:1.40.0 123 | # entrypoint: '' 124 | # environment: 125 | # SENTRY_PROJECT: DEFINE-YOUR-PROJECT-NAME-HERE 126 | # steps: 127 | # - attach_workspace: 128 | # at: ~/project 129 | # - run: sentry-cli releases new --project ${SENTRY_PROJECT} ${SENTRY_PROJECT}@${CIRCLE_SHA1} 130 | # - run: sentry-cli releases files ${SENTRY_PROJECT}@${CIRCLE_SHA1} upload-sourcemaps build --ignore '*.css.map' 131 | # - run: sentry-cli releases finalize ${SENTRY_PROJECT}@${CIRCLE_SHA1} 132 | 133 | workflows: 134 | version: 2 135 | 136 | build: 137 | jobs: 138 | - docker/hadolint: 139 | dockerfiles: .production/Dockerfile,docker/selenium/Dockerfile,Dockerfile 140 | - yarn-install 141 | - lint: 142 | requires: 143 | - yarn-install 144 | - build: 145 | requires: 146 | - yarn-install 147 | - unit-tests: 148 | requires: 149 | - yarn-install 150 | - functional-tests: 151 | context: saucelabs 152 | requires: 153 | - yarn-install 154 | # TODO while bootstrapping, uncomment this block to activate workflow 155 | # - sentry-release: 156 | # context: sentry 157 | # requires: 158 | # - build 159 | # filters: 160 | # branches: 161 | # only: 162 | # - develop 163 | # - master 164 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /build 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 4 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | # editorconfig-tools is unable to ignore longs strings or urls 20 | max_line_length = null 21 | 22 | 23 | [{package.json,yarn.lock}] 24 | indent_size = 2 25 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "@thetribe/eslint-config-react" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Compiled output 5 | /build 6 | 7 | # Test coverage 8 | coverage 9 | 10 | # Logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Docker 16 | /docker-compose.override.yml 17 | /watchOptions.config.js 18 | 19 | # Env 20 | /.env 21 | -------------------------------------------------------------------------------- /.production/.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | config: 'db.config.js', 5 | 'migrations-path': 'migrations', 6 | 'models-path': 'models', 7 | 'seeders-path': 'seeders', 8 | }; 9 | -------------------------------------------------------------------------------- /.production/Dockerfile: -------------------------------------------------------------------------------- 1 | ################ Build the application ############################ 2 | 3 | FROM node:12.16.0-slim as build 4 | 5 | WORKDIR /usr/src/app 6 | 7 | ENV PATH="/usr/src/app/node_modules/.bin:${PATH}" 8 | 9 | COPY package.json yarn.lock ./ 10 | RUN yarn install 11 | 12 | COPY babel.config.js ./ 13 | COPY tools ./tools 14 | COPY api ./api 15 | COPY app ./app 16 | 17 | ENV NODE_ENV=production 18 | 19 | RUN yarn build 20 | RUN babel api/migrations -d build/migrations --copy-files 21 | RUN babel api/db.config.js -o build/db.config.js 22 | COPY .production/.sequelizerc ./build 23 | 24 | RUN cp -f build/package.json . 25 | 26 | RUN yarn install 27 | 28 | ################ Build the finale image ################### 29 | 30 | FROM node:12.16.0-slim 31 | 32 | WORKDIR /usr/src/app 33 | 34 | COPY --from=build /usr/src/app/node_modules ./node_modules 35 | COPY --from=build /usr/src/app/build ./ 36 | 37 | ENV NODE_ENV=production 38 | 39 | CMD [ "yarn", "start" ] 40 | -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | config: path.join('api', 'db.config.js'), 5 | 'migrations-path': path.join('api', 'migrations'), 6 | 'models-path': path.join('api', 'models'), 7 | 'seeders-path': path.join('api', 'seeders'), 8 | }; 9 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "indentation": 4 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.16.0-slim 2 | 3 | COPY docker/docker-entrypoint.sh docker/docker-is-script.js /usr/local/bin/ 4 | ENTRYPOINT ["docker-entrypoint.sh"] 5 | 6 | RUN userdel node 7 | 8 | ARG UID=1000 9 | RUN useradd --uid $UID --create-home app 10 | USER app 11 | 12 | WORKDIR /usr/src/project 13 | 14 | ENV PATH="/usr/src/project/node_modules/.bin:${PATH}" 15 | 16 | CMD ["start"] 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 theTribe 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 | ![StarterKitBanner](app/images/StarterKitTheTribe.png) 2 | 3 | ## Overview 4 | 5 | This repository contains the source code for Node-React-Starter-Kit made by theTribe.io developpers team. 6 | The starter kit is built on top of Node.js, Express, React and Redux, containing modern web development tools such as Webpack and Babel. 7 | 8 | A solid starting point for both professionals and newcomers to the industry. 9 | 10 | |**Front-end tools**|**Back-end tools**|**Functional Testing**|**Dev Environment Configuration**| 11 | |---|---|---|---| 12 | |React|Express|Selenium|Docker| 13 | |Redux|Postgresql|Cucumber.js| 14 | |CSS/SCSS|Sequelize|Saucelabs| 15 | 16 | ## Customization 17 | 18 | The `master` branch of the starter kit does not include advanced integrations. 19 | However we do provide variants that you can use as a reference. 20 | 21 | * [variant/graphql](https://github.com/thetribeio/node-react-starter-kit/tree/variant/graphql) : 22 | Provide an [GraphQL][gql] API running with [apollo][apollo] client. 23 | * [variant/ssr-graphql](https://github.com/thetribeio/node-react-starter-kit/tree/variant/ssr-graphql) : 24 | Provide server side rendering support with a [GraphQL][gql] API running with [apollo][apollo] client. 25 | 26 | [gql]: https://graphql.org 27 | [apollo]: https://www.apollographql.com/docs/react/ 28 | 29 | ## Getting Started 30 | 31 | ### Installation 32 | 33 | #### start the app 34 | 35 | To run locally the project you should first install the dependencies 36 | 37 | ```bash 38 | # for local environment 39 | yarn install 40 | yarn start 41 | 42 | # for docker environment 43 | docker-compose up -d 44 | docker-compose stop app 45 | docker-compose run --rm app yarn install 46 | docker-compose run --rm app sequelize db:migrate 47 | docker-compose start app 48 | ``` 49 | 50 | You may create a file `docker-compose.override.yml` at the root to override your configuration. 51 | Most likely you might be interested in opening a port on the host, or use your yarn cache in docker containers. 52 | 53 | ```yaml 54 | version: '2.4' 55 | services: 56 | app: 57 | volumes: 58 | - ~/.cache/yarn:/home/app/.cache/yarn:rw 59 | ports: 60 | - 3000:3000 61 | ``` 62 | 63 | 64 | ## Configure your user id 65 | 66 | The containers are configured tu run with an user with ID 1000 to remove permissions problems, but if your user ID is 67 | not 1000 you will need to configure the images to use your user ID. 68 | 69 | You can get the your user ID by running `id -u` 70 | 71 | If your user ID is not 1000 you will need to add the following config to your `.env` 72 | 73 | ``` 74 | UID=YourUID 75 | ``` 76 | 77 | And then run `docker-compose build` to rebuild your containers. 78 | 79 | ### run the linter on your code 80 | 81 | ```bash 82 | # run locally 83 | yarn lint 84 | 85 | # run in docker 86 | docker-compose run --rm app lint 87 | ``` 88 | 89 | ### build the app 90 | 91 | ```bash 92 | # to build locally 93 | yarn build 94 | 95 | # to build a production image (docker) 96 | docker build -f .production/Dockerfile -t [image:tag] . 97 | ``` 98 | 99 | ### Run the production image locally 100 | 101 | You might try to run the production image locally with `docker-compose`, to do so, simply create a new directory anywhere and use the following configuration. 102 | 103 | ```yaml 104 | version: '2.0' 105 | services: 106 | app: 107 | # use the right tag 108 | image: [image:tag] 109 | environment: 110 | DATABASE_HOST: postgres 111 | DATABASE_NAME: thetribe 112 | DATABASE_USER: thetribe 113 | DATABASE_PASSWORD: 424242 114 | # set the env as you need it 115 | depends_on: 116 | - postgres 117 | ports: 118 | - 3000:3000 119 | postgres: 120 | image: postgres:10.7 121 | environment: 122 | POSTGRES_USER: thetribe 123 | POSTGRES_PASSWORD: 424242 124 | ``` 125 | 126 | ### Watch options 127 | 128 | You may provide watch options for the compiler simply by writing a file named `watchOptions.config.js` at the root directory. 129 | 130 | ```js 131 | module.exports = { 132 | // Watching may not work with NFS and machines in VirtualBox 133 | // Uncomment next line if it is your case (use true or interval in milliseconds) 134 | // poll: true, 135 | // Decrease CPU or memory usage in some file systems 136 | // ignored: /node_modules/, 137 | }; 138 | ``` 139 | 140 | ### Global & module style sheets 141 | 142 | You may import style sheets two ways in your app. 143 | Firstly, if you important a style sheets from the directories `app/components` or `app/routes`, 144 | your style will be imported as a module. 145 | It means you have to import it and manipulate it that way ; 146 | 147 | ```js 148 | // import it as a module 149 | import style from './style.css'; 150 | 151 | // and use it that way 152 |
153 | ``` 154 | 155 | However if you import a style sheet from elsewhere (node modules or another location in your sources), 156 | it wil be imported as a global. It means you have to import it that way ; 157 | 158 | ```js 159 | import './style.css'; 160 | ``` 161 | 162 | You may either import CSS style sheets or SASS stylesheet (using the extension `.scss`). 163 | 164 | ### Inject settings to frontend (appData) 165 | 166 | The backend renders the HTML entry point for your application. 167 | By doing so, it allows you to inject settings into your frontend application. 168 | 169 | First you've to push your data into an object on your server side. 170 | 171 | ```js 172 | // api/index.js 173 | const appData = { 174 | /* ... */ 175 | myInjectedSettings: 42, 176 | /* ... */ 177 | }; 178 | ``` 179 | 180 | Then you may get those injected settings anywhere in your react scope. 181 | You may either use a `custom hook`. 182 | 183 | ```js 184 | import { useAppData } from '@app/App'; 185 | 186 | const MyComponent = () => { 187 | const { myInjectedSettings } = useAppData(); 188 | 189 | /* ... */ 190 | }; 191 | ``` 192 | 193 | Or use the `context consumer` directly. 194 | 195 | ```js 196 | import { AppDataContext } from '@app/App'; 197 | 198 | class MyComponent extends Component { 199 | render() { 200 | const { myInjectedSettings } = this.context; 201 | 202 | /* ... */ 203 | } 204 | } 205 | 206 | MyComponent.contextType = AppDataContext; 207 | ``` 208 | 209 | The injected settings (we call here `appData`) are also available in `thunk reducers` 210 | 211 | ```js 212 | const asyncActionCreator = () => (dispatch, getState, { appData }) => { 213 | const { myInjectedSettings } = appData; 214 | 215 | /* ... */ 216 | }; 217 | ``` 218 | -------------------------------------------------------------------------------- /api/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "module-resolver", { 5 | "alias": { 6 | "@api": "./api", 7 | } 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /api/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "settings": { 6 | "import/internal-regex": "^@api/", 7 | "import/resolver": { 8 | "alias": { 9 | extensions: ['.js', '.jsx'], 10 | map: [ 11 | ["@api", "./api"] 12 | ] 13 | } 14 | } 15 | }, 16 | "globals": { 17 | "__DEV__": false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /api/components/Html.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | const Html = ({ appData, manifest: { js: scripts, css: styles } }) => ( 5 | 6 | 7 | 8 | theTribe 9 | 10 | 11 | {styles.map((style) => )} 12 | 13 | 14 |
15 | {scripts.map((script) =>