├── client ├── .env.production ├── .prettierrc ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── apple-touch-icon.png │ ├── manifest.json │ └── index.html ├── .flowconfig ├── cypress │ ├── videos │ │ └── homepage_spec.js.mp4 │ └── integration │ │ └── homepage_spec.js ├── cypress.json ├── src │ ├── index.jsx │ ├── css │ │ └── Home.css │ ├── pages │ │ └── Home.jsx │ └── utils │ │ └── apiWrapper.js ├── .eslintrc.json ├── README.md └── package.json ├── api ├── example.env ├── .prettierrc ├── src │ ├── utils │ │ ├── index.js │ │ ├── mongo-setup.js │ │ └── createResponse.js │ ├── api │ │ ├── index.js │ │ └── home.js │ ├── middleware │ │ ├── index.js │ │ ├── errorWrap.js │ │ └── errorHandler.js │ ├── models │ │ └── home.js │ └── app.js ├── .eslintrc.json ├── test │ └── home.test.js ├── package.json ├── bin │ └── www └── README.md ├── LICENSE ├── vercel.json ├── .github └── workflows │ ├── api.yaml │ └── client.yaml ├── .gitignore ├── README.md └── walkthrough.md /client/.env.production: -------------------------------------------------------------------------------- 1 | REACT_APP_VERCEL_URL=${VERCEL_URL} 2 | -------------------------------------------------------------------------------- /api/example.env: -------------------------------------------------------------------------------- 1 | # Delete and replace with a .env file as needed 2 | MONGO_URL= -------------------------------------------------------------------------------- /api/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 2, 4 | "trailingComma": "all" 5 | } -------------------------------------------------------------------------------- /client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 2, 4 | "trailingComma": "all" 5 | } -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hack4impact-uiuc/mern-template/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [lints] 8 | 9 | [options] 10 | 11 | [strict] 12 | -------------------------------------------------------------------------------- /api/src/utils/index.js: -------------------------------------------------------------------------------- 1 | const createResponse = require('./createResponse'); 2 | 3 | module.exports = { 4 | createResponse, 5 | }; 6 | -------------------------------------------------------------------------------- /client/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hack4impact-uiuc/mern-template/HEAD/client/public/apple-touch-icon.png -------------------------------------------------------------------------------- /client/cypress/videos/homepage_spec.js.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hack4impact-uiuc/mern-template/HEAD/client/cypress/videos/homepage_spec.js.mp4 -------------------------------------------------------------------------------- /client/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "projectId": "your-id", 4 | "pluginsFile": false, 5 | "supportFile": false 6 | } 7 | -------------------------------------------------------------------------------- /api/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@hack4impact-uiuc"], 3 | "extends": ["plugin:@hack4impact-uiuc/base"], 4 | "rules": { 5 | "require-await": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /api/src/api/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | router.use('/home', require('./home')); 5 | 6 | module.exports = router; 7 | -------------------------------------------------------------------------------- /api/src/middleware/index.js: -------------------------------------------------------------------------------- 1 | const errorHandler = require('./errorHandler'); 2 | const errorWrap = require('./errorWrap'); 3 | 4 | module.exports = { 5 | errorHandler, 6 | errorWrap, 7 | }; 8 | -------------------------------------------------------------------------------- /api/src/models/home.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Home = new mongoose.Schema({ 4 | text: { type: String }, 5 | }); 6 | 7 | module.exports = mongoose.model('Home', Home); 8 | -------------------------------------------------------------------------------- /client/cypress/integration/homepage_spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Home page', () => { 4 | beforeEach(() => { 5 | cy.visit('/'); 6 | }); 7 | 8 | it('successfully loads', () => { 9 | cy.visit('/'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /client/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Home from './pages/Home'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root'), 11 | ); 12 | -------------------------------------------------------------------------------- /api/src/middleware/errorWrap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Middleware to allow us to safely handle errors in async/await code 3 | * without wrapping each route in try...catch blocks 4 | */ 5 | const errorWrap = (fn) => (req, res, next) => 6 | Promise.resolve(fn(req, res, next)).catch(next); 7 | 8 | module.exports = errorWrap; 9 | -------------------------------------------------------------------------------- /client/src/css/Home.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter&display=swap'); 2 | 3 | h1, 4 | p { 5 | font-family: 'Inter', sans-serif; 6 | margin: 0 auto; 7 | text-align: center; 8 | } 9 | 10 | h1 { 11 | padding-top: 15vh; 12 | } 13 | 14 | p { 15 | padding-top: 2em; 16 | } 17 | -------------------------------------------------------------------------------- /api/src/middleware/errorHandler.js: -------------------------------------------------------------------------------- 1 | const errorHandler = (err, req, res) => { 2 | console.error(err); 3 | console.error(err.stack); 4 | res.status(500).json({ 5 | code: 500, 6 | message: err.message, 7 | result: {}, 8 | success: false, 9 | }); 10 | }; 11 | 12 | module.exports = errorHandler; 13 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "mern-template", 3 | "name": "mern-template", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /api/src/utils/mongo-setup.js: -------------------------------------------------------------------------------- 1 | // const mongoose = require('mongoose'); 2 | 3 | // // CONNECTION TO MONGO 4 | // mongoose.connect(process.env.MONGO_URL, { 5 | // useNewUrlParser: true, 6 | // useUnifiedTopology: true, 7 | // }); 8 | 9 | // mongoose.Promise = global.Promise; 10 | 11 | // mongoose.connection 12 | // .once('open', () => console.log('Connected to MongoLab instance.')) 13 | // .on('error', (error) => console.log('Error connecting to MongoLab:', error)); 14 | -------------------------------------------------------------------------------- /api/src/utils/createResponse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Standardizes a format for API responses. 3 | * @param {Number} status is the status code for the response. 4 | * @param {String} message describes the response. 5 | * @param {Object} data contains relevant data for the response. 6 | * @return {Object} is the response body. 7 | */ 8 | const createResponse = (status, message, data) => ({ 9 | message, 10 | success: 200 <= status && status < 300, 11 | result: data, 12 | }); 13 | 14 | module.exports = createResponse; 15 | -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@hack4impact-uiuc/base", 4 | "plugin:@hack4impact-uiuc/react", 5 | "plugin:flowtype/recommended" 6 | ], 7 | "plugins": ["@hack4impact-uiuc", "flowtype"], 8 | "rules": { 9 | "import/order": [ 10 | "warn", 11 | { 12 | "groups": [ 13 | "builtin", 14 | "external", 15 | "internal", 16 | "parent", 17 | "sibling", 18 | "index" 19 | ], 20 | "newlines-between": "always" 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Hack4Impact MERN Template 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /client/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { useEffect, useState } from 'react'; 3 | import type { Node } from 'react'; 4 | 5 | import { getSampleResponse } from '../utils/apiWrapper'; 6 | 7 | import '../css/Home.css'; 8 | 9 | function Home(): Node { 10 | const [text, setText] = useState('You did not run local API!'); 11 | 12 | useEffect(() => { 13 | const populateText = async () => { 14 | const resp = await getSampleResponse(); 15 | if (!resp.error) { 16 | setText(resp.data.result); 17 | } 18 | }; 19 | 20 | populateText(); 21 | }, []); 22 | 23 | return ( 24 | <> 25 |

