├── .dockerignore ├── .env ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .husky ├── pre-commit └── pre-push ├── .lintstagedrc.json ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── .stylelintignore ├── .stylelintrc.json ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── index.html ├── package-lock.json ├── package.json ├── src ├── client │ ├── App.jsx │ ├── App.module.css │ ├── components │ │ ├── Button │ │ │ ├── Button.jsx │ │ │ └── Button.module.css │ │ └── Welcome │ │ │ ├── Welcome.jsx │ │ │ └── Welcome.module.css │ ├── favicon.svg │ ├── globals.css │ ├── logo.svg │ └── main.jsx └── server │ ├── index.js │ └── lib │ └── router.js └── vite.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | # config files 2 | .github/ 3 | .husky/ 4 | .jest/ 5 | .vscode/ 6 | .editorconfig 7 | .eslintrc 8 | .git 9 | .gitignore 10 | .prettierignore 11 | .prettierrc 12 | .releaserc 13 | Dockerfile 14 | docker-compose.yml 15 | Jenkinsfile 16 | jest.config.js 17 | 18 | # documentation 19 | LICENSE 20 | CHANGELOG.md 21 | README.md 22 | 23 | # build files 24 | node_modules/ 25 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | VITE_SVG_PATH='/public/generated' 2 | PORT=3001 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | .eslintcache -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": ["eslint:recommended", "prettier", "plugin:react/recommended"], 8 | "parser": "babel-eslint", 9 | "parserOptions": { 10 | "ecmaVersion": 12, 11 | "sourceType": "module" 12 | }, 13 | "plugins": ["react-hooks"], 14 | "rules": {}, 15 | "settings": { 16 | "react": { 17 | "version": "17.0" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | .eslintcache -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged --config .lintstagedrc.json 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm test 5 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": "eslint --fix --cache", 3 | "*.css": "stylelint --fix", 4 | "*.{js,jsx,ts,tsx,css,html,json,md,mdx}": "prettier --write" 5 | } 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v14.15.5 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | .eslintcache -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | .eslintcache -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-slim 2 | 3 | WORKDIR /app 4 | 5 | # Setup a path for using local npm packages 6 | RUN mkdir -p /opt/node_modules 7 | 8 | COPY ./package.json /app 9 | COPY ./package-lock.json /app 10 | 11 | RUN npm ci 12 | 13 | COPY ./ /app 14 | 15 | RUN npm run client:build 16 | # server build needs to run after client build because the client build using Vite 17 | # removes the dist/ folder before compiling its code 18 | RUN npm run server:build 19 | 20 | EXPOSE 3001 21 | 22 | CMD ["npm", "start"] 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Leon Machens 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 | # Vite React Express Docker Boilerplate 2 | 3 | > Quickly bootstrap a new project with Vite React Express Boilerplate. 4 | 5 | This boilerplate is a fork of [lmachens/vite-boilerplate](https://github.com/lmachens/vite-boilerplate), but replaces TypeScript with JavaScript, adds Docker, and removes Storybook. 6 | 7 | This boilerplate contains all the tools you need to build a modern web app with Vite, Docker, React, and Express. You can use it to quickly bootstrap your project. 8 | 9 | ESLint, stylelint, prettier, husky and lintstaged are configured to give you a solid development experience. 10 | 11 | ## Installing / Developing 12 | 13 | First, [create a repository from this template](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/creating-a-repository-from-a-template). 14 | 15 | Now you are ready to go: 16 | 17 | ```shell 18 | docker-compose build 19 | ``` 20 | 21 | This will install the dependencies required to run the boilerplate. 22 | 23 | ```shell 24 | docker-compose up 25 | ``` 26 | 27 | Boom! The Docker container will run your server and client in development mode. 28 | 29 | The default PORTS are: 30 | 31 | - `3001` for the server 32 | - `3000` for the client 33 | 34 | You can configure the server port by setting the `PORT` environment variable in `.env`. 35 | 36 | | KEY | VALUE | 37 | | ---- | ------------------------------------------------------------- | 38 | | PORT | (Optional) Port for the server environment (defaults to 3001) | 39 | 40 | ## Building 41 | 42 | To build the Docker image, run: 43 | 44 | ```shell 45 | docker build -t vite-react-express . 46 | ``` 47 | 48 | To run the image locally: 49 | 50 | ```shell 51 | docker run --rm --name vite-react-express -p 3001:3001 vite-react-express:latest 52 | ``` 53 | 54 | and navigate to `http://localhost:3001`. 55 | 56 | In production, you have a single server serving everything. 57 | 58 | `/api/*` is the API endpoint. 59 | `/*` is the client. 60 | 61 | ## Tests 62 | 63 | A test runner is not installed (right now). But ESLint and Prettier are checked on commit and pushed thanks to husky and lintstaged. 64 | 65 | ## Licensing 66 | 67 | MIT 68 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | services: 3 | app: 4 | build: . 5 | container_name: vite-react-express 6 | command: npm run dev 7 | image: vite-react-express:latest 8 | ports: 9 | - '3000:3000' 10 | - '3001:3001' 11 | volumes: 12 | - .:/app 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-react-express-docker-boilerplate", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "clean": "rimraf dist", 6 | "dev": "concurrently \"npm run server:dev\" \"npm run client:dev\"", 7 | "client:dev": "vite", 8 | "server:dev": "nodemon src/server/index.js", 9 | "server:build": "babel --verbose --out-dir dist/server src/server", 10 | "client:build": "vite build", 11 | "build": "npm run server:build && npm run client:build", 12 | "serve": "vite preview", 13 | "prepare": "husky install", 14 | "test": "prettier --check . && eslint . && stylelint \"**/*.css\"", 15 | "start": "node dist/server/index.js" 16 | }, 17 | "devDependencies": { 18 | "@babel/cli": "^7.15.7", 19 | "@babel/core": "^7.15.7", 20 | "@vitejs/plugin-react": "^1.0.2", 21 | "babel-eslint": "^10.1.0", 22 | "babel-loader": "^8.2.2", 23 | "concurrently": "^6.2.1", 24 | "eslint": "^7.32.0", 25 | "eslint-config-prettier": "^8.3.0", 26 | "eslint-plugin-react": "^7.26.1", 27 | "eslint-plugin-react-hooks": "^4.2.0", 28 | "http-proxy-middleware": "^2.0.1", 29 | "husky": "^7.0.2", 30 | "lint-staged": "^11.1.2", 31 | "nodemon": "^2.0.13", 32 | "prettier": "2.4.1", 33 | "prop-types": "^15.7.2", 34 | "rimraf": "^3.0.2", 35 | "stylelint": "^13.13.1", 36 | "stylelint-config-prettier": "^8.0.2", 37 | "stylelint-config-standard": "^22.0.0", 38 | "vite": "^2.5.10" 39 | }, 40 | "dependencies": { 41 | "dotenv": "^10.0.0", 42 | "express": "^4.17.1", 43 | "react": "^17.0.2", 44 | "react-dom": "^17.0.2", 45 | "react-router-dom": "^5.3.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/client/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import logo from './logo.svg'; 3 | import styles from './App.module.css'; 4 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; 5 | import Welcome from './components/Welcome/Welcome'; 6 | 7 | function App() { 8 | const [count, setCount] = useState(0); 9 | 10 | return ( 11 | 12 |
13 |
14 | logo 15 | 16 |

17 | 20 |

21 |

22 | Edit App.jsx and save to test HMR updates. 23 |

24 | {/* Testing env variable - https://vitejs.dev/guide/env-and-mode.html#env-files */} 25 |

SVG path: {`${import.meta.env.VITE_SVG_PATH}`}

26 |

27 | 33 | Learn React 34 | 35 | {' | '} 36 | 42 | Vite Docs 43 | 44 |

45 | 46 | 47 |
About
48 |
49 | 50 |
Home
51 |
52 |
53 |
54 |
55 |
56 | ); 57 | } 58 | 59 | export default App; 60 | -------------------------------------------------------------------------------- /src/client/App.module.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | 40 | button { 41 | font-size: calc(10px + 2vmin); 42 | } 43 | -------------------------------------------------------------------------------- /src/client/components/Button/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classes from './Button.module.css'; 4 | 5 | function Button({ children }) { 6 | return ; 7 | } 8 | 9 | Button.propTypes = { 10 | children: PropTypes.element.isRequired, 11 | }; 12 | 13 | export default Button; 14 | -------------------------------------------------------------------------------- /src/client/components/Button/Button.module.css: -------------------------------------------------------------------------------- 1 | .button { 2 | color: #000; 3 | background: linear-gradient(180deg, #ffbb3b, #ffa90a); 4 | font-size: 0.8em; 5 | padding: 0.8em 2em; 6 | border: none; 7 | border-radius: 0.4em; 8 | text-transform: uppercase; 9 | } 10 | -------------------------------------------------------------------------------- /src/client/components/Welcome/Welcome.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import classes from './Welcome.module.css'; 3 | 4 | function Welcome() { 5 | const [message, setMessage] = useState(''); 6 | 7 | useEffect(() => { 8 | fetch('/api/hello') 9 | .then((response) => response.json()) 10 | .then((data) => setMessage(data.message)); 11 | }, []); 12 | 13 | return

{message}

; 14 | } 15 | 16 | export default Welcome; 17 | -------------------------------------------------------------------------------- /src/client/components/Welcome/Welcome.module.css: -------------------------------------------------------------------------------- 1 | .message { 2 | font-weight: bold; 3 | } 4 | -------------------------------------------------------------------------------- /src/client/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/client/globals.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/client/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/client/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './globals.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.querySelector('#root') 11 | ); 12 | -------------------------------------------------------------------------------- /src/server/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const router = require('./lib/router'); 4 | 5 | const { PORT = 3001 } = process.env; 6 | 7 | const app = express(); 8 | 9 | // Middleware that parses json and looks at requests where the Content-Type header matches the type option. 10 | app.use(express.json()); 11 | 12 | // Serve API requests from the router 13 | app.use('/api', router); 14 | 15 | // Serve app production bundle 16 | app.use(express.static('dist/client')); 17 | 18 | // Handle client routing, return all requests to the app 19 | app.get('*', (_req, res) => { 20 | res.sendFile(path.join(__dirname, '../client/index.html')); 21 | }); 22 | 23 | app.listen(PORT, () => { 24 | console.log(`Server listening at http://localhost:${PORT}`); 25 | }); 26 | -------------------------------------------------------------------------------- /src/server/lib/router.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const router = express.Router(); 4 | 5 | router.get('/hello', async (_req, res) => { 6 | res.status(200).json({ message: 'Hello World!' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | dotenv.config(); 3 | 4 | import { defineConfig } from 'vite'; 5 | import react from '@vitejs/plugin-react'; 6 | 7 | const { PORT = 3001 } = process.env; 8 | 9 | // https://vitejs.dev/config/ 10 | export default defineConfig({ 11 | plugins: [react()], 12 | server: { 13 | host: '0.0.0.0', 14 | proxy: { 15 | '/api': { 16 | target: `http://localhost:${PORT}`, 17 | changeOrigin: true, 18 | }, 19 | }, 20 | }, 21 | build: { 22 | outDir: 'dist/client', 23 | }, 24 | }); 25 | --------------------------------------------------------------------------------