27
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
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.
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
:
import { combineReducers } from "redux";
import posts from "./posts";
export default combineReducers({ posts });
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:
27