MERN Template

26 |

27 | Below will tell you if the API is running. 28 |
29 | {text} 30 |

31 | 32 | ); 33 | } 34 | 35 | export default Home; 36 | -------------------------------------------------------------------------------- /api/src/api/home.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const { errorWrap } = require('../middleware'); 4 | const { createResponse } = require('../utils'); 5 | 6 | // uncomment to use the schema 7 | // const Home = require('../models/home'); 8 | 9 | router.get( 10 | '/', 11 | errorWrap(async (req, res) => { 12 | // MongoDB connection 13 | // const homeText = await Home.findOne(); 14 | const homeText = "You've connected the database! Isn't it so beautiful???"; 15 | 16 | // Template for formulating a successful API response 17 | const statusCode = 200; 18 | const responseBody = createResponse( 19 | statusCode, 20 | 'Successfully returned home text', 21 | homeText, 22 | ); 23 | res.status(statusCode).json(responseBody); 24 | }), 25 | ); 26 | 27 | module.exports = router; 28 | -------------------------------------------------------------------------------- /api/test/home.test.js: -------------------------------------------------------------------------------- 1 | const app = require('../src/app'); 2 | const mongoose = require('mongoose'); 3 | const request = require('supertest'); 4 | 5 | afterAll(async () => { 6 | await mongoose.connection.close(); 7 | }); 8 | 9 | describe('GET / ', () => { 10 | test('API should return working message', async () => { 11 | const response = await request(app).get('/'); 12 | expect(response.body).toEqual('API working!'); 13 | expect(response.statusCode).toBe(200); 14 | }); 15 | }); 16 | 17 | describe('GET /api/home/ ', () => { 18 | test('API should return home text', async () => { 19 | const response = await request(app).get('/api/home/'); 20 | expect(response.body.message).toEqual('Successfully returned home text'); 21 | expect(response.body.result).toEqual( 22 | "You've connected the database! Isn't it so beautiful???", 23 | ); 24 | expect(response.statusCode).toBe(200); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Client 2 | 3 | This folder contains the frontend client of the application. 4 | 5 | ## Install & Run 6 | 7 | To set up, first `cd` into this directory. Then, 8 | 9 | ```bash 10 | yarn && yarn start 11 | ``` 12 | 13 | Then go to [http://localhost:3000](http://localhost:3000) in your browser. Make sure that your backend is running in order for data to be populated. 14 | 15 | ## Technologies 16 | 17 | Built with [React](https://reactjs.org/). 18 | 19 | ### Testing 20 | 21 | Use Cypress to test. To run, `yarn test` in the directory. 22 | 23 | ### Code Style 24 | 25 | Use [ESLint](https://eslint.org) with [Prettier](https://prettier.io/) and the [Airbnb Javascript Style Guide](https://github.com/airbnb/javascript). 26 | 27 | To run, `yarn lint` and `yarn format` in the directory. 28 | 29 | ### Type Checking 30 | 31 | Use [Flow](https://flow.org/) for type checking. 32 | 33 | To run, `yarn flow` in the directory. 34 | -------------------------------------------------------------------------------- /api/src/app.js: -------------------------------------------------------------------------------- 1 | const createError = require('http-errors'); 2 | const cors = require('cors'); 3 | const express = require('express'); 4 | const helmet = require('helmet'); 5 | const logger = require('morgan'); 6 | const bodyParser = require('body-parser'); 7 | const apiRoutes = require('./api'); 8 | const { errorHandler } = require('./middleware'); 9 | 10 | const app = express(); 11 | 12 | app.use(helmet()); 13 | app.use(cors()); 14 | 15 | app.use(logger('dev')); 16 | 17 | app.use(bodyParser.json({ limit: '2.1mb' })); 18 | app.use(bodyParser.urlencoded({ limit: '2.1mb', extended: false })); 19 | 20 | // Mongo setup 21 | require('./utils/mongo-setup'); 22 | 23 | // Routes 24 | app.use('/api', apiRoutes); 25 | app.get('/', (req, res) => res.json('API working!')); 26 | app.get('/favicon.ico', (req, res) => res.status(204)); 27 | 28 | app.use(function (req, res, next) { 29 | next(createError(404)); 30 | }); 31 | 32 | app.use(errorHandler); 33 | 34 | module.exports = app; 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hack4Impact UIUC 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. -------------------------------------------------------------------------------- /client/src/utils/apiWrapper.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const BASE_URL = process.env.REACT_APP_VERCEL_URL 4 | ? `https://${process.env.REACT_APP_VERCEL_URL}/api` 5 | : 'http://localhost:9000/api'; 6 | 7 | /** 8 | * Returns a sample API response to demonstrate a working backend 9 | * Returns GET_SAMPLE_FAIL upon failure 10 | */ 11 | export const getSampleResponse = () => { 12 | const requestString = `${BASE_URL}/home`; 13 | return axios 14 | .get(requestString, { 15 | headers: { 16 | 'Content-Type': 'application/JSON', 17 | }, 18 | }) 19 | .catch((error) => ({ 20 | type: 'GET_SAMPLE_FAIL', 21 | error, 22 | })); 23 | }; 24 | 25 | /** 26 | * Executes a sample POST request 27 | * Returns POST_SAMPLE_FAIL upon failure 28 | */ 29 | export const addSampleResponse = (body) => { 30 | const requestString = `${BASE_URL}/home`; 31 | return axios 32 | .post(requestString, body, { 33 | headers: { 34 | 'Content-Type': 'application/JSON', 35 | }, 36 | }) 37 | .catch((error) => ({ 38 | type: 'POST_SAMPLE_FAIL', 39 | error, 40 | })); 41 | }; 42 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "regions": ["iad1"], 3 | "env": { 4 | "MONGO_URL": "@mern_template_db_url" 5 | }, 6 | "builds": [ 7 | { "src": "api/src/app.js", "use": "@now/node" }, 8 | { 9 | "src": "client/package.json", 10 | "use": "@now/static-build", 11 | "config": { "distDir": "build" } 12 | } 13 | ], 14 | "routes": [ 15 | { 16 | "src": "/api/(.*)", 17 | "headers": { "cache-control": "s-maxage=0" }, 18 | "dest": "api/src/app.js" 19 | }, 20 | { 21 | "src": "/static/(.*)", 22 | "headers": { "cache-control": "s-maxage=31536000, immutable" }, 23 | "dest": "client/static/$1" 24 | }, 25 | { "src": "/favicon.ico", "dest": "client/favicon.ico" }, 26 | { 27 | "src": "/asset-manifest.json", 28 | "dest": "client/asset-manifest.json" 29 | }, 30 | { 31 | "src": "/precache-manifest.(.*)", 32 | "dest": "client/precache-manifest.$1" 33 | }, 34 | { "src": "/manifest.json", "dest": "client/manifest.json" }, 35 | { 36 | "src": "/service-worker.js", 37 | "headers": { "cache-control": "s-maxage=0" }, 38 | "dest": "client/service-worker.js" 39 | }, 40 | { "src": "/(.*)", "dest": "client/index.html" } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mern-template-api", 3 | "version": "0.1.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "if-env NODE_ENV=production ?? npm run start:prod || npm run start:dev", 7 | "start:prod": "node -r dotenv/config ./bin/www", 8 | "start:dev": "nodemon -r dotenv/config ./bin/www", 9 | "lint": "eslint src", 10 | "lint:fix": "eslint --fix src", 11 | "format": "prettier --write \"./**/*.{js,jsx,json,md}\"", 12 | "format:check": "prettier --check \"./**/*.{js,jsx,json,md}\"", 13 | "test": "jest --setupFiles dotenv/config" 14 | }, 15 | "dependencies": { 16 | "cors": "^2.8.5", 17 | "debug": "~4.3.1", 18 | "dotenv": "^8.2.0", 19 | "express": "~4.17.1", 20 | "helmet": "^4.4.1", 21 | "http-errors": "~1.8.0", 22 | "if-env": "^1.0.4", 23 | "isomorphic-unfetch": "^3.0.0", 24 | "mongodb": "^3.3.2", 25 | "mongoose": "^5.11.13", 26 | "morgan": "~1.10.0", 27 | "mpath": "^0.8.4" 28 | }, 29 | "devDependencies": { 30 | "@hack4impact-uiuc/eslint-plugin": "^2.0.10", 31 | "eslint": "^7.18.0", 32 | "jest": "^26.6.3", 33 | "nodemon": "^2.0.7", 34 | "prettier": "^2.2.1", 35 | "supertest": "^6.1.1" 36 | }, 37 | "jest": { 38 | "testEnvironment": "node", 39 | "verbose": true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mern-template", 3 | "version": "0.1.0", 4 | "homepage": "https://pineapple.lol/", 5 | "license": "MIT", 6 | "dependencies": { 7 | "axios": "^0.21.1", 8 | "immer": "^9.0.6", 9 | "react": "^17.0.1", 10 | "react-dom": "^17.0.1", 11 | "react-scripts": "4.0.1" 12 | }, 13 | "devDependencies": { 14 | "@babel/cli": "^7.6.2", 15 | "@babel/core": "^7.6.2", 16 | "@babel/preset-flow": "^7.0.0", 17 | "@hack4impact-uiuc/eslint-plugin": "^2.0.10", 18 | "cypress": "^6.3.0", 19 | "eslint": "^7.18.0", 20 | "eslint-plugin-flowtype": "^5.2.0", 21 | "flow-bin": "^0.143.1", 22 | "prettier": "^2.2.1" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "lint": "eslint src --ext js,jsx", 27 | "lint:fix": "eslint --fix src --ext js,jsx", 28 | "format": "prettier --write \"./**/*.{js,jsx,css,scss,json,md}\"", 29 | "format:check": "prettier --check \"./**/*.{js,jsx,css,scss,json,md}\"", 30 | "flow": "flow", 31 | "build": "react-scripts build", 32 | "test": "yarn run cypress run", 33 | "eject": "react-scripts eject" 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /api/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const app = require('../src/app'); 4 | const debug = require('debug')('api:server'); 5 | const http = require('http'); 6 | 7 | const port = normalizePort(process.env.PORT || '9000'); 8 | app.set('port', port); 9 | 10 | const server = http.createServer(app); 11 | 12 | // Listen on provided port 13 | server.listen(port); 14 | server.on('error', onError); 15 | server.on('listening', onListening); 16 | 17 | // Normalize port into number, string, or false 18 | function normalizePort(val) { 19 | const port = parseInt(val, 10); 20 | 21 | if (isNaN(port)) return val; 22 | if (port >= 0) return port; 23 | 24 | return false; 25 | } 26 | 27 | // Event listener for HTTP server "error" event. 28 | function onError(error) { 29 | if (error.syscall !== 'listen') { 30 | throw error; 31 | } 32 | 33 | const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; 34 | 35 | switch (error.code) { 36 | case 'EACCES': 37 | console.error(bind + ' requires elevated privileges'); 38 | process.exit(1); 39 | break; 40 | case 'EADDRINUSE': 41 | console.error(bind + ' is already in use'); 42 | process.exit(1); 43 | break; 44 | default: 45 | throw error; 46 | } 47 | } 48 | 49 | // Event listener for HTTP server "listening" event. 50 | function onListening() { 51 | const addr = server.address(); 52 | const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/api.yaml: -------------------------------------------------------------------------------- 1 | name: api 2 | 3 | on: push 4 | 5 | defaults: 6 | run: 7 | working-directory: ./api 8 | 9 | jobs: 10 | api-format: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out repository 14 | uses: actions/checkout@v2 15 | 16 | - name: Set up Node 17 | uses: actions/setup-node@v2 18 | with: 19 | node-version: 12 20 | 21 | - name: Cache dependencies 22 | uses: actions/cache@v2 23 | with: 24 | path: api/node_modules 25 | key: ${{ runner.os }}-cache 26 | 27 | - name: Install dependencies 28 | run: yarn 29 | 30 | - name: Check formatting 31 | run: yarn format:check 32 | 33 | api-lint: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Check out repository 37 | uses: actions/checkout@v2 38 | 39 | - name: Set up Node 40 | uses: actions/setup-node@v2 41 | with: 42 | node-version: 12 43 | 44 | - name: Cache dependencies 45 | uses: actions/cache@v2 46 | with: 47 | path: api/node_modules 48 | key: ${{ runner.os }}-cache 49 | 50 | - name: Install dependencies 51 | run: yarn 52 | 53 | - name: Run linter 54 | run: yarn lint 55 | 56 | api-test: 57 | runs-on: ubuntu-latest 58 | steps: 59 | - name: Check out repository 60 | uses: actions/checkout@v2 61 | 62 | - name: Set up Node 63 | uses: actions/setup-node@v2 64 | with: 65 | node-version: 12 66 | 67 | - name: Cache dependencies 68 | uses: actions/cache@v2 69 | with: 70 | path: api/node_modules 71 | key: ${{ runner.os }}-cache 72 | 73 | - name: Install dependencies 74 | run: yarn 75 | 76 | - name: Build application 77 | run: yarn test 78 | -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | This folder contains the backend api of the application. 4 | 5 | ## Environments 6 | 7 | There are three environments that the backend runs in: `production`, `dev`, and `test`, each with their own database. `production` is set on deployment, `dev` is set whenever running locally, and `test` is set when tests are running. These environments are automatically set based on the task. 8 | 9 | ## Install & Run 10 | 11 | To set up, first create a `/config` directory in the `/api` directory. Then, create three `.env` files: `production.env`, `dev.env`, and `test.env`. Each should contain the following field, with unique values (a seperate database for each environment). 12 | 13 | ``` 14 | MONGO_URL=mongodb://:@.mlab.com: 15 | ``` 16 | 17 | Make sure that you have `dotenv-cli` installed globally with, 18 | 19 | ``` 20 | yarn global add dotenv-cli 21 | ``` 22 | 23 | Then, 24 | 25 | ```bash 26 | yarn && yarn start 27 | ``` 28 | 29 | This will create a server on [http://localhost:9000](http://localhost:9000). 30 | 31 | ## Technologies 32 | 33 | Built with [Express](https://expressjs.com/) and [MongoDB](https://www.mongodb.com/). 34 | 35 | ## Development 36 | 37 | If you are working with the API, remember to change the `BASE_URL` in [`client/src/utils/apiWrapper.js`](https://github.com/hack4impact-uiuc/mern-template/blob/master/client/src/utils/apiWrapper.js) to the local server `http://localhost:9000/`. 38 | 39 | ### Code Style 40 | 41 | Use [ESLint](https://eslint.org) with [Prettier](https://prettier.io/). 42 | 43 | ## Testing 44 | 45 | The unit tests are written with [Jest](https://jestjs.io/) and [SuperTest](https://github.com/visionmedia/supertest). 46 | 47 | To test, 48 | 49 | ```bash 50 | yarn test 51 | ``` 52 | 53 | If you are recieving the warning about [mismatched binaries](https://github.com/nodenv/nodenv/wiki/FAQ#npm-warning-about-mismatched-binaries), run 54 | 55 | ```bash 56 | npm config set scripts-prepend-node-path auto 57 | ``` 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | build 107 | .vercel 108 | -------------------------------------------------------------------------------- /.github/workflows/client.yaml: -------------------------------------------------------------------------------- 1 | name: client 2 | 3 | on: push 4 | 5 | defaults: 6 | run: 7 | working-directory: ./client 8 | 9 | jobs: 10 | client-format: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out repository 14 | uses: actions/checkout@v2 15 | 16 | - name: Set up Node 17 | uses: actions/setup-node@v2 18 | with: 19 | node-version: 12 20 | 21 | - name: Cache dependencies 22 | uses: actions/cache@v2 23 | with: 24 | path: client/node_modules 25 | key: ${{ runner.os }}-cache 26 | 27 | - name: Install dependencies 28 | run: yarn 29 | 30 | - name: Check formatting 31 | run: yarn format:check 32 | 33 | client-lint: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Check out repository 37 | uses: actions/checkout@v2 38 | 39 | - name: Set up Node 40 | uses: actions/setup-node@v2 41 | with: 42 | node-version: 12 43 | 44 | - name: Cache dependencies 45 | uses: actions/cache@v2 46 | with: 47 | path: client/node_modules 48 | key: ${{ runner.os }}-cache 49 | 50 | - name: Install dependencies 51 | run: yarn 52 | 53 | - name: Run linter 54 | run: yarn lint 55 | 56 | client-build: 57 | runs-on: ubuntu-latest 58 | steps: 59 | - name: Check out repository 60 | uses: actions/checkout@v2 61 | 62 | - name: Set up Node 63 | uses: actions/setup-node@v2 64 | with: 65 | node-version: 12 66 | 67 | - name: Cache dependencies 68 | uses: actions/cache@v2 69 | with: 70 | path: client/node_modules 71 | key: ${{ runner.os }}-cache 72 | 73 | - name: Install dependencies 74 | run: yarn 75 | 76 | - name: Build application 77 | run: yarn build 78 | 79 | client-test: 80 | runs-on: ubuntu-latest 81 | steps: 82 | - name: Check out repository 83 | uses: actions/checkout@v2 84 | 85 | - name: Cypress run 86 | uses: cypress-io/github-action@v2 87 | with: 88 | # record: true 89 | # parallel: true 90 | working-directory: client 91 | start: yarn start 92 | wait-on: "http://localhost:3000" 93 | wait-on-timeout: 120 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | hack4impact logo 3 |
4 | MERN Template 5 |
6 |

