Skip to content

Instantly share code, notes, and snippets.

@mfrancois3k
Forked from LevPewPew/mern-e2e-deploy.md
Last active August 1, 2022 05:36
Show Gist options
  • Save mfrancois3k/0e04f434e5f9a53b5f8b0c6b3e214bed to your computer and use it in GitHub Desktop.
Save mfrancois3k/0e04f434e5f9a53b5f8b0c6b3e214bed to your computer and use it in GitHub Desktop.
fully detailed process of getting a mern app deployment

full proper step by step deploy MERN

using "cool-fruit-app" and a "banana" model for the example

initial setup

1. create a top level folder

mkdir cool-fruit-app cd cool-fruit-app

2. create a frontend repo with the basics

mkdir cool-fruit-app-frontend cd cool-fruit-app-frontend yarn init yarn add react npm install [email protected] [email protected] yarn add react-scripts touch .gitignore git init code .

3. add the following to the .gitignore

# dependencies
/node_modules

# production
/build
/.netlify

# logging
*.log

# misc
.DS_Store

4. create a backend repo with the basics

mkdir cool-fruit-app-backend cd cool-fruit-app-backend npm init touch .gitignore git init code .

5. add the following to the .gitignore

# dependencies
/node_modules

# production
/build

# environment variables
.env

# logging
*.log

# misc
.DS_Store

6. connect both repos to github remote, add, commit, push

7. confirm packages and change scripts to get frontend package.json looking like so (with latest versions)

{
  "name": "cool-fruit-app-frontend",
  "version": "1.0.0",
  "description": "cool-fruit-app app frontend in react",
  "main": "index.js",
  "author": "Levente Toth",
  "license": "MIT",
  "dependencies": {
    "axios": "^0.19.1",
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-scripts": "^3.3.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "deploy": "yarn build && netlify deploy --prod --dir=build"
  }
}

8. confirm packages and change scripts to get backend package.json looking like so (with latest versions)

{
  "name": "cool-fruit-app-backend",
  "version": "1.0.0",
  "description": "banana app backend in express, connecting to MongoDB Atlas",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js",
    "dev": "nodemon index.js"
  },
  "author": "Levente Toth",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "mongoose": "^5.8.6",
    "morgan": "^1.9.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.2"
  }
}

basic functionality of backend repo, running locally

1. create a index.js file in cool-fruit-app-backend folder with the following code

const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
const morgan = require("morgan");
require("dotenv").config();

const app = express();

// this short circuit is here for using the deployed env vairiable of heroku, or 5000 if running locally
const PORT = process.env.PORT || 5000;

const DB_URL = process.env.DB_URL;
const dbConfig = { useNewUrlParser: true, useUnifiedTopology: true };

// create a list for the CORS whitelist, do this so we can add the deployed site later
const whitelist = ["http://localhost:3000"];
const corsOptions = {
  origin: function (origin, callback) {
    if (whitelist.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error("Not allowed by CORS"));
    }
  },
};

mongoose.connect(DB_URL, dbConfig, (err) => {
  if (err) {
    console.log("MongooseDB Connection Error");
  } else {
    console.log("Connected to MongooseDB");
  }
});

// middlewares:
// this is used for better logging in server terminal
app.use(morgan("dev"));
// this is needed for JSON to parse properly by post/put HTTP actions
app.use(express.json());
// this allows external addresses to access the website, generally keep it restricted to the front end apps address (wether that is the local address or deployed one while still developing) IMPORTANT: this cors policy middlware must be called before the routes middleware, otherwise it won't be applied to those routes cause CORS bugs
app.use(cors(corsOptions));
// connect with index.js file in routes directory
app.use(require("./routes/index"));

app.listen(PORT, () => console.log(`Listening on port ${PORT}`));

2. create the model file banana.js, in here we make a schema/validation

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const bananaSchema = new Schema(
  {
    title: String,
    description: String,
  },
  {
    timestamps: true,
  }
);

const Banana = mongoose.model("Banana", bananaSchema);

module.exports = Banana;

3. create a controller file banana-controller.js, include a simple get and post actions to start, to prove database working as expected

// import the model these actions control
const Banana = require("../models/banana");

const index = async (req, res) => {
  try {
    const bananas = await Banana.find();
    res.send(bananas);
  } catch (err) {
    res.status(404).send(err);
  }
};

const create = async (req, res) => {
  const { title, description } = req.body;

  try {
    const newBanana = await Banana.create({ title, description });
    res.send(newBanana);
  } catch (err) {
    res.status(400).send(err);
  }
};

