DigitalOcean MongoDB Hackathon - Memories Sharing App

somsubhra1

Somsubhra Das

Posted on June 30, 2021

DigitalOcean MongoDB Hackathon - Memories Sharing App

Hello everyone,
In this post we are going to learn how to create a memories sharing app on MERN stack. If you don't know what MERN stack is, it's a full stack technology which utilises the following:

  1. MongoDB - A NoSQL Database which is Document based
  2. ExpressJS - A microservice web framework.
  3. ReactJS - A frontend Library
  4. NodeJS - A runtime environment for JavaScript used on server side.

So let's get started and build our app.

We would be requiring:

  1. NodeJS
  2. Visual Studio Code or any other IDE of your choice.

First we need to create a project directory and initialize our project using the command:

npm init
Enter fullscreen mode Exit fullscreen mode

Server:

This will create a package.json file. Now we need to add a few external dependencies to our app. So to do that run the following command:

npm i body-parser cors express mongoose
Enter fullscreen mode Exit fullscreen mode

After installing these, its's time to create a Database instance on DigitalOcean Managed MongoDB platform.

Copy the connection string and add it to .env file with MongoURI as variable name.

Once we have created the .env to add a few scripts to our package.json file:

"scripts": {
    "start": "node server.js",
    "server": "nodemon server.js",
    "client": "npm start --prefix client",
    "client-install": "cd client && npm install",
    "dev": "concurrently -n server,client -c red,blue \"npm run server\" \"npm run client\"",
    "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client",
    "build": "npm run heroku-postbuild"
  },
Enter fullscreen mode Exit fullscreen mode

After doing so the package.json file should look like this:

{
  "name": "memories-app",
  "version": "1.0.0",
  "description": "A memories app made on MERN stack",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "start": "node server.js",
    "server": "nodemon server.js",
    "client": "npm start --prefix client",
    "client-install": "cd client && npm install",
    "dev": "concurrently -n server,client -c red,blue \"npm run server\" \"npm run client\"",
    "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client",
    "build": "npm run heroku-postbuild"
  },
  "keywords": [],
  "author": "Somsubhra Das",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "mongoose": "^5.10.13"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now Copy the following code to a new file server.js:

import express from "express";
import bodyParser from "body-parser";
import mongoose from "mongoose";
import cors from "cors";
import { config } from "dotenv";

import postRoutes from "./routes/post.js";

config();

const app = express();

app.use(bodyParser.json({ limit: "30mb", extended: true }));
app.use(bodyParser.urlencoded({ limit: "30mb", extended: true }));

app.use(cors());

const CONNECTION_URL = process.env.MongoURI;

mongoose
  .connect(CONNECTION_URL, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useFindAndModify: false,
  })
  .then(() => console.log("Successfully connected to MongoDB"))
  .catch((err) => console.log(`Error connecting to MongoDB ${err.message}`));

// app.get("/", (req, res) => res.send("Hello"));

app.use("/posts", postRoutes);

// Serve static assets if it's production environment
if (process.env.NODE_ENV === "production") {
  // Set static folder
  app.use(express.static("client/build"));
  app.get("*", (req, res) => {
    res.sendFile(path.resolve(__dirname, "client", "build", "index.html"));
  });
}

const PORT = process.env.PORT || 5000;

app.listen(PORT, () => console.log(`Server is running on port ${PORT}`));
Enter fullscreen mode Exit fullscreen mode

The above code configures the database connections, routes, port.

After that create a models folder and inside it create postMessage.js file. Copy the following to the file:

import mongoose from "mongoose";

const postSchema = mongoose.Schema({
  title: String,
  message: String,
  creator: String,
  tags: [String],
  selectedFile: String,
  likeCount: {
    type: Number,
    default: 0,
  },
  createdAt: {
    type: Date,
    default: new Date(),
  },
});

const PostMessage = mongoose.model("PostMessage", postSchema);

export default PostMessage;
Enter fullscreen mode Exit fullscreen mode

The above code creates a MongoDB Schema for our Database. It will be used for data fetching and data entry to database.

After that create a routes folder and inside it create a file named post.js and enter the following:

import express from "express";
import {
  getPosts,
  createPost,
  updatePost,
  deletePost,
  likePost,
} from "../controllers/posts.js";

const router = express.Router();

router.get("/", getPosts);
router.post("/", createPost);
router.patch("/:id", updatePost);
router.delete("/:id", deletePost);
router.patch("/:id/likePost", likePost);

export default router;
Enter fullscreen mode Exit fullscreen mode

This setups the routes for our CRUD APIs. Having done so, it's time to create the controllers to handle our requests and responses. So create a folder named controllers and inside it create posts.js:

import PostMessage from "../models/postMessage.js";
import mongoose from "mongoose";

export const getPosts = async (req, res) => {
  try {
    const postMessages = await PostMessage.find();
    // console.log(postMessages);

    return res.json(postMessages);
  } catch (error) {
    res.status(404).json({ message: error.message });
  }
};

export const createPost = async (req, res) => {
  const post = req.body;
  // console.log(req.body);
  const newPost = new PostMessage(post);

  try {
    await newPost.save();

    res.status(201).json(newPost);
  } catch (error) {
    res.status(409).json({ message: error.message });
  }
};