7 | 8 |

9 | 10 | 11 |

12 | 13 |

A project by Hack4Impact UIUC in collaboration with pineapplelol.

14 | 15 |

16 | Background • 17 | Usage • 18 | Technologies • 19 | Team • 20 | License 21 |

22 | 23 | ## Background 24 | 25 | MERN is one of the most widely used stacks for modern web development. Composed of [MongoDB](https://www.mongodb.com), [Express](https://expressjs.com), [React](https://reactjs.org), and [Node](https://nodejs.org/en/), MERN represents the full stack from the frontend to the backend. 26 | 27 | This template is a starting repository for setting up a MERN project. It includes directories for both the frontend and backend, as well as linting and testing workflows and settings. 28 | 29 | ## Usage 30 | 31 | The latest version of this application can be found at [pineapple.lol](https://pineapple.lol). 32 | 33 | To install and set up locally, follow the instructions in the [`/client`](https://github.com/hack4impact-uiuc/mern_template/tree/main/client) and [`/api`](https://github.com/hack4impact-uiuc/mern_template/tree/main/api) directories. 34 | 35 | ## Technologies 36 | 37 | This application is built with React, Express, MongoDB, and tested with Cypress and Jest. Vercel is used for automatic deployment. 38 | 39 | ## Team 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
Pusheen
Pusheen

Product Manager
Unicorn Pusheen
Unicorn Pusheen

Technical Lead
Pilot Pusheen
Pilot Pusheen

Product Designer
Angry Pusheen
Angry Pusheen

Product Researcher
Chef Pusheen
Chef Pusheen

Software Developer
Sad Pusheen
Sad Pusheen

Software Developer
Silly Pusheen
Silly Pusheen

Software Developer
Coffee Pusheen
Coffee Pusheen

Software Developer
Chatty Pusheen
Chatty Pusheen

Software Developer
Cookie Pusheen
Cookie Pusheen

Software Developer
58 | 59 | ## License 60 | 61 | [MIT](https://github.com/hack4impact-uiuc/ymca/blob/master/LICENSE) licensed. Copyright © 2022 [Hack4Impact UIUC](https://github.com/hack4impact-uiuc). 62 | -------------------------------------------------------------------------------- /walkthrough.md: -------------------------------------------------------------------------------- 1 | # Hack4Impact UIUC - MERN Template 2 | 3 | ## Background 4 | 5 | The majority of Hack4Impact projects are web based, and from those, are built using the MERN stack. 6 | MERN is one of the most widely used stacks for modern web development. Composed of [MongoDB](https://www.mongodb.com), [Express](https://expressjs.com), [React](https://reactjs.org), and [Node](https://nodejs.org/en/), MERN represents the full stack from the frontend to the backend. 7 | 8 | ## Provided Set Ups 9 | 10 | - Sexy README 11 | - Frontend and backend skeletons 12 | - Frontend testing with Cypress 13 | - Backend testing with Jest 14 | - Frontend and backend linting with ESLint 15 | - Continuous integration tests with GitHub Actions 16 | 17 | ## Project Structure 18 | 19 | The frontend code is contained in `client/` and the backend code is contained in `api/`. Each corresponding directory contains the respective configurations for testing and linting, as well as a `package.json` file with the necessary production and development packages. 20 | 21 | # README 22 | 23 | A sexy readme is provided. A couple things to change include 24 | 25 | - Nonprofit logo (replaces Hack4Impact logo) 26 | - Team member images & links 27 | 28 | # Client 29 | 30 | Navigate to `client/` 31 | 32 | To begin to set up the client, navigate to the `/public` directory. This will contain project metadata, such as the favicon and Apple touch icon. In addition, you can change the title of your project in `index.html`. 33 | 34 | In `package.json`, change the fields as needed (including project name, GitHub repository, and license). 35 | 36 | ## Running Client Locally 37 | 38 | To run, `yarn` to install dependencies, and then `yarn start` to start the development server. It will be located on `http://localhost:3000`. 39 | 40 | In `/src`, the React components will be developed. Given is a single `Home` component located in `/src/pages` which connects to the backend. It simply just renders an `h1` header and two `p` tags. If the API is not connected, the second tag will render the default state that indicates that the API is not connected. If it is connected, the state gets updated upon API fetch. 41 | 42 | ## Linting, Formatting, Typechecking, and Testing 43 | 44 | The linting, formatting, and testing frameworks have been provided. 45 | 46 | Linting uses ESLint linter, with the Airbnb style guide. Customization for specific rules can be entered in `.eslintrc.json`. To run linting, `yarn lint` within the `client` directory. 47 | 48 | Formatting uses `prettier` and can be customized in `.prettierrc`. Run formatting with `yarn format` in the `client` directory. 49 | 50 | Type checking uses Flow, which can be run with `yarn flow`. For a file to be detected by flow, add `// @flow` at the top. By default, type checking is not enabled in continuous integration as it hinders fast project development. 51 | 52 | Frontend testing uses Cypress. There is a default configuration for Cypress set up. For your project, please navigate to the Hack4Impact UIUC Cypress dashboard (which can be reached by signing in with GitHub to Cypress), and enable your project. You will recieve a project id, which you should enter in `cypress.json`. Tests can be found in `client/cypress/integration` To run Cypress tests, `yarn test` in the `client` directory. 53 | 54 | # API 55 | 56 | Navigate to `api/` 57 | 58 | In `package.json`, change the fields as needed (including project name, GitHub repository, and license). 59 | 60 | Here in `/src`, there are many folders. `/src/api` contains the code for fetching from MongoDB (currently commented out), `/src/middleware` contains the code for error handling, `/src/models` contains the MongoDB models, and `/src/routes` contains the routes for the endpoints of the API. 61 | 62 | There is also configuration for three seperate envirments: production (`production`), development (`dev`), and testing (`test`). Each of these have their own database, which can be configured within the `/config` directory. Inside, create three `.env` files: `production.env`, `dev.env`, and `test.env`. Each should contain the following field, with unique values (a seperate database for each environment). 63 | 64 | ``` 65 | MONGO_URL=mongodb://:@.mlab.com: 66 | ``` 67 | 68 | `production` is set on deployment, `dev` is set whenever running locally, and `test` is set when tests are running. These environments are automatically set based on the task within `app.js`. 69 | 70 | ## Running API Locally 71 | 72 | To run, `yarn` to install dependencies, and then `yarn start` to start the development server. It will be located on `http://localhost:9000`. 73 | 74 | Currently there is a single endpoint at `/api/home` which returns the text for home. 75 | 76 | ## Linting, Formatting Testing 77 | 78 | The linting, formatting, and testing frameworks have been provided. 79 | 80 | Linting uses ESLint linter, with the Airbnb style guide. Customization for specific rules can be entered in `.eslintrc.json`. To run linting, `yarn lint` within the `api` directory. 81 | 82 | Formatting uses `prettier` and can be customized in `.prettierrc`. Run formatting with `yarn format` in the `api` directory. 83 | 84 | Backend testing uses Jest. There is a default configuration for Jest set up. Tests can be found in `api/test`. Simply run `yarn test` to run the tests. 85 | 86 | # Continuous Integration 87 | 88 | Continuous integration is just the practice of automatically running tests for every single code push, which can be automated by services such as GitHub Actions. To edit these, modify the `yaml` configuration files in `.github/workflows`. 89 | 90 | Currently, provided jobs include: 91 | 92 | - `client` `lint` : Ensuring `yarn lint` within the `client/` directory 93 | - `client` `test` : Ensuring `yarn test` within the `client/` directory 94 | - `client` `format` : Ensuring `yarn format:check` within the `client/` directory 95 | - `client` `build` : Ensuring `yarn build` within the `client/` directory 96 | - `api` `lint` : Ensuring `yarn lint` within the `api/` directory 97 | - `api` `test` : Ensuring `yarn test` within the `api/` directory 98 | - `api` `format` : Ensuring `yarn format:check` within the `api/` directory 99 | 100 | If you have set up Cypress in the dashboard and gotten your project id, add it to the CircleCI environment variables and add the `record: true` flag. 101 | 102 | By default for `client` `test`, parallelism is turned off. To enable parallelism, add the flag 103 | 104 | - `parallel`: `true` 105 | --------------------------------------------------------------------------------