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:
  • MongoDB - A NoSQL Database which is Document based
  • ExpressJS - A microservice web framework.
  • ReactJS - A frontend Library
  • NodeJS - A runtime environment for JavaScript used on server side.
  • So let's get started and build our app.
    We would be requiring:
  • NodeJS
  • 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
    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
    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"
      },
    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"
      }
    }
    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}`));
    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;
    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;
    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);
    };
    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
    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
    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);
      }
    };
    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`);
    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 });
    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;
      }
    };
    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";
    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")
    );
    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
    The App should look like this:
    I hope you liked how this memories app was made. Please check out the following links:

    37

    This website collects cookies to deliver better user experience

    DigitalOcean MongoDB Hackathon - Memories Sharing App