export const updatePost = async (req, res) => {
  const { id: _id } = req.params;
  const post = req.body;

  if (!mongoose.Types.ObjectId.isValid(_id)) {
    return res.status(404).send("No post with that id");
  }

  const updatedPost = await PostMessage.findByIdAndUpdate(_id, post, {
    new: true,
  });

  return res.json(updatedPost);
};

export const deletePost = async (req, res) => {
  const { id: _id } = req.params;

  if (!mongoose.Types.ObjectId.isValid(_id)) {
    return res.status(404).send("No post with that id");
  }

  await PostMessage.findByIdAndRemove(_id);

  return res.json({ message: "Post deleted successfully" });
};

export const likePost = async (req, res) => {
  const { id } = req.params;

  if (!mongoose.Types.ObjectId.isValid(id)) {
    return res.status(404).send("No post with that id");
  }

  const updatedPost = await PostMessage.findByIdAndUpdate(
    id,
    {
      $inc: { likeCount: 1 },
    },
    { new: true }
  );
  return res.json(updatedPost);
};
Enter fullscreen mode Exit fullscreen mode

This code handles the request and responses for the Posts CRUD API server.

Having done so, we have successfully setup our server side and the APIs.

Client:

Next we would be handling our client side setup. So run the following command:

npx create-react-app client
Enter fullscreen mode Exit fullscreen mode

This will create a directory named client with all the frontend code. Change directory to client and run:

npm i @material-ui/core @material-ui/icons axios moment react-redux redux redux-thunk
Enter fullscreen mode Exit fullscreen mode

Now we need to setup actions. Create actions folder and inside it posts.js and enter the following code:

import {
  CREATE,
  UPDATE,
  DELETE,
  LIKE,
  FETCH_ALL,
} from "../constants/actionTypes";
import * as api from "../api";

// Action Creators
export const getPosts = () => async (dispatch) => {
  try {
    const { data } = await api.fetchPosts();
    // const action = { type: "FETCH_ALL", payload: [] };
    dispatch({ type: FETCH_ALL, payload: data });
  } catch (error) {
    console.log(error.message);
  }
};

export const createPost = (post) => async (dispatch) => {
  try {
    const { data } = await api.createPost(post);

    dispatch({ type: CREATE, payload: data });
  } catch (error) {
    console.log(error);
  }
};

export const updatePost = (id, post) => async (dispatch) => {
  try {
    const { data } = await api.updatePost(id, post);
    dispatch({ type: UPDATE, payload: data });
  } catch (error) {
    console.log(error.message);
  }
};

export const deletePost = (id) => async (dispatch) => {
  try {
    await api.deletePost(id);
    dispatch({ type: DELETE, payload: id });
  } catch (error) {
    console.log(error);
  }
};

export const likePost = (id) => async (dispatch) => {
  try {
    const { data } = await api.likePost(id);
    dispatch({ type: LIKE, payload: data });
  } catch (error) {
    console.log(error.message);
  }
};
Enter fullscreen mode Exit fullscreen mode

Now it's time to create the API calls. Create api folder and inside it index.js:

import axios from "axios";

const url = "/posts";

export const fetchPosts = () => axios.get(url);

export const createPost = (newPost) => axios.post(url, newPost);

export const updatePost = (id, updatedPost) =>
  axios.patch(`${url}/${id}`, updatedPost);

export const deletePost = (id) => axios.delete(`${url}/${id}`);

export const likePost = (id) => axios.patch(`${url}/${id}/likePost`);
Enter fullscreen mode Exit fullscreen mode

Create reducers folder and inside it create index.js & posts.js:

index.js
import { combineReducers } from "redux";

import posts from "./posts";

export default combineReducers({ posts });
Enter fullscreen mode Exit fullscreen mode
posts.js
import {
  CREATE,
  FETCH_ALL,
  DELETE,
  LIKE,
  UPDATE,
} from "../constants/actionTypes";

export default (posts = [], action) => {
  switch (action.type) {
    case FETCH_ALL:
      return action.payload;
    case CREATE:
      return [...posts, action.payload];

    case UPDATE:
    case LIKE:
      return posts.map((post) =>
        post._id === action.payload._id ? action.payload : post
      );

    case DELETE:
      return posts.filter((post) => post._id !== action.payload);

    default:
      return posts;
  }
};
Enter fullscreen mode Exit fullscreen mode

Create constants folder and inside it actionTypes.js:

export const CREATE = "CREATE";
export const UPDATE = "UPDATE";
export const DELETE = "DELETE";
export const FETCH_ALL = "FETCH_ALL";
export const LIKE = "LIKE";
Enter fullscreen mode Exit fullscreen mode

Now go to src/index.js and edit the contents to make the file resemble the following:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";

import reducers from "./reducers";

import "./index.css";

const store = createStore(reducers, compose(applyMiddleware(thunk)));

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);
Enter fullscreen mode Exit fullscreen mode

Now visit here and copy the components and images folders to your src folder of your project.

Also copy app.js, index.css, style.js to your project directory as well.

Finally run:

npm run dev
Enter fullscreen mode Exit fullscreen mode

The App should look like this:

Memories App

I hope you liked how this memories app was made. Please check out the following links:

  1. Demo
  2. GitHub Repo
💖 💪 🙅 🚩
somsubhra1
Somsubhra Das

Posted on June 30, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related