21
loading...
This website collects cookies to deliver better user experience
Node.js is a popular server-side framework that allows us to run JavaScript code on a web server.
Express is a Node.js web application framework that makes Node application development simpler and faster.
MongoDB is a NoSQL database that stores data persistently in the form of collections and documents.
React is a JavaScript frontend library for creating user interfaces.
npm init -y
npm install cors express dotenv mongoose nodemon body-parser
Import express module.
Import bodyParser module
Import mongoose module
Import CORS module
Use express() to start our app.
//server.js
import express from "express";
import bodyParser from "body-parser";
import mongoose from "mongoose";
import cors from "cors";
const app = express();
//server.js
import express from "express";
import bodyParser from "body-parser";
import mongoose from "mongoose";
import cors from "cors";
const app = express();
app.use(bodyParser.json({ limit: '50mb', extended: true }))
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }))
app.use(cors());
//server.js
import express from "express";
import bodyParser from "body-parser";
import mongoose from "mongoose";
import cors from "cors";
import dotenv from "dotenv";
dotenv.config();
const app = express();
app.use(bodyParser.json({ limit: "50mb", extended: true }));
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
app.use(cors());
const DB_CONNECTION = process.env.DATABASE_URL;
const PORT = process.env.PORT || 6000;
mongoose
.connect(DB_CONNECTION, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() =>
app.listen(PORT, () =>
console.log(`Server is running @ : http://localhost:${PORT}`)
)
)
.catch((error) => console.error(error));
PORT=4000
DATABASE_URL=mongodb+srv://admin:<password>@cluster0.ddtsa.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
// routes/blogPosts.routes.js
import express from "express";
const router = express.Router();
router.get("/", (req, res) => {
res.send("Awesome MERN BLOG");
});
export default router;
// server.js
import express from "express";
import bodyParser from "body-parser";
import mongoose from "mongoose";
import cors from "cors";
import dotenv from "dotenv";
import blogPosts from "./routes/blogPosts.js";
dotenv.config();
const app = express();
app.use(bodyParser.json({ limit: "50mb", extended: true }));
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
app.use(cors());
// remember to add this after cors
app.use("/api/blogs", blogPosts);
const DB_CONNECTION = process.env.DATABASE_URL;
const PORT = process.env.PORT || 6000;
mongoose
.connect(DB_CONNECTION, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() =>
app.listen(PORT, () =>
console.log(`Server is running at: http://localhost:${PORT}`)
)
)
.catch((error) => console.log(error));
//routes/blogPosts.routes.js
import express from 'express';
import { getAllBlogPosts } from '../controllers/blogPosts.controller.js';
const router = express.Router();
router.get('/', getAllBlogPosts);
export default router;
//controllers/blogPosts.controller.js
import express from "express";
import mongoose from "mongoose";
const router = express.Router();
export const getAllBlogPosts = (req, res) => {
res.send("Awesome MERN BLOG");
};
export default router;
// models/blogs.js
import mongoose from "mongoose";
const blogSchema = mongoose.Schema({
title: String,
description: String,
tags: [String],
fileUpload: String,
upvote: {
type: Number,
default: 0,
},
creator: String,
createdAt: {
type: Date,
default: new Date(),
},
});
var BlogPost = mongoose.model("BlogArticle", blogSchema);
export default BlogPost;
// routes/blogPosts.routes.js
import express from "express";
import {
getAllBlogPosts,
addBlogPost,
getSinglePost,
updateSingleBlogPost,
removeSingleBlogPost,
likeBlogPost,
} from "../controllers/blogPosts.controller.js";
const router = express.Router();
router.get("/", getAllBlogPosts);
router.post("/", addBlogPost);
router.get("/:id", getSinglePost);
router.patch("/:id", updateSingleBlogPost);
router.delete("/:id", removeSingleBlogPost);
router.patch("/:id/likeedBlogPost", likeBlogPost);
export default router;
export const getAllBlogPosts = async (req, res) => {
try {
const blogPosts = await BlogPost.find();
res.status(200).json(blogPosts);
} catch (error) {
res.status(404).json({ message: error.message });
}
};
export const addBlogPost = async (req, res) => {
const { title, description, fileUpload, creator, tags } = req.body;
const createNewPost = new BlogPost({
title,
description,
fileUpload,
creator,
tags,
});
try {
await createNewPost.save();
res.status(201).json(createNewPost);
} catch (error) {
res.status(409).json({ message: error.message });
}
};
export const getSinglePost = async (req, res) => {
const { id } = req.params;
try {
const singlepost = await BlogPost.findById(id);
res.status(200).json(singlepost);
} catch (error) {
res.status(404).json({ message: error.message });
}
};
export const updateSingleBlogPost = async (req, res) => {
const { id } = req.params;
const { title, description, creator, fileUpload, tags } = req.body;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send(`post ${id} not found`);
const updatedBlogPost = {
creator,
title,
description,
tags,
fileUpload,
_id: id,
};
await BlogPost.findByIdAndUpdate(id, updatedBlogPost, { new: true });
res.json(updatedBlogPost);
};
export const removeSingleBlogPost = (req, res) => {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send(`post ${id} not found`);
await BlogPost.findByIdAndRemove(id);
res.json({ message: "Successfully deleted" });
};
export const likeBlogPost = async (req, res) => {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send(`No post with id: ${id}`);
const post = await BlogPost.findById(id);
const updatedBlogPost = await BlogPost.findByIdAndUpdate(
id,
{ upvote: post.upvote + 1 },
{ new: true }
);
res.json(updatedBlogPost);
};
// blogPosts.controller.js
import express from "express";
import mongoose from "mongoose";
import BlogPost from "../models/blogs.js";
const router = express.Router();
export const getAllBlogPosts = async (req, res) => {
try {
const blogPosts = await BlogPost.find();
res.status(200).json(blogPosts);
} catch (error) {
res.status(404).json({ message: error.message });
}
};
export const addBlogPost = async (req, res) => {
const { title, description, fileUpload, creator, tags } = req.body;
const createNewPost = new BlogPost({
title,
description,
fileUpload,
creator,
tags,
});
try {
await createNewPost.save();
res.status(201).json(createNewPost);
} catch (error) {
res.status(409).json({ message: error.message });
}
};
export const getSinglePost = async (req, res) => {
const { id } = req.params;
try {
const singlepost = await BlogPost.findById(id);
res.status(200).json(singlepost);
} catch (error) {
res.status(404).json({ message: error.message });
}
};
export const updateSingleBlogPost = async (req, res) => {
const { id } = req.params;
const { title, description, creator, fileUpload, tags } = req.body;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send(`post ${id} not found`);
const updatedBlogPost = {
creator,
title,
description,
tags,
fileUpload,
_id: id,
};
await BlogPost.findByIdAndUpdate(id, updatedBlogPost, { new: true });
res.json(updatedBlogPost);
};
export const likeBlogPost = async (req, res) => {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send(`No post with id: ${id}`);
const post = await BlogPost.findById(id);
const updatedBlogPost = await BlogPost.findByIdAndUpdate(
id,
{ upvote: post.upvote + 1 },
{ new: true }
);
res.json(updatedBlogPost);
};
export const removeSingleBlogPost = async (req, res) => {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send(`post ${id} not found`);
await BlogPost.findByIdAndRemove(id);
res.json({ message: "Successfully deleted" });
};
export default router;
npm install @material-ui/core axios moment react-file-base64 redux react-redux redux-thunk
// src/styles/app.styles.js
import { makeStyles } from "@material-ui/core/styles";
export default makeStyles(() => ({
navigationBar: {
borderRadius: 10,
margin: "6px 0px",
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
},
title: {
color: "#8661d1",
fontFamily: "Poppins",
fontStyle: "bold",
},
image: {
marginRight: "25px",
},
}));
//App.js
import React, { useState, useEffect } from "react";
import "./App.css";
import { Container, AppBar, Typography, Grow, Grid } from "@material-ui/core";
import blogLogo from "./Assets/blogLogo.gif";
import BlogPosts from "./components/BlogPosts";
import BlogPostsForm from "./components/BlogPostsForm";
import useStyles from "./styles/app.styles.js";
function App() {
const appStyles = useStyles();
return (
<div className="App">
<Container maxWidth="xl">
<AppBar
className={appStyles.navigationBar}
position="static"
color="inherit"
>
<img
className={appStyles.image}
src={blogLogo}
alt="icon"
height="100"
/>
<Typography className={appStyles.title} variant="h4" align="center">
Mern awesome blog
</Typography>
</AppBar>
<Grow in>
<Container>
<Grid
container
justify="space-between"
alignItems="stretch"
spacing={2}
>
<Grid item xs={12} sm={7}>
<BlogPostsForm />
</Grid>
<Grid item xs={12} sm={4}>
<BlogPosts />
</Grid>
</Grid>
</Container>
</Grow>
</Container>
</div>
);
}
export default App;
import axios from "axios";
const url = "http://localhost:4000/api/blogs";
export const fetchAllBlogPosts = () => axios.get(url);
//index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { reducers } from "./reducers/blogPosts.js";
import App from "./App";
import "./index.css";
const store = createStore(reducers, compose(applyMiddleware(thunk)));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
// reducers/index.js
import { combineReducers } from "redux";
import blogPosts from "./blogPosts";
export const reducers = combineReducers({ blogPosts });
// App.js
import React, { useEffect } from "react";
import "./App.css";
import { Container, AppBar, Typography, Grow, Grid } from "@material-ui/core";
import blogLogo from "./Assets/blogLogo.gif";
import Blogs from "./components/Blogs";
import BlogPostsForm from "./components/BlogPostsForm";
import useStyles from "./styles/app.styles.js";
import { useDispatch } from "react-redux";
import { fetchAllBlogPosts } from "./actions/blogPosts";
function App() {
const dispatch = useDispatch();
const appStyles = useStyles();
useEffect(() => {
dispatch(fetchAllBlogPosts());
}, [dispatch]);
return (
<div className="App">
<Container maxWidth="xl">
<AppBar
className={appStyles.navigationBar}
position="static"
color="inherit"
>
<img
className={appStyles.image}
src={blogLogo}
alt="icon"
height="100"
/>
<Typography className={appStyles.title} variant="h2" align="center">
Mern awesome blog
</Typography>
</AppBar>
<Grow in>
<Grid
container
justifyContent="space-between"
alignItems="stretch"
spacing={2}
>
<Grid item xs={12} sm={3}>
<BlogPostsForm />
</Grid>
<Grid item xs={12} sm={9}>
<Blogs />
</Grid>
</Grid>
</Grow>
</Container>
</div>
);
}
export default App;
// actions/blogPosts.js
import * as api from "../api/api.js";
export const fetchAllBlogPosts = () => async (dispatch) => {
try {
const { data } = await api.fetchAllBlogPosts();
dispatch({ type: GET_ALL_BLOGS, payload: data });
} catch (error) {
console.log(error.message);
}
};
// reducers/blogPosts.js
export default (posts = [], action) => {
switch (action.type) {
case "GET_ALL_BLOGS":
return action.payload;
default:
return posts;
}
};
//components/Blogs
import React from "react";
import { Grid, CircularProgress } from "@material-ui/core";
import { useSelector } from "react-redux";
import BlogPosts from "../BlogPosts";
import useStyles from "./styles";
const Blogs = () => {
const posts = useSelector((state) => state.blogPosts);
const classes = useStyles();
console.log("this is post", posts);
return (
<>
<BlogPosts />
</>
);
};
export default Blogs;
// BlogPostsForm.js
import React, { useState, useEffect } from "react";
import { Paper, TextField, Typography, Button } from "@material-ui/core";
import { useDispatch, useSelector } from "react-redux";
import FileBase from "react-file-base64";
import useStyles from "./styles";
import { addBlogPosts, editBlogPosts } from "../../actions/blogPosts";
const BlogPostsForm = ({ blogPostId, setBlogPostId }) => {
const [blogInfo, setBlogInfo] = useState({
creator: "",
title: "",
description: "",
tags: "",
fileUpload: "",
});
const post = useSelector((state) =>
blogPostId
? state.posts.find((message) => message._id === blogPostId)
: null
);
const dispatch = useDispatch();
const blogPostsStyles = useStyles();
useEffect(() => {
if (post) setBlogInfo(post);
}, [post]);
const handleSubmit = async (e) => {
e.preventDefault();
if (blogPostId === 0) {
dispatch(addBlogPosts(blogInfo));
} else {
dispatch(editBlogPosts(blogInfo));
}
};
return (
<Paper className={blogPostsStyles.paper}>
<form
autoComplete="on"
noValidate
className={`${blogPostsStyles.root} ${blogPostsStyles.form}`}
onSubmit={handleSubmit}
>
<Typography variant="h5">
{blogPostId ? `Update "${post.title}"` : "✨ Create a blog ✨"}
</Typography>
<div className={blogPostsStyles.chooseFile}>
<Typography> 🖼️ Upload Blog Image</Typography>
<FileBase
type="file"
multiple={false}
onDone={({ base64 }) =>
setBlogInfo({ ...blogInfo, fileUpload: base64 })
}
/>
</div>
<TextField
name="title"
variant="outlined"
label="🔥 Blog Title"
fullWidth
value={blogInfo.title}
onChange={(e) => setBlogInfo({ ...blogInfo, title: e.target.value })}
/>
<TextField
name="description"
variant="outlined"
label="📙 Blog Description"
fullWidth
multiline
rows={7}
value={blogInfo.description}
onChange={(e) =>
setBlogInfo({ ...blogInfo, description: e.target.value })
}
/>
<TextField
name="creator"
variant="outlined"
label="✍️ Author name"
fullWidth
value={blogInfo.creator}
onChange={(e) =>
setBlogInfo({ ...blogInfo, creator: e.target.value })
}
/>
<Typography>Tags (5 max seperated by comma)</Typography>
<TextField
name="tags"
variant="outlined"
label="🏷️ Tags"
fullWidth
value={blogInfo.tags}
onChange={(e) =>
setBlogInfo({ ...blogInfo, tags: e.target.value.split(",") })
}
/>
<Button
className={blogPostsStyles.publishButton}
variant="contained"
color="secondary"
size="large"
type="submit"
>
Publish 📝
</Button>
</form>
</Paper>
);
};
export default BlogPostsForm;
// components/BlogPostsForm/styles.js
import { makeStyles } from "@material-ui/core/styles";
export default makeStyles((theme) => ({
root: {
"& .MuiTextField-root": {
margin: theme.spacing(1),
},
},
paper: {
padding: theme.spacing(5),
},
chooseFile: {
width: "95%",
margin: "10px 0",
},
publishButton: {
marginBottom: 10,
},
form: {
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
},
}));
// api/api.js
import axios from "axios";
const url = "http://localhost:4000/api/blogs";
export const fetchBlogPosts = () => axios.get(url);
export const addNewBlogPost = (newBlog) => axios.post(url, newBlog);
export const editSingleBlogPost = (id, editedBlogPost) =>
axios.patch(`${url}/${id}`, editedBlogPost);
// actions/blogPosts.js
import * as api from "../api/api.js";
export const fetchAllBlogPosts = () => async (dispatch) => {
try {
const { data } = await api.fetchBlogPosts();
dispatch({ type: "GET_ALL_BLOG_POST", payload: data });
} catch (error) {
console.log(error.message);
}
};
export const addBlogPosts = (post) => async (dispatch) => {
try {
const { data } = await api.addNewBlogPost(post);
dispatch({ type: "ADD_NEW_BLOG_POST", payload: data });
} catch (error) {
console.log(error.message);
}
};
export const editBlogPosts = (id, post) => async (dispatch) => {
try {
const { data } = await api.editSingleBlogPost(id, post);
dispatch({ type: "EDIT_SINGLE_BLOG_POST", payload: data });
} catch (error) {
console.log(error.message);
}
};
export default (posts = [], action) => {
switch (action.type) {
case "GET_ALL_BLOG_POST":
return action.payload;
case "ADD_NEW_BLOG_POST":
return [...posts, action.payload];
case "EDIT_SINGLE_BLOG_POST":
return posts.map((post) =>
post._id === action.payload._id ? action.payload : post
);
default:
return posts;
}
};
//App.js
import React, { useState, useEffect } from "react";
import "./App.css";
import { Container, AppBar, Typography, Grow, Grid } from "@material-ui/core";
import blogLogo from "./Assets/blogLogo.gif";
import Blogs from "./components/Blogs";
import BlogPostsForm from "./components/BlogPostsForm";
import useStyles from "./styles/app.styles.js";
import { useDispatch } from "react-redux";
import { fetchAllBlogPosts } from "./actions/blogPosts";
function App() {
const [blogPostId, setBlogPostId] = useState(0);
const dispatch = useDispatch();
const appStyles = useStyles();
useEffect(() => {
dispatch(fetchAllBlogPosts());
}, [blogPostId, dispatch]);
return (
<div className="App">
<Container maxWidth="xl">
<AppBar
className={appStyles.navigationBar}
position="static"
color="inherit"
>
<img
className={appStyles.image}
src={blogLogo}
alt="icon"
height="100"
/>
<Typography className={appStyles.title} variant="h2" align="center">
Mern awesome blog
</Typography>
</AppBar>
<Grow in>
<Grid
container
justifyContent="space-between"
alignItems="stretch"
spacing={2}
>
<Grid item xs={12} sm={3}>
<BlogPostsForm
blogPostId={blogPostId}
setBlogPostId={setBlogPostId}
/>
</Grid>
<Grid item xs={12} sm={9}>
<Blogs setBlogPostId={setBlogPostId} />
</Grid>
</Grid>
</Grow>
</Container>
</div>
);
}
export default App;
// components/Blogs.js
import React from "react";
import { Grid, CircularProgress } from "@material-ui/core";
import { useSelector } from "react-redux";
import BlogPosts from "../BlogPosts";
import useStyles from "./styles";
const Blogs = ({ setBlogPostId }) => {
const posts = useSelector((state) => state.posts);
const classes = useStyles();
console.log("this is post", posts);
return !posts.length ? (
<CircularProgress />
) : (
<Grid
className={classes.container}
container
alignItems="stretch"
spacing={4}
>
{posts.map((post) => (
<Grid key={post._id} item xs={12} sm={12}>
<BlogPosts post={post} setBlogPostId={setBlogPostId} />
</Grid>
))}
</Grid>
);
};
export default Blogs;
// components/BlogPosts.js
import React from "react";
import {
Typography,
CardMedia,
Button,
Card,
CardActions,
CardContent,
} from "@material-ui/core/";
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
import DeleteIcon from "@material-ui/icons/Delete";
import EditIcon from "@material-ui/icons/Edit";
import moment from "moment";
import { useDispatch } from "react-redux";
import blogImageLogo from "../../Assets/blogLogo.gif";
import { upvoteBlogPosts, removeBlogPosts } from "../../actions/blogPosts";
import useStyles from "./styles";
const BlogPosts = ({ post, setCurrentId }) => {
const dispatch = useDispatch();
const blogPostStyles = useStyles();
return (
<>
<Card className={blogPostStyles.blogContainer}>
<CardMedia
className={blogPostStyles.imageContainer}
image={post.fileUpload || blogImageLogo}
title={post.title}
/>{" "}
<div className={blogPostStyles.nameOverlay}>
<Typography variant="h6"> {post.creator} </Typography>{" "}
<Typography variant="body2">
{" "}
{moment(post.createdAt).fromNow()}{" "}
</Typography>{" "}
</div>{" "}
<div className={blogPostStyles.editOverlay}>
<Button
style={{
color: "white",
}}
size="small"
onClick={() => setCurrentId(post._id)}
>
<EditIcon fontSize="default" />
</Button>{" "}
</div>{" "}
<div className={blogPostStyles.tagSection}>
<Typography variant="body2" color="textSecondary" component="h2">
{" "}
{post.tags.map((tag) => `#${tag} `)}{" "}
</Typography>{" "}
</div>{" "}
<Typography
className={blogPostStyles.titleSection}
gutterBottom
variant="h5"
component="h2"
>
{post.title}{" "}
</Typography>{" "}
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">
{" "}
{post.description}{" "}
</Typography>{" "}
</CardContent>{" "}
<CardActions className={blogPostStyles.cardActions}>
<Button
size="small"
color="primary"
onClick={() => dispatch(upvoteBlogPosts(post._id))}
>
<ArrowUpwardIcon fontSize="small" /> {post.likeCount}{" "}
</Button>{" "}
<Button
size="small"
color="primary"
onClick={() => dispatch(removeBlogPosts(post._id))}
>
<DeleteIcon fontSize="big" />
</Button>{" "}
</CardActions>{" "}
</Card>{" "}
</>
);
};
export default BlogPosts;
// api/api.js
import axios from "axios";
const url = "http://localhost:4000/api/blogs";
export const fetchBlogPosts = () => axios.get(url);
export const addNewBlogPost = (newBlog) => axios.post(url, newBlog);
export const editSingleBlogPost = (id, editedBlogPost) =>
axios.patch(`${url}/${id}`, editedBlogPost);
export const upvoteSingleBlogPost = (id) =>
axios.patch(`${url}/${id}/likedBlogPost`);
export const removeBlogPost = (id) => axios.delete(`${url}/${id}`);
// reducers/blogPosts.js
export default (posts = [], action) => {
switch (action.type) {
case "GET_ALL_BLOG_POST":
return action.payload;
case "ADD_NEW_BLOG_POST":
return [...posts, action.payload];
case "EDIT_SINGLE_BLOG_POST":
return posts.map((post) =>
post._id === action.payload._id ? action.payload : post
);
case "UPVOTE_SINGLE_BLOG_POST":
return posts.map((post) =>
post._id === action.payload._id ? action.payload : post
);
case "DELETE_SINGLE_BLOG_POST":
return posts.filter((post) => post._id !== action.payload);
default:
return posts;
}
};