Open In App

Community Forum Page using MEAN Stack

Last Updated : 01 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

Creating a community forum page using the MEAN (MongoDB, Express.js, Angular, Node.js) stack will clear the concepts of MEAN stack. It will help strengthen the understanding of CRUD operations. This article will discuss about the features of creating, updating, deleting a post, like / unlike feature, getting the complete post list, and retrieving the post for a specific user.

Output Preview: Let us have a look at how the final output will look like.

preview2

PROJECT PREVIEW IMAGE

Prerequisites:

Approach:

Backend:

  • Set up a new node.js project
  • Create server.js file to setup the server using express and use cors as a middleware
  • Create instance of app
  • Create controllers folder which will handle the methods for specific routes
  • Create routes folder and setup routes for all API requests
  • Create models folder to create the database schemas
  • Set up local Mongo DB database
  • Connect to the database in server.js file
  • Create a database and collections to store and retrieve the data from database
  • Three collection are created – Posts, Likes, User
  • Implement the core logic of post – creating, updating, deleting, retrieving the post and like/ unlike of post
  • Test the API endpoints using postman

Frontend:

  • Create a new Angular project
  • Create components for implementing various functionality and create HTML, CSS files for the same
  • Create a service to establish communication between frontend and backend
  • Create various routes along with components to be loaded in app.routes.js
  • Test the frontend application in browser

Steps to create the Backend:

Step 1: Create the folder for the project

mkdir community-forum

Step 2: Initialize the node.js project

npm init -y

Step 3: Install the dependencies

npm install mongoose express cors body-parser jsonwebtoken bcryptjs

The updated dependencies in package.json of backend will look like:

"dependencies": {
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.3",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.2.1",
"nodemon": "^3.1.0"
}

Project Structure (Backend):

Screenshot-2024-03-17-033202

PROJECT STRUCTURE IMAGE FOR BACKEND

Example: Create the required files as seen on the project structure and add the following codes.

Node
// authController.js

const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/User');

exports.register = async (req, res) => {
    try {
        const { username, email, password } = req.body;
        let user = await User.findOne({ email });
        if (user) {
            return res.status(400).json({
                message: "User Already Exist",
                success: false
            });
        }
        user = new User({
            username: username,
            email: email,
            password: password
        });
        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(password, salt);
        await user.save();
        const token = generateJwtToken(user.id);
        res.status(201).json({
            success: true,
            token: token,
            message: "User registered successfully"
        });
    }
    catch (error) {
        res.status(500).json({
            message: "Server error! New user registration failed",
            success: false
        });
    }
};

exports.login = async (req, res) => {
    try {
        const { email, password } = req.body;
        const user = await User.findOne({ email });
        if (!user) {
            return res.status(400).json({
                message: "Invalid credentials",
                success: false
            });
        }
        const isMatched = await bcrypt.compare(password, user.password);
        if (!isMatched) {
            return res.status(400).json({
                message: "Invalid credentials",
                success: false
            });
        }
        const token = generateJwtToken(user.id);
        res.status(200).json({
            success: true,
            message: "User logged in successfully",
            token: token
        });
    }
    catch (error) {
        return res.status(500).json({
            success: false,
            message: "Internal Server Error, Login unsuccessful"
        });
    }
};

function generateJwtToken(userID) {
    const payload = {
        user: {
            id: userID
        }
    };
    return jwt.sign(payload, 'jwtSecret', { expiresIn: 3600 });
}

exports.getUserDetailsFronUserId = async (req, res) => {
    try {
        const { id } = req.params;
        const user = await User.findById(id);
        return res.status(200).json(user);
    }
    catch (error) {
        res.status(500).json({
            success: false,
            message: error.message
        });
    }
};
Node
// likeController.js

const Like = require('../models/Like');
const Post = require('../models/Post');
const jwt = require('jsonwebtoken');
const secretKey = 'jwtSecret';
exports.toggleLike = async (req, res) => {
    const { postId } = req.params;
    const post = await Post.findById(postId);
    if (!post) {
        return res.status(404).json({ success: false, message: 'Post not found' });
    }
    let userId;
    try {
        if (req.headers['authorization'] && req.headers['authorization'].startsWith('Bearer ')) {
            jwt.verify(req.headers['authorization'].substring(7), secretKey, async (error, decodedToken) => {
                if (error) {
                    res.status(401).json({
                        success: false,
                        message: error.message
                    });
                }
                else {
                    userId = decodedToken.user.id;
                    const existingLike = await Like.findOne({ postId, userId });

                    if (existingLike) {
                        await existingLike.deleteOne();
                        const likedIndex = post.likes.indexOf(userId);
                        post.likes.splice(likedIndex, 1);
                        post.isLiked = false;
                        await post.save();
                        res.status(200).json({ success: true, message: 'Post unliked successfully' });
                    }
                    else {
                        const newLike = new Like({ postId, userId });
                        await newLike.save();
                        post.likes.push(userId);
                        post.isLiked = true;
                        await post.save();
                        res.status(200).json({ success: true, message: 'Post liked successfully' });
                    }
                }
            });
        }
    }
    catch (error) {
        res.status(500).json({ success: false, message: error.message });
    }
};
Node
// postController.js

const Post = require('../models/Post');
const jwt = require('jsonwebtoken');
const secretKey = 'jwtSecret';

exports.getAllPosts = async (req, res) => {
    try {
        const posts = await Post.find();
        res.status(200).json(posts);
    }
    catch (error) {
        return res.status(500).json({
            success: false,
            message: error.message
        });
    }
};

exports.getPostById = async (req, res) => {
    try {
        const { postId } = req.params;
        const post = await Post.findById(postId);
        res.status(200).json(post);
    }
    catch (error) {
        res.status(500).json({
            success: false,
            message: error.message
        });
    }
};

exports.getMyPosts = async (req, res) => {
    let userId;
    try {
        jwt.verify(req.headers['authorization']
            .substring(7), secretKey, (error, decodedToken) => {
                if (error) {
                    res.status(401).json({
                        success: false,
                        message: error.message
                    });
                }
                else {
                    userId = decodedToken.user.id;
                }
            });
        const posts = await Post.find({ author: userId });
        res.status(200).json(posts);
    }
    catch (error) {
        return res.status(500).json({
            success: false,
            message: error.message
        });
    }
};


exports.createPost = async (req, res) => {
    try {
        let post = {};
        const { title, content, category, attachment,
            createdAt, updatedAt } = req.body.post;
        if (req.headers['authorization']
            && req.headers['authorization']
                .startsWith('Bearer ')) {
            jwt.verify(req.headers['authorization']
                .substring(7), secretKey, (error, decodedToken) => {
                    if (error) {
                        res.status(401).json({
                            success: false,
                            message: error.message
                        });
                    }
                    else {
                        post = new Post({
                            title: title,
                            content: content,
                            author: decodedToken.user.id,
                            createdAt: createdAt,
                            updatedAt: updatedAt,
                            category: category,
                            likes: [],
                            attachment: attachment
                        });
                    }
                });
            await post.save();
            res.status(200).json(post);
        }
    }
    catch (error) {
        res.status(500).json({
            success: false,
            message: error.message
        });
    }
};

exports.updatePost = async (req, res) => {
    try {
        const postId = req.params.postId;
        const { title, content,
            category, attachment } = req.body;
        const post = await Post.findByIdAndUpdate(postId, {
            title: title,
            content: content,
            category: category,
            attachment: attachment
        }, { new: true });
        res.status(201).json(post);
    }
    catch (error) {
        res.status(500).json({
            success: false,
            message: error.message
        });
    }
};

exports.deletePost = async (req, res) => {
    try {
        const postId = req.params.postId;
        await Post.findByIdAndDelete(postId);
        res.status(200).json({
            success: true,
            message: "Post deleted successfully"
        });
    }
    catch (error) {
        res.status(500).json({
            success: true,
            message: error.message
        });
    }
};
Node
// likeModel.js

const mongoose = require('mongoose');

const likeSchema = new mongoose.Schema({
    postId: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Post',
        required: true
    },
    userId: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User',
        required: true
    }
});

const Like = mongoose.model('Like', likeSchema);

module.exports = Like;
Node
// postModel.js

const mongoose = require('mongoose');
const User = require('./User');

const postSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    content: {
        type: String,
        required: true
    },
    author: {
        type: mongoose.Schema.ObjectId,
        ref: User,
        required: true
    },
    createdAt: {
        type: Date,
        required: true
    },
    updatedAt: {
        type: Date,
        required: true
    },
    category: {
        type: String,
        default: ''
    },
    isLiked: {
        type: Boolean,
        default: false
    },
    likes: [
        {
            type: mongoose.Schema.ObjectId,
            ref: User
        }
    ],
    attachment: {
        type: String
    }
});

module.exports = mongoose.model('Post', postSchema);
Node
// userModel.js

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true
    },
    password: {
        type: String,
        required: true
    }
});

module.exports = mongoose.model('User', userSchema);
Node
// authRoutes.js

const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');

router.post('/register', authController.register);

router.post('/login', authController.login);

router.get('/:id', authController.getUserDetailsFronUserId);

module.exports = router;
Node
// likeRoutes.js

const express = require('express');
const router = express.Router();
const likeController = require('../controllers/likeController');

router.post('/:postId', likeController.toggleLike);

module.exports = router;
Node
// postRoutes.js

const express = require('express');
const router = express.Router();
const postController = require('../controllers/postController');

router.get("/getAllPosts", postController.getAllPosts);

router.get("/getPostById/:postId", postController.getPostById);

router.get("/getMyPosts", postController.getMyPosts);

router.post("/createPost", postController.createPost);

router.put("/updatePost/:postId", postController.updatePost);

router.delete("/deletePost/:postId", postController.deletePost);

module.exports = router;
Node
// server.js

const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const authRoutes = require('../backend/routes/authRoutes');
const postRoutes = require('../backend/routes/postRoutes');
const likeRoutes = require('../backend/routes/likeRoutes');

const app = express();
app.use(cors());
app.use(express.json());

mongoose.connect('mongodb://localhost:27017/community-forum', {
    family: 4
})
    .then(() => console.log("Mongo DB Connected"))
    .catch(error => console.log(error));

app.use('/api/auth', authRoutes);
app.use('/api/post', postRoutes);
app.use('/api/like', likeRoutes);

const PORT = 5000;
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));

To start the backend run the following command

nodemon server.js

Step 4: Install the angular CLI

npm install -g @angular/cli

Step 5: Create a new angular project

ng new frontend

Step 6: Create folder auth, like and post

mkdir auth
mkdir like
mkdir post

Step 7: Create post, add-post, mypost, sidebar components inside post folder and user component inside auth folder in angular

Syntax - ng generate component <component-name>
ng generate component post
ng generate component add-post
ng generate component mypost
ng generate component sidebar
ng generate component user

Step 8: Create the services for backend and frontend connection

ng generate service <service-name>
ng generate service auth
ng generate service post
ng generate service like
ng generate service data
ng generate service shared

The updated dependencies in package.json file of frontend will look like:

"dependencies": {
"@angular/animations": "^17.2.0",
"@angular/common": "^17.2.0",
"@angular/compiler": "^17.2.0",
"@angular/core": "^17.2.0",
"@angular/forms": "^17.2.0",
"@angular/platform-browser": "^17.2.0",
"@angular/platform-browser-dynamic": "^17.2.0",
"@angular/platform-server": "^17.2.0",
"@angular/router": "^17.2.0",
"@angular/ssr": "^17.2.3",
"@auth0/angular-jwt": "^5.2.0",
"express": "^4.18.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
}

Project Structure (Frontend):

frontend-folder-structure

PROJECT STRUCTURE GIF FOR FRONTEND

Example: Create the required files as seen in project structure and add the following codes

JavaScript
// post.component.ts

import { CommonModule } from '@angular/common';
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { PostService } from '../post.service';
import { DataService } from '../../data.service';
import { LikeService } from '../../like/like.service';
import { Router, RouterOutlet } from '@angular/router';

@Component({
    selector: 'app-post',
    standalone: true,
    imports: [FormsModule, CommonModule, RouterOutlet],
    templateUrl: './post.component.html',
    styleUrl: './post.component.css'
})
export class PostComponent implements OnInit {
    postList: any[] = [];
    displayedPostList: any[] = [];
    showUpdateForm: boolean = false;
    postById: any = {};
    myPosts: any[] = [];
    postUpdated: any = {};
    errorMessage: string = "";
    userDetails: any = {};
    loggedInUser: string | null = "";
    isLiked: boolean = false;
    username: any[] = [];
    email: any[] = [];
    posts: any[] = [];
    @Output() buttonClicked: EventEmitter<void> =
        new EventEmitter < void> ();
    likes: number = 0;
    constructor(private postService: PostService,
        private dataService: DataService, private 
        likeService: LikeService, private router: Router) { }
    ngOnInit(): void {
        this.getAllPosts();
    }

    getUserId(): string | null {
        if (typeof localStorage !== 'undefined') {
            const token = localStorage.getItem('token');
            if (token) {
                const tokenPayload = JSON.parse(atob(token.split('.')[1]));
                return tokenPayload.user.id;
            }
        }
        return null;
    }

    getAllPosts(): void {
        if (typeof localStorage !== 'undefined') {
            const token = localStorage.getItem('token');
            if (token) {
                this.postService.getAllPosts()
                    .subscribe((postList: any) => {
                        this.postList = postList;
                        this.displayedPostList = [...this.postList];
                    });
            }
        }
    }

    getUserDetails(userId: string): any {
        this.dataService.getUserDetails(userId)
            .subscribe((userDetails: any) => {
                this.userDetails = userDetails;
            });
    }

    getPostById(postId: string): void {
        if (typeof localStorage !== 'undefined') {
            const token = localStorage.getItem('token');
            if (token) {
                this.showUpdateForm = false;
                this.postService.getPostById(postId)
                    .subscribe((postById) => {
                        this.postById = postById;
                    });
            }
        }
    }

    getMyPosts(): void {
        if (typeof localStorage !== 'undefined') {
            const token = localStorage.getItem('token');
            if (token) {
                this.showUpdateForm = false;
                this.postService.getMyPosts(token)
                    .subscribe((myPosts) => {
                        this.myPosts = myPosts;
                        this.displayedPostList = [...this.myPosts];
                    });
            }
        }
    }

    populateUpdateForm(post: any) {
        this.loggedInUser = this.getUserId();
        if (this.loggedInUser === post.author) {
            this.postUpdated = { ...post };
            this.postUpdated.updatedAt = new Date;
            this.postUpdated.createdAt = this.postUpdated
                .createdAt.slice(0, 10);
            this.getUserDetails(post.author);
            this.showUpdateForm = true;
        }
    }

    updatePost(postId: string): void {
        if (typeof localStorage !== 'undefined') {
            const token = localStorage.getItem('token');
            if (token) {
                this.postService.updatePost(this.postUpdated, token)
                    .subscribe((postUpdated: any) => {
                        const index = this.displayedPostList.findIndex((p) =>
                            p._id === postId);
                        if (index !== -1) {
                            this.postList[index] = postUpdated;
                            this.displayedPostList[index] = postUpdated;
                            this.getAllPosts();
                            this.showUpdateForm = false;
                            this.router.navigate(["/getAllPosts"]);
                        }
                        this.cancelUpdate();
                    },
                        error => {
                            this.errorMessage = "Error in updating the post";
                        });
            }
        }
    }

    cancelUpdate(): void {
        this.showUpdateForm = false;
        this.postUpdated = {};
    }

    confirmDelete(postId: string): void {
        const confirmDelete = window.confirm
            ("Are you sure you want to delete the post");
        if (confirmDelete) {
            this.deletePost(postId);
        }
    }

    deletePost(post: any): void {
        const postId = post._id;
        if (typeof localStorage !== 'undefined') {
            const token = localStorage.getItem('token');
            if (token) {
                this.postService.deletePost(postId, token)
                    .subscribe(() => {
                        this.postList = this.postList.filter((post: any)
                            => post._id !== postId);
                        this.displayedPostList = [...this.postList];
                    },
                        error => {
                            this.errorMessage = "Error in deleting the post";
                        }
                    );
            }
        }
    }

    toggleLike(postId: string) {
        if (typeof localStorage !== "undefined") {
            const token = localStorage.getItem('token');
            if (token) {
                const postIndex = this.displayedPostList.findIndex
                    (post => post._id === postId);
                if (postIndex !== -1) {
                    const post = this.displayedPostList[postIndex];
                    this.likeService.toggleLike(postId, token)
                        .subscribe((response) => {
                            if (response.success) {
                                post.isLiked = !post.isLiked;
                                if (post.isLiked) {
                                    post.likes.push('');
                                } else {
                                    post.likes.pop();
                                }
                                this.displayedPostList[postIndex] = post;
                            } else {
                                console.error(response.message);
                            }
                        });
                }
            }
        }
    }
}
JavaScript
// mypost.component.ts

import { Component, OnInit } from '@angular/core';
import { PostService } from '../post.service';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';

@Component({
    selector: 'app-mypost',
    standalone: true,
    imports: [FormsModule, CommonModule],
    templateUrl: './mypost.component.html',
    styleUrl: './mypost.component.css'
})
export class MypostComponent implements OnInit {
    myPosts: any[] = [];
    displayedPostList: any[] = [];
    constructor(private postService: PostService) { }
    ngOnInit(): void {
        this.getMyPosts();
    }
    getMyPosts(): void {
        if (typeof localStorage !== 'undefined') {
            const token = localStorage.getItem('token');
            if (token) {
                this.postService.getMyPosts(token).subscribe((myPosts) => {
                    this.myPosts = myPosts;
                    this.displayedPostList = [...this.myPosts];
                });
            }
        }
    }
}
JavaScript
// add-post.component.ts

import { Component, OnInit } from '@angular/core';
import { PostService } from '../post.service';
import { Router } from '@angular/router';
import { DataService } from '../../data.service';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';

@Component({
    selector: 'app-add-post',
    standalone: true,
    imports: [FormsModule, CommonModule],
    templateUrl: './add-post.component.html',
    styleUrl: './add-post.component.css'
})
export class AddPostComponent implements OnInit {
    postCreated: any = {
        createdAt: new Date,
        updatedAt: new Date
    };
    userDetails: any = {};
    userId: any = "";
    constructor(private postService: PostService,
        private dataService: DataService, private router: Router) { }
    ngOnInit(): void {
        this.userId = this.getUserId();
        if (this.userId) {
            this.dataService.getUserDetails(this.userId)
                .subscribe((userDetails) => {
                    this.userDetails = userDetails;
                });
        }
    }

    getUserId(): string | null {
        if (typeof localStorage !== 'undefined') {
            const token = localStorage.getItem('token');
            if (token) {
                const tokenPayload = JSON.parse(atob(token.split('.')[1]));
                return tokenPayload.user.id;
            }
        }
        return null;
    }
    createPost(): void {
        if (typeof localStorage !== 'undefined') {
            const token = localStorage.getItem('token');
            if (token) {
                this.postService.createPost(this.postCreated, token)
                    .subscribe((postCreated: any) => {
                        this.postCreated = postCreated;
                        this.closeAddForm();
                        this.router.navigate(["/getAllPosts"]);
                    });
            }
        }
    }

    closeAddForm(): void {
        this.postCreated = {
            postTitle: "",
            postContent: "",
            postCategory: "",
            attachment: "",
            createdAt: this.postCreated.createdAt,
            updatedAt: this.postCreated.updatedAt
        };
    }
}
JavaScript
// sidebar.component.ts

import { Component, EventEmitter, OnInit, Output } from '@angular/core';

@Component({
    selector: 'app-sidebar',
    standalone: true,
    imports: [],
    templateUrl: './sidebar.component.html',
    styleUrl: './sidebar.component.css'
})
export class SidebarComponent implements OnInit {
    isLoggedIn: boolean = true;
    constructor() { }
    ngOnInit(): void {
        if (typeof localStorage !== 'undefined') {
            const token = localStorage.getItem('token');
            if (token) {
                this.isLoggedIn = true;
            }
        }
    }
    @Output() contentLoad = new EventEmitter < string > ();

    loadContent(page: string) {
        this.contentLoad.emit(page);
    }
}
JavaScript
// user.component.ts

import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AuthService } from '../auth.service';
import { Router } from '@angular/router';
import { SharedService } from '../../shared.service';
import { PostService } from '../../post/post.service';

@Component({
    selector: 'app-user',
    standalone: true,
    imports: [FormsModule, CommonModule],
    templateUrl: './user.component.html',
    styleUrl: './user.component.css'
})
export class UserComponent implements OnInit {
    username!: string;
    email!: string;
    password!: string;
    credentials: any = {};
    successMessage: string = '';
    errorMessage: string = '';
    loginActive: boolean = true;
    registerActive: boolean = false;
    constructor(private authService: AuthService,
        private router: Router, private sharedService: SharedService,
        private postService: PostService) { }
    ngOnInit(): void {
        this.sharedService.loginEvent.subscribe(() => {
            this.errorMessage = "";
            this.successMessage = "";
            this.loginActive = true;
            this.registerActive = false;
            this.email = "";
            this.password = "";
            this.username = "";
        });
        this.sharedService.registerEvent.subscribe(() => {
            this.errorMessage = "";
            this.successMessage = "";
            this.registerActive = true;
            this.loginActive = false;
            this.email = "";
            this.password = "";
            this.username = "";
        });
    }

    login(): void {
        const credentials = {
            email: this.email,
            password: this.password
        };
        this.authService.login(credentials).subscribe(
            (response: any) => {
                const token = response.token;
                localStorage.setItem("token", token);
                this.authService.setAuthenticationStatus(true);
                this.authService.emitLoggedInEvent();
                this.loginActive = false;
                this.registerActive = false;
                this.successMessage = response.message;
                this.router.navigate(["/getAllPosts"]);
            },
            error => {
                console.error('Error logging in:', error);
                this.errorMessage = "Login unsuccessfull !
         Please reload or try in incognito tab";
            }
        );
    }

    register(): void {
        const userData = {
            username: this.username,
            email: this.email,
            password: this.password
        };

        this.authService.register(userData).subscribe(
            (response: any) => {
                this.successMessage = response.message;
                this.loginActive = true;
                this.registerActive = false;
            },
            (error: any) => {
                console.error(error);
                this.errorMessage = "User not registered successfully";
            }
        );
    }

}
JavaScript
// app.component.ts

import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Router, RouterOutlet } from '@angular/router';
import { AuthService } from './auth/auth.service';
import { SharedService } from './shared.service';
import { PostComponent } from './post/post/post.component';
import { SidebarComponent } from './post/sidebar/sidebar.component';

@Component({
    selector: 'app-root',
    standalone: true,
    imports: [RouterOutlet, FormsModule, CommonModule,
        PostComponent, SidebarComponent],
    templateUrl: './app.component.html',
    styleUrl: './app.component.css'
})
export class AppComponent {
    title = 'GeeksForGeeks Community Forum';
    isLoggedIn: boolean = false;
    constructor(private router: Router, private authService: AuthService,
        private sharedService: SharedService) { }
    ngOnInit(): void {
        this.authService.loggedInEvent.subscribe((data: any) => {
            this.isLoggedIn = true;
        });
        if (typeof localStorage !== 'undefined' &&
            localStorage.getItem('token')) {
            this.isLoggedIn = true;
        }
    }

    login(): void {
        this.sharedService.triggerLoginEvent();
        this.router.navigate(["/"]);
    }

    register(): void {
        this.sharedService.triggerRegisterEvent();
        this.router.navigate(["/"]);
    }

    logout(): void {
        this.authService.setAuthenticationStatus(false);
        this.isLoggedIn = false;
        localStorage.removeItem('token');
        this.router.navigate(["/"]);
    }

    loadContent(page: string) {
        if (page === "allposts") {
            this.router.navigate(["/getAllPosts"]);
        }
        else if (page === "addpost") {
            this.router.navigate(["/createPost"]);
        }
        else if (page === "myposts") {
            this.router.navigate(["/getMyPosts"]);
        }
    }
}
JavaScript
// shared.service.ts

import { EventEmitter, Injectable } from '@angular/core';

@Injectable({
    providedIn: 'root'
})
export class SharedService {
    loginEvent: EventEmitter<void> = new EventEmitter < void> ();
    registerEvent: EventEmitter<void> = new EventEmitter < void> ();
    constructor() { }

    triggerLoginEvent(): void {
        this.loginEvent.emit();
    }

    triggerRegisterEvent(): void {
        this.registerEvent.emit();
    }
}
JavaScript
// data.service.ts

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
    providedIn: 'root'
})
export class DataService {
    private baseUrl = "http://localhost:5000";
    constructor(private http: HttpClient) { }

    getUserDetails(userId: string): Observable<any> {
        return this.http.get < any > (`${this.baseUrl}/api/auth/${userId}`);
    }

    getPostDetails(postId: string): Observable<any> {
        return this.http.get < any > (`${this.baseUrl}/api/posts/getPostById/${postId}`);
    }
}
JavaScript
// post.service.ts

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
    providedIn: 'root'
})
export class PostService {
    private baseUrl = 'http://localhost:5000/api/post';
    constructor(private httpClient: HttpClient) { }

    getAllPosts(): Observable<any> {
        return this.httpClient.get < any >
            (`${this.baseUrl}/getAllPosts`);
    }

    getPostById(postId: string): Observable<any> {
        return this.httpClient.get < any >
            (`${this.baseUrl}/getPostById/${postId}`);
    }

    getMyPosts(token: string): Observable<any> {
        const headers = new HttpHeaders({
            'Authorization': `Bearer ${token}`
        });
        return this.httpClient.get < any >
            (`${this.baseUrl}/getMyPosts`, { headers });
    }

    createPost(post: any, token: string): Observable<any> {
        const headers = new HttpHeaders({
            'Authorization': `Bearer ${token}`
        });
        return this.httpClient.post < any >
            (`${this.baseUrl}/createPost`, { post }, { headers });
    }

    updatePost(post: any, token: string): Observable<any> {
        const headers = new HttpHeaders({
            'Authorization': `Bearer ${token}`
        });
        return this.httpClient.put < any >
            (`${this.baseUrl}/updatePost/${post._id}`, post, { headers });
    }

    deletePost(postId: string, token: any): Observable<void> {
        const headers = new HttpHeaders({
            'Authorization': `Bearer ${token}`
        });
        return this.httpClient.delete < void>
            (`${this.baseUrl}/deletePost/${postId}`, { headers });
    }
}
JavaScript
// like.service.ts

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
    providedIn: 'root'
})
export class LikeService {
    private baseUrl = "http://localhost:5000";
    constructor(private http: HttpClient) { }
    toggleLike(postId: string, token: string):
        Observable<any> {
        const headers = new HttpHeaders({
            'Authorization': `Bearer ${token}`
        });
        return this.http.post(`${this.baseUrl}
        /api/like/${postId}`, {}, { headers });
    }
}
JavaScript
// auth.service.ts

import { HttpClient } from '@angular/common/http';
import { EventEmitter, Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
    providedIn: 'root'
})
export class AuthService {
    private baseUrl = 'http://localhost:5000/api/auth';
    constructor(private httpClient: HttpClient) { }

    register(userData: any): Observable<any> {
        return this.httpClient
            .post(`${this.baseUrl}/register`, userData);
    };

    login(credentials: any): Observable<any> {
        return this.httpClient
            .post(`${this.baseUrl}/login`, credentials);
    };

    private isAuthenticatedSubject = new BehaviorSubject
        < boolean > (false);

    isAuthenticated(): Observable<boolean> {
        return this.isAuthenticatedSubject
            .asObservable();
    }

    setAuthenticationStatus(isAuthenticated: boolean): void {
        this.isAuthenticatedSubject
            .next(isAuthenticated);
    }

    loggedInEvent: EventEmitter<any>
        = new EventEmitter();
    emitLoggedInEvent() {
        this.loggedInEvent.emit();
    }
}
JavaScript
// app.routes.ts

import { Routes } from '@angular/router';
import { UserComponent } from './auth/user/user.component';
import { PostComponent } from './post/post/post.component';
import { AddPostComponent } from './post/add-post/add-post.component';
import { MypostComponent } from './post/mypost/mypost.component';

export const routes: Routes = [
    { path: '', component: UserComponent },
    { path: 'getAllPosts', component: PostComponent },
    { path: 'createPost', component: AddPostComponent },
    { path: 'getMyPosts', component: MypostComponent },
];
JavaScript
// app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
import { RouterModule } from '@angular/router';
import { routes } from './app.routes';
import { PostComponent } from './post/post/post.component';
import { UserComponent } from './auth/user/user.component';
import { MypostComponent } from './post/mypost/mypost.component';
@NgModule({
    declarations: [
        AppComponent,
        PostComponent,
        UserComponent,
        MypostComponent
    ],
    imports: [
        BrowserModule,
        FormsModule,
        RouterModule.forRoot(routes),
    ],
    exports: [RouterModule],
    providers: [{
        provide: JWT_OPTIONS,
        useValue: JWT_OPTIONS
    }, JwtHelperService],
    bootstrap: [AppComponent]
})
export class AppModule { }
HTML
<!-- post.component.html -->

<div *ngIf="displayedPostList && displayedPostList.length > 0; else noPosts">
  <div class="post" *ngFor="let post of displayedPostList; let i = index">
    <div class="post-header">
      <div class="post-title">{{post.title}}</div>
    </div>
    <div class="post-content">{{post.content}}</div>
    <div class="post-actions">
      <button class="action-button" (click)="toggleLike(post._id)">
        <span *ngIf="post.isLiked">Unlike</span>
        <span *ngIf="!post.isLiked">Like</span>
      </button>
      <span>{{ post.likes && post.likes.length }} likes</span>
    </div>
    <div class="post-actions" *ngIf="getUserId() === post.author">
      <hr />
      <button
        class="action-button-header"
        style="background-color: green"
        (click)="populateUpdateForm(post)"
      >
        Update
      </button>
      <button
        class="action-button-header"
        style="background-color: crimson"
        (click)="confirmDelete(post)"
      >
        Delete
      </button>
    </div>
  </div>
</div>
<ng-template #noPosts>
  <div class="no-posts-container">
    <p>No Posts available</p>
  </div>
</ng-template>

<div *ngIf="showUpdateForm" class="update-form-container">
  <button class="close-button" (click)="cancelUpdate()">X</button>
  <h2>Update Post</h2>
  <form class="update-form" (ngSubmit)="updatePost(postUpdated._id)">
    <div>
      <label for="author">Post Author:</label>
      <input
        type="text"
        id="author"
        name="author"
        [(ngModel)]="userDetails.username"
        readonly
      />
    </div>
    <div>
      <label for="updatePostTitle">Post Title:</label>
      <input
        type="text"
        id="updatePostTitle"
        name="updatePostTitle"
        [(ngModel)]="postUpdated.title"
        required
      />
    </div>
    <div>
      <label for="updatePostContent">Post Content:</label>
      <input
        type="text"
        id="updatePostContent"
        name="updatePostContent"
        [(ngModel)]="postUpdated.content"
        required
      />
    </div>
    <div>
      <label for="updatePostCategory">Post Category:</label>
      <input
        type="text"
        id="updatePostCategory"
        name="updatePostCategory"
        [(ngModel)]="postUpdated.category"
        required
      />
    </div>
    <div>
      <label for="createdAt">Created At:</label>
      <input
        type="text"
        id="createdAt"
        name="createdAt"
        [(ngModel)]="postUpdated.createdAt"
        readonly
      />
    </div>
    <div>
      <label for="updatedAt">Updated At:</label>
      <input
        type="text"
        id="updatedAt"
        name="updatedAt"
        [(ngModel)]="postUpdated.updatedAt"
        readonly
      />
    </div>
    <div>
      <label for="updatePostAttachment">Attachment:</label>
      <input
        type="text"
        id="updatePostAttachment"
        name="updatePostAttachment"
        [(ngModel)]="postUpdated.attachment"
        required
      />
      <img src="{{postUpdated.attachment}}" />
    </div>
    <div>
      <button type="submit">Update Post</button>
      <button type="button" (click)="cancelUpdate()">Cancel</button>
    </div>
  </form>
</div>
HTML
<!-- mypost.component.html -->

<div *ngIf="displayedPostList && displayedPostList.length > 0; else noPosts">
    <div class="post" *ngFor="let post of displayedPostList; let i = index">
        <div class="post-header">
            <div class="post-title">{{post.title}}</div>
        </div>
        <div class="post-content">{{post.content}}</div>
        <div class="post-actions">
            <span>{{ post.likes && post.likes.length }} likes</span>
        </div>
    </div>
</div>
<ng-template #noPosts>
    <div class="no-posts-container">
        <p>No Posts available</p>
    </div>
</ng-template>
HTML
<!-- add-post.component.html -->

<div class="update-form-container">
  <h2>Add Post</h2>
  <form class="update-form" (ngSubmit)="createPost()">
    <div>
      <label for="author">Post Author:</label>
      <input
        type="text"
        id="author"
        name="author"
        [(ngModel)]="userDetails.username"
        readonly
      />
    </div>
    <div>
      <label for="title">Post Title:</label>
      <input
        type="text"
        id="title"
        name="title"
        [(ngModel)]="postCreated.title"
        required
      />
    </div>
    <div>
      <label for="content">Post Content:</label>
      <input
        type="text"
        id="content"
        name="content"
        [(ngModel)]="postCreated.content"
        required
      />
    </div>
    <div>
      <label for="category">Post Category:</label>
      <input
        type="text"
        id="category"
        name="category"
        [(ngModel)]="postCreated.category"
        required
      />
    </div>
    <div>
      <label for="createdAt">Created At:</label>
      <input
        type="text"
        id="createdAt"
        name="createdAt"
        [(ngModel)]="postCreated.createdAt"
        readonly
      />
    </div>
    <div>
      <label for="createdAt">Updated At:</label>
      <input
        type="text"
        id="createdAt"
        name="createdAt"
        [(ngModel)]="postCreated.updatedAt"
        readonly
      />
    </div>
    <div>
      <label for="attachment">Attachments:</label>
      <input
        type="text"
        id="attachment"
        name="attachment"
        [(ngModel)]="postCreated.attachment"
      />
      <img src="{{postCreated.attachment}}" *ngIf="postCreated.attachment" />
    </div>
    <div>
      <button type="submit">Add Post</button>
      <button type="button" (click)="closeAddForm()">Clear</button>
    </div>
  </form>
</div>
HTML
<!-- sidebar.component.html -->

<ul class="nav">
    <li><a (click)="loadContent('allposts')">All Posts</a></li>
    <li><a (click)="loadContent('myposts')">My Posts</a></li>
    <li><a (click)="loadContent('addpost')">Add Post</a></li>
</ul>
HTML
<!-- user.component.html -->

<div class="error-message" *ngIf="errorMessage">{{ errorMessage }}</div>
<div class="success-message" *ngIf="successMessage">{{ successMessage }}</div>
<div class="container" *ngIf="loginActive">
  <h2>Login</h2>
  <form (ngSubmit)="login()">
    <div class="form-group">
      <label for="email">Email:</label>
      <input
        type="email"
        class="form-control"
        id="email"
        name="email"
        [(ngModel)]="email"
        required
      />
    </div>
    <div class="form-group">
      <label for="password">Password:</label>
      <input
        type="password"
        class="form-control"
        id="password"
        name="password"
        [(ngModel)]="password"
        required
      />
    </div>
    <button type="submit" class="btn btn-primary" href="getAllPosts">
      Login
    </button>
  </form>
</div>
<div class="container" *ngIf="registerActive">
  <h2>Register</h2>
  <form (submit)="register()">
    <div class="form-group">
      <label for="username">Username</label>
      <input
        type="text"
        id="username"
        class="form-control"
        [(ngModel)]="username"
        name="username"
        required
      />
    </div>
    <div class="form-group">
      <label for="email">Email</label>
      <input
        type="email"
        id="email"
        class="form-control"
        [(ngModel)]="email"
        name="email"
        required
      />
    </div>
    <div class="form-group">
      <label for="password">Password</label>
      <input
        type="password"
        id="password"
        class="form-control"
        [(ngModel)]="password"
        name="password"
        required
      />
    </div>
    <button type="submit" class="btn btn-primary">Register</button>
  </form>
</div>
HTML
<!-- app.component.html -->

<nav class="navbar">
    <div class="navbar-title">{{ title }}</div>
    <ul class="navbar-menu">
        <li><a href="#" (click)="login()" *ngIf="!isLoggedIn">Login</a></li>
        <li><a href="#" (click)="register()" *ngIf="!isLoggedIn">Register</a></li>
        <li><a href="#" (click)="logout()" *ngIf="isLoggedIn">Logout</a></li>
    </ul>
</nav>

<div class="container">
    <app-sidebar (contentLoad)="loadContent($event)"></app-sidebar>
    <div class="content">
        <router-outlet></router-outlet>
    </div>
</div>
CSS
/* post.component.css */

.post {
    border: 1px solid #ccc;
    border-radius: 5px;
    padding: 10px;
    margin-bottom: 20px;
}

.post-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 10px;
}

.post-title {
    font-size: 1.2rem;
    font-weight: bold;
}

.post-author {
    font-style: italic;
    display: block;
}

.post-content {
    margin-bottom: 10px;
}

.post-actions {
    margin-top: 0.4vmax;
}

.action-button {
    background-color: cadetblue;
    color: #fff;
    border: none;
    border-radius: 3px;
    padding: 5px 10px;
    margin-right: 5px;
    cursor: pointer;
}

.action-button-header {
    background-color: #0056b3;
    color: #fff;
    border: none;
    border-radius: 3px;
    padding: 5px 10px;
    margin-right: 5px;
    cursor: pointer;
}

.no-posts-container {
    margin-top: 20px;
    padding: 10px;
    background-color: #f8d7da;
    color: #721c24;
    border: 1px solid #f5c6cb;
    border-radius: 4px;
}

.no-posts-container p {
    text-align: center;
    margin: 0;
}

/* Update Post */

.update-form-container {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 91%;
    background-color: rgba(255, 255, 255, 1);
    padding: 3.7vmax;
    border-radius: 5px;
}

.update-form-container h2 {
    text-align: center;
    font-size: 2rem;
}

.update-form-container .close-btn {
    position: absolute;
    top: 5px;
    right: 5px;
    font-size: 18px;
    color: #555;
    cursor: pointer;
}

.update-form-container .close-btn:hover {
    color: #333;
}

.update-form label {
    display: block;
    margin-bottom: 5px;
}

.update-form input[type="text"],
.update-form input[type="number"],
.update-form input[type="date"] {
    width: calc(100% - 12px);
    padding: 10px;
    margin-bottom: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
}

.update-form button[type="submit"],
.update-form button[type="button"] {
    width: 15%;
    padding: 10px;
    background-color: green;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    margin-right: 10px;
}

.update-form button[type="button"] {
    background-color: #dc3545;
}

/* View Post */
.view-activity-container {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 90%;
    background-color: rgba(255, 255, 255, 1);
    padding: 3.7vmax;
    border-radius: 5px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

.view-activity-container h2 {
    text-align: center;
    font-size: 2rem;
}

.view-activity-form label {
    display: block;
    margin-bottom: 5px;
}

.view-activity-form input[type="text"],
.view-activity-form input[type="number"],
.view-activity-form input[type="date"] {
    width: calc(100% - 12px);
    padding: 10px;
    margin-bottom: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
}

.view-activity-form button[type="submit"],
.view-activity-form a {
    width: 10%;
    padding: 10px;
    background-color: #0056b3;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    text-align: center;
    display: inline-block;
    text-decoration: none;
}

.close-button {
    position: absolute;
    top: 10px;
    right: 10px;
    padding: 5px 10px;
    background-color: #ccc;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

.close-button:hover {
    background-color: #aaa;
}

img {
    border-radius: 50%;
    width: 2.5vmax;
    height: 2.5vmax;
}
CSS
/* myposts.component.css */

.post {
    border: 1px solid #ccc;
    border-radius: 5px;
    padding: 10px;
    margin-bottom: 20px;
}

.post-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 10px;
}

.post-title {
    font-size: 1.2rem;
    font-weight: bold;
}

.post-content {
    margin-bottom: 10px;
}

.post-actions {
    margin-top: 0.4vmax;
}

.no-posts-container {
    margin-top: 20px;
    padding: 10px;
    background-color: #f8d7da;
    color: #721c24;
    border: 1px solid #f5c6cb;
    border-radius: 4px;
}

.no-posts-container p {
    text-align: center;
    margin: 0;
}
CSS
/* add-post.component.css */

.update-form-container h2 {
    text-align: center;
    font-size: 2rem;
}

.update-form-container .close-btn {
    position: absolute;
    top: 5px;
    right: 5px;
    font-size: 18px;
    color: #555;
    cursor: pointer;
}

.update-form-container .close-btn:hover {
    color: #333;
}

.update-form label {
    display: block;
    margin-bottom: 5px;
}

.update-form input[type="text"],
.update-form input[type="number"],
.update-form input[type="date"] {
    width: calc(100% - 12px);
    padding: 10px;
    margin-bottom: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
}

.update-form button[type="submit"],
.update-form button[type="button"] {
    width: 10%;
    padding: 10px;
    background-color: green;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    margin-right: 10px;
}

.update-form button[type="button"] {
    background-color: #dc3545;
}

img {
    border-radius: 50%;
    width: 2.5vmax;
    height: 2.5vmax;
}
CSS
/* sidebar.component.css */

ul {
    border: 1px solid lightgray;
    margin-top: 0%;
    border-radius: 10px;
}

li {
    list-style-type: none;
    margin: 3vmax;
    cursor: pointer;
    box-sizing: border-box;
}

li:hover {
    color: lightgray;
}

a {
    color: lightgray;
    text-decoration: none;
}

a:hover {
    color: white;
    text-decoration: none;
}
CSS
/* user.component.css */

.container {
    width: 50%;
    margin: 2rem auto;
    padding: 1.5vmax;
    padding-right: 2.5vmax;
    border: 1px solid #ccc;
    border-radius: 5px;
}

h2 {
    text-align: center;
    margin-bottom: 20px;
    font-size: 2rem;
}

.form-group {
    margin-bottom: 20px;
}

label {
    display: block;
    margin-bottom: 5px;
}

input[type="text"],
input[type="email"],
input[type="password"] {
    width: 97%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
}

button[type="submit"] {
    width: 20%;
    padding: 1.1vmax;
    background-color: #0056b3;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-weight: bold;
    font-size: 1rem;
    align-self: center;
    margin-top: 1vmax;
}

.container {
    width: 50%;
    margin: 2rem auto;
    padding: 1.5vmax;
    padding-right: 3.5vmax;
    border: 1px solid #ccc;
    border-radius: 5px;
}

h2 {
    text-align: center;
    margin-bottom: 20px;
    font-size: 2rem;
}

.form-group {
    margin-bottom: 20px;
}

label {
    display: block;
    margin-bottom: 5px;
}

input[type="email"],
input[type="password"] {
    width: 99%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
}

button[type="submit"] {
    width: 20%;
    padding: 1.1vmax;
    background-color: #0056b3;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-weight: bold;
    font-size: 1rem;
    align-self: center;
    margin-top: 1vmax;
}

.error-message {
    color: #FF0000;
    background-color: #FFEFEF;
    padding: 10px;
    border: 1px solid #FF0000;
    border-radius: 5px;
    margin-bottom: 10px;
}

.success-message {
    color: green;
    background-color: rgb(185, 231, 185);
    padding: 10px;
    border: 1px solid green;
    border-radius: 5px;
    margin-bottom: 10px;
}
CSS
/* app.component.css */

.navbar {
    background-color: #333;
    color: #fff;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1.5vmax 3vmax;
}

.navbar-title {
    font-size: 1.7rem;
}

.navbar-menu {
    list-style-type: none;
    padding: 0;
    margin: 0;
}

.navbar-menu li {
    display: inline;
    margin-right: 2vmax;
    font-size: 1.3rem;
}

.navbar-menu li:last-child {
    margin-right: 0;
}

.navbar-menu li a {
    color: #fff;
    text-decoration: none;
}

.navbar-menu li a:hover {
    text-decoration: underline;
}

.container {
    display: flex;
    height: 100%;
    min-height: 87vh;
    margin-top: 0.4vmax;
}

.content {
    flex: 1;
    padding: 20px;
}

app-sidebar {
    width: 20%;
    background-color: #333;
    padding: 20px;
    color: white;
    font-size: 1.4rem;
}

Output:

output-(8)

Community Forum Page using MEAN Stack



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads