├── 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 | You need to enable JavaScript to run this app.
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 |
3 |
4 | MERN Template
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
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 |
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 |
--------------------------------------------------------------------------------