module.exports = {
  index,
  create,
};

4. create routes

  • make a routes folder
  • make a routes index.js to collate your routes
const express = require("express");
const router = express.Router();

const index = (req, res) => {
  res.send("hello world");
};

router.get("/", index);

// define routes here, this will mean anything in the banana-routes.js file (yet to be created) will assume that /banana will prepend the route path defined in banana-routes.js
router.use("/bananas", require("./bananas-routes"));

module.exports = router;
  • make a routes file banana-routes.js for your first group of routes
const express = require("express");
const router = express.Router();
// import actions from controller
const { index, create } = require("../controllers/bananas-controller");

// the full address will be localhost:5000/bananas to reach these two routes/actions, as defined by the router.use call in the routes/index.js file
router.get("/", index);
router.post("/", create);

module.exports = router;

5. create a .env file for environment variables, here we will store the MongoDB address, for now we will just use a local database, later in heroku we will define the environment variable in the heroku dashboard, and use a Mongo Atlas address

DB_URL=mongodb://localhost:27017/cool-fruit-app

6. run the server using nodemon with the npm run dev command

7. use postman to test the home, index and create routes

  • run the express server (using nodemon via yarn dev or npm run dev)
  • header must include key: Content-Type and value: application/json
  • POST to localhost:5000/bananas
{
  "title": "banana split sundae",
  "description": "basic banana dessert"
}
  • GET to localhost:5000/bananas response to expect:
[
  {
    "_id": "5e180c805e93b25201a94234",
    "title": "banana split sundae",
    "description": "basic banana dessert",
    "createdAt": "2020-01-10T05:32:48.127Z",
    "updatedAt": "2020-01-10T05:32:48.127Z",
    "__v": 0
  }
]

8. push to remote git repo

basic functionality of frontend, running locally

1. create a index.html file in public folder with the following

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>MERN Boilerplate by Levente Toth</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

2. create a index.js file in src folder with the following

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App.jsx";

ReactDOM.render(<App />, document.getElementById("root"));

3. create a App.jsx file in src/components folder with the following

4. create a .env.development file for environment variables, here we will store the address of our express server, for now we will just use a local database, later in when we deploy the frontend to netlify, and have our backend deployed to heroku, we will use a .env file that contains the heroku deployed address. make sure the port matches the one currently being used to run the express server

REACT_APP_BACKEND_URL=http://localhost:5000/

5. run the react app using yarn start, press Y when it asks about detecting target browsers. make sure react is running on port 3000 since that is the address we unblocked in cors in the express server

6. push to remote git repo

deploy the backend

1. install the heroku CLI (if you don't have it already) and login (make sure you have a heroku account) so that we can use heroku commands in our terminal

2. create heroku repo and push the backend to it

  • heroku create
  • git push heroku master

3. add environement variables needed by heroku

  • go to your mongoDB atlas account and get the link of the cluster you have created (create one if you havn't already, also make sure there is a user with read/write access to any database you create)
    • [connect]
    • [connect your application]
  • copy paste this link somewhere, and swap out the username and password, as well as the database "test" to be the name of the database you want your app to be using (it doesn't have to already exist, mongoDB will create one automatically if it is not.
  • go to the heroku website
  • enter the dashboard for the app you just created
  • settings
  • reveal config vars
  • enter DB_URL and the link you just copied and modified into the Key and Value fields

4. use postman to test the home, index and create routes

  • do exactly the same as earlier, but replace the localhost address with the heroku deployed website URL as given in the heroku web dashboard, expect the same results

deploy the frontend

1. install the netlify CLI (if you don't have it already) and login (make sure you have a netlify account) so that we can use netlify commands in our terminal

2. in the .env file, add the deployed backend url

REACT_APP_BACKEND_URL=https://cool-fruit-app.herokuapp.com

3. create a file in the root called netlify.toml this is just some extra config needed by netlify for a build and deploy to work correctly

[[redirects]]
  from = "/*"
  to= "/index.html"
  status = 200

4. git add, commit, push (just to save, don't actually need to do this to deploy)

5. deploy to netlify

  • yarn deploy

6. in the backend, add the URL created for the frontend to the cors policy in index.js

...

const whitelist = ['http://localhost:3000', 'https://cool-fruit-app.netlify.com'];

...

7. load up the front page deployed netlify site, see if it is populated with data

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment