├── .gitignore ├── api ├── .gitignore ├── package.json └── src │ └── server.js ├── docker ├── docker-compose.yml ├── nginx.conf ├── nginx.dockerfile └── node.dockerfile ├── license ├── package.json ├── readme.md └── web ├── .gitignore ├── package.json ├── public ├── favicon.ico └── index.html └── src ├── App.css ├── App.js ├── App.test.js ├── index.css ├── index.js └── logo.svg /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-api", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "express": "^4.15.2" 7 | }, 8 | "devDependencies": { 9 | "babel-cli": "^6.24.0", 10 | "babel-preset-env": "^1.2.1", 11 | "nodemon": "^1.11.0", 12 | "rimraf": "^2.6.1" 13 | }, 14 | "scripts": { 15 | "start": "nodemon src/server.js --exec babel-node", 16 | "clean": "rimraf build", 17 | "prebuild": "npm run clean", 18 | "build": "babel src -d build" 19 | }, 20 | "babel": { 21 | "presets": [ 22 | [ 23 | "env", 24 | { 25 | "targets": { 26 | "node": "current" 27 | } 28 | } 29 | ] 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /api/src/server.js: -------------------------------------------------------------------------------- 1 | import http from "http"; 2 | import express from "express"; 3 | 4 | const PORT = 8080; 5 | const DELAY = 2500; 6 | 7 | const reverse = s => { 8 | const out = s.split("").reverse().join(""); 9 | return new Promise(resolve => { 10 | setTimeout(() => resolve(out), DELAY); 11 | }); 12 | }; 13 | 14 | const app = express(); 15 | app.server = http.createServer(app); 16 | 17 | app.get("/api/reverse/:something", async (req, res) => { 18 | const something = req.params.something; 19 | console.log(`Reversing "${something}"`); 20 | const reversed = await reverse(something); 21 | res.json(reversed); 22 | }); 23 | 24 | app.server.listen(PORT); 25 | console.log(`API started on port ${app.server.address().port}`); 26 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | node: 4 | build: 5 | context: .. 6 | dockerfile: docker/node.dockerfile 7 | nginx: 8 | build: 9 | context: .. 10 | dockerfile: docker/nginx.dockerfile 11 | ports: 12 | - "8000:80" 13 | #docker-compose -p docker up --build -------------------------------------------------------------------------------- /docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | 5 | location / { 6 | root /usr/share/nginx/html; 7 | index index.html index.htm; 8 | } 9 | 10 | location /api/ { 11 | proxy_set_header X-Real-IP $remote_addr; 12 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 13 | proxy_set_header Host $http_host; 14 | proxy_set_header X-NginX-Proxy true; 15 | 16 | proxy_pass http://node:8080/api/; 17 | proxy_redirect off; 18 | } 19 | } -------------------------------------------------------------------------------- /docker/nginx.dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:alpine 2 | COPY web/build /usr/share/nginx/html 3 | COPY docker/nginx.conf /etc/nginx/conf.d/default.conf 4 | -------------------------------------------------------------------------------- /docker/node.dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | RUN mkdir -p /usr/src/app 3 | WORKDIR /usr/src/app 4 | COPY api/build /usr/src/app 5 | COPY api/node_modules /usr/src/app/node_modules 6 | EXPOSE 8080 7 | CMD [ "node", "server.js" ] -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Hexacta 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample", 3 | "version": "1.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "scripts": { 7 | "postinstall": "run-p install:api install:web", 8 | "install:api": "cd api && npm install", 9 | "install:web": "cd web && npm install", 10 | 11 | "start": "run-p start:api start:web", 12 | "start:api": "npm run start --prefix api", 13 | "start:web": "npm run start --prefix web", 14 | 15 | "build": "run-p build:api build:web", 16 | "build:api": "cd api && npm run build && cd ..", 17 | "build:web": "cd web && npm run build && cd ..", 18 | 19 | "prestart:docker": "npm run build", 20 | "start:docker": "docker-compose -f docker/docker-compose.yml up -d --build", 21 | "poststart:docker": "opn http://localhost:8000/", 22 | 23 | "stop:docker": "docker-compose -f docker/docker-compose.yml down" 24 | }, 25 | "devDependencies": { 26 | "npm-run-all": "^4.0.2", 27 | "opn-cli": "^3.1.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Project Structure Sample 2 | 3 | This repostory complements a [medium post explaining a project structure focused on keeping back end and front end separated](https://medium.com/@pomber/do-not-mix-front-end-and-back-end-530d55b998a5). 4 | 5 | ## Getting Started 6 | 7 | First thing you need to do is install dependencies: 8 | ``` 9 | $ npm install 10 | ``` 11 | 12 | Then you can run the SPA and the API in development mode (watching for changes) with: 13 | ``` 14 | $ npm start 15 | ``` 16 | 17 | And, if you have docker installed, you can dockerize it and run it with: 18 | ``` 19 | $ npm start:docker 20 | ``` 21 | 22 | ### web and api folders 23 | 24 | Each folder has its own scripts you can run if you just want to work in one part of the app. 25 | 26 | ## npm scripts 27 | 28 | ### npm install 29 | `npm install` will install the root folder (development) dependencies, and after that do the same with `web` dependencies and `api` dependencies. 30 | 31 | ### npm start 32 | `npm start` will run both subfolders' `npm start` in parallel using [`npm-run-all`](https://github.com/mysticatea/npm-run-all). 33 | 34 | ### npm run build 35 | `npm run build` will run both subfolders' `npm run build` in parallel. 36 | 37 | ### npm run start:docker 38 | `npm run start:docker` first call `npm build` then build, create and start two containers, one for node running the api, and the other running nginx serving the static SPA files and proxying all requests starting in `/api/` to the node container. 39 | After that it opens a browser on "http://localhost:8000/". 40 | 41 | You can stop the containers with `npm run stop:docker`. 42 | 43 | ## License 44 | 45 | MIT © [Hexacta](https://www.hexacta.com) -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-web", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "15.4.2", 7 | "react-dom": "15.4.2" 8 | }, 9 | "devDependencies": { 10 | "react-scripts": "0.9.5" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test --env=jsdom", 16 | "eject": "react-scripts eject" 17 | }, 18 | "proxy": "http://localhost:8080" 19 | } -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexacta/project-structure-sample/83f60ef176f29ab82f10c466b53cbef1f66fe270/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 16 |50 | {this.state.result} 51 |
52 |