Multi-purpose platform in Node.js and MongoDB

A multi-purpose platform is created in Node.js and MongoDB. Developing an API in Node.js and MongoDB allows you to create real-time, high performance and scalable applications. When you are building a website or a mobile app, you might have a different design than what is already built. But your backend will be almost similar to your competitor. Let’s say you want to build a social network like Facebook. You will pick an HTML template or design a UI specific for your business. But you want to have a module to create users, posts, pages, groups, friends, chats and much more.

So we are providing you a multi-purpose platform that allows you to use social network, job portal, manage your media files and much more. It is a self-hosted script so all the data will be stored on your server, no third-party storage service is used here.

Tech stack ๐Ÿ’ป

  • Bootstrap 5+
  • React 18+
  • Node.js 20+
  • MongoDB 4+

User Registration and Profile ๐Ÿ™‹๐Ÿปโ€โ™‚๏ธ

This API allows you to register an account. The passwords are encrypted before saving them in database. User can also login using his email and password that he entered during registration.

On successfull login, a Json Web Token (JWT) is generated and returned in the API response. The client application needs to save that token in its local storage because that token will be use in other APIs in headers to identify the user. This token is also stored in server as well. So if you want to logout a user manually, you can simply remove his token from database.

User can edit his name and profile image. The email field is read-only, it cannot be changed. User can also change his password. For that, he needs to provide his current password and enter his new password 2 times for confirmation.

Social Network Posts ๐Ÿ“

If you are building a social network, then this module allows you to create and manage posts for a social network. You can also share other’s posts on your timeline. You can edit or remove your own posts. Other users will be able to like, comment or share your posts. Users will be able to edit to delete their own comments. You will be able to reply to the comments on your posts, and you can also edit or remove your reply if you want. Users can also see the list of all users who has liked or shared your post.

Pages ๐Ÿ“ƒ

If you are running a business, you can create a page for your business and start posting about your products or services in that page. In order to create a page, you need to set it’s name, tell a little about the page, and provide a cover photo for the page. User can follow your page and start seeing your page’s posts in their newsfeed. Only creator of page can create a post in that page.

You can see a list of all users who has followed your page. User can also see a list of all pages he has followed. The owner of the page can edit or delete the page. Once page is deleted, all the posts inside that will be deleted as well.

Groups ๐Ÿฅณ

You can create groups to create a community of like-minded people. In order to create a group, you need to enter the name of group, a little description about the group and it’s cover photo. You can see a list of all groups created in the platform. There are separate links from where you can see only the groups that you have created or the groups you have joined. You can join or leave the group whenever you want. However, admin of the group can also remove you from the group if he wants.

Only admin or group members can post in a group. Posts uploaded by admin will be immediately displayed on the groups newsfeed. However, the posts uploaded by group members will be held pending for approval from the admin. Admin can see a list of all posts uploaded by group members. He can accept or decline a post he wants.

Admin of the group can update the name or description of the group, and he can also change the cover photo of the group. Admin can delete the group as well. Upon deleting, all the posts uploaded in that group will be deleted as well.

Media ๐Ÿ“‚

This API allows you to upload media files (images, videos) on the platform. Each media file will have a title, alt attribute, and caption. These will help you in your Search Engine Optimization (SEO). You can edit or delete your own media files whenever you want.

Friends ๐Ÿ™๐Ÿป

You can create friends by sending them a friend request, just like you do in Facebook. Other user can see a list of all friend requests he has received. He can accept or decline your request. If accepted, then you both will become friends and you can chat with each other. You can see a list of all of your friends. At any point, you can remove a user from your friend list.

Realtime Chat ๐Ÿ’ฌ

You can have realtime chat with your friends. Chats are end-to-end encrypted, that means that the messages are encrypted before sending to the server. And they are decrypted only after receiving the response from the server. Your messages will remain secure in-transit.

For realtime communication, we are using Socket IO. When you send a message, an event is emitted. The receiver will be listening to that event and display a notification alert that you have received a new message. If the chat is already opened, then the new message will automatically be appended at the bottom of the chat.

Job Portal ๐Ÿ‘จ๐Ÿปโ€๐Ÿ”ง ๐Ÿ‘จ๐Ÿปโ€๐Ÿณ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿซ ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽจ

Since this is a multi-purpose platform and it is developed in scalable technologies (Node.js & MongoDB), a job portal platform is also added that allows recruiter to post jobs and candidates can apply on that job. Recruiter can see all the applications he has received on a job and can change the status of applicant to shortlisted, interviewing, rejected or selected etc. Candidate can upload multiple CVs and choose the relevant CV while applying for the job. Recruiter can update or delete the job any time.

You can also filter job applications by status. So if you are recruiter, you will post a job. Then you will start receiving applications from candidates. Recruiter can change the status from interviewing to shortlisted employess. Then recruiter can change the status to interviewing. Recruiter can also reject the candidate, but he also needs to provide a reason for rejection. Finally, recruiter can set the candidate as “Hired”. So there must be a way for recruiter to filter job applications by their status.

We also have a PHP and MySQL version of Job Portal website. You can get it from here.

Admin Panel ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ผ

An admin panel is created where you can manage all the users, social network posts and jobs created on the platform. It is a single page application created in React JS and can be deployed as a sub-domain to your main website.

Super admin can see the list of all users. He can add a new user if he wants. An email with password set by admin will be sent to the new user. Admin can also update the user password as well. He can also delete the user, all the posts uploaded by that user will be deleted as well.

Super admin can see all the posts uploaded on social network platform. Just like users, he can delete any post he did not feel appropriate.

Admin can also see all the jobs posted by recruiter and if any job post goes against the standards, super admin can remove it.

Blogs ๐Ÿ“œ

Since this is a multi-purpose platform, so I added a blog module that allows you to write articles on your website. Admin can create blogs from admin panel and they will be displayed on user side. User can post a comment if they are logged-in. Other users or admin can reply to their comments. Admin can manage all posts and comments from admin panel. Admin can delete any post or command he did not find suitable.

We are using sockets to update the blogs in realtime. So if user is reading a post and admin changes something from admin panel, it will immediately gets updated on user side. User does not need to refresh the page.

Page Builder ๐Ÿ—

We are using page builder to create blog posts. It allows you to create paragraphs, headings, images, videos and buttons.

You can set the attributes like id and class. You can also set CSS properties of each tag.

If you are using image or video, you can add alt and caption attributes as well.

Freelance Platform ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ๐Ÿค‘

This multi-purpose platform project also has a freelance platform where you can hire experts to get your job done. Or, if you are a skilled person, you can find work there.

There are 2 entities in freelance platform: Buyer ๐Ÿ’ฐ and Seller ๐Ÿค‘. Buyer will create a task to be done, he will mention his budget and deadline. Buyer must have sufficient balance in their account. They can add balance using Stripe. ๐Ÿ’ณ

Sellers will start bidding on that task. Seller will also send a proposal mentioning how he will get the work done, what is his offer and in how many days he will do it. ๐ŸŒš

Buyer will see all the bids from sellers. He will also see a freelance profile of each bidder, telling the orders done by each seller, their ratings and reviews etc. โญ๏ธ

Buyer can accept the bid of any seller he seems fit for the job. The seller will be notified and their order will be started. โฐ

On their order detail page, they can chat with each other. They can send messages ๐Ÿ’ฌ with attachments ๐Ÿงท. At any point, buyer or seller can cancel the order.

After the work is done, buyer can complete the order โœ…. Once it is completed, the amount that was offered in the bid will be deducted from buyer’s account. 5 percent will be deducted as a “platform fee”, and the remaining 95% of the amount will be added in the seller’s account ๐Ÿค‘.

Buyer can also gives ratings โญ๏ธ to a seller and also can give a review about his experience. This will help seller build his freelance profile.

Seller can withdraw the money by entering his bank account details ๐Ÿฆ. These details will be sent to the admin panel ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ผ. Admin can transfer the funds and once the transfer is completed, seller’s balance will be returned to 0.

Notifications ๐Ÿ””

User will receive a notification when:

  • His freelance order gets completed/cancelled.
  • A new message has been received on his order.
  • His bid got accepted on freelance project.
  • His withdrawl request has been updated.
  • Someone commented on his post.
  • Someone replied to his comment.
  • Someone likes his post on social network.
  • His post has been shared by someone.

These notifications will be displayed as a bell icon ๐Ÿ”” on header when user is logged-in. If he has any unread notifications, then it will be displayed as read.

Unread notification’s background will be gray. On clicking the notification, it will be marked as read.

Installer ๐Ÿคน๐Ÿปโ€โ™‚๏ธ

Run the following commands in your “admin” folder:

npm install
npm start

In order to create a super admin, you need to run the following command in your “premium-api” folder once:

node installer.js

This will create a super admin in your database. You can set the email and password of your choice from “installer.js” file.

Paste the “multi-purpose-platform-nodejs-mongodb” folder in your “htdocs” folder if you are using XAMPP or MAMP, or in “www” folder if you are using WAMP.

Run the following commands in the “premium-api” folder:

npm update
npm install -g nodemon
nodemon index.js

Project can be accessed from:

http://localhost/multi-purpose-platform-nodejs-mongodb/web/index.html

Demos ๐ŸŽฏ

1. Social network – Posts

2. Pages

3. Groups

4. Friends & chat

5. Job portal

6. Admin panel

7. Blogs

8. Freelance Platform

More features can also be added on-demand in this multi-purpose platform. If you do not want all the features, we can remove or change features as per your needs.

Files included ๐Ÿ—„

  • api
    • index.js
    • installer.js
    • modules
      • admin
        • admin.js
        • auth.js
      • auth-optional.js
      • auth.js
      • freelance.js
      • banks.js
      • payments.js
      • blogs.js
      • categories.js
      • chats.js
      • files.js
      • job-portal
        • cvs.js
        • gigs.js
        • jobs.js
      • mails.js
      • media.js
      • notifications.js
      • sn
        • friends.js
        • groups.js
        • pages.js
        • posts.js
      • users.js
    • package.json
    • uploads
      • private
      • public
  • web
    • freelance
      • buyer.html
      • order-detail.html
      • orders.html
      • seller.html
      • task-detail.html
    • blogs
      • index.html
      • detail.httml
    • change-password.html
    • index.html
    • balance.html
    • withdraw.html
    • job-portal
      • applied.html
      • create.html
      • cv-manager.html
      • detail.html
      • edit.html
      • index.html
      • my.html
    • login.html
    • media
      • index.html
      • edit.html
    • profile.html
    • public
      • css
      • js
      • img
    • register.html
    • sn
      • chat.html
      • edit-comment.html
      • edit-post.html
      • edit-reply.html
      • friends.html
      • groups
        • create.html
        • detail.html
        • edit.html
        • index.html
        • members.html
        • my-joined.html
        • my.html
        • pending-posts.html
      • index.html
      • notifications.html
      • pages
        • create.html
        • detail.html
        • edit.html
        • index.html
        • followers.html
        • my-followed.html
        • my.html
      • post.html
      • profile.html
      • search.html
      • send-reply.html
  • admin (SPA)
    • package.json
    • public
    • src
      • App.css
      • App.js
      • index.js
      • components
        • blogs
          • AddPost.js
          • Comments.js
          • EditPost.js
          • Posts.js
        • categories
          • AddCategory.js
          • Categories.js
          • EditCategory.js
        • Dashboard.js
        • ecommerce
          • AddProduct.js
          • EditProduct.js
          • Orders.js
          • Products.js
        • files
          • Files.js
        • jobs
          • Jobs.js
        • layouts
          • Header.js
          • Footer.js
          • Sidebar.js
        • Login.js
        • sn
          • Posts.js
        • users
          • AddUser.js
          • EditUser.js
          • Users.js

Media Queries in CSS

Learn how to make your website responsive on desktops, tablets, and mobile phones, using media queries in CSS.

What are media queries ?

In CSS, media queries are used to apply styles on your website based on the properties of the user’s device. It helps you make your website responsive on all devices (desktops, tablets, and mobile phones).

Video Tutorial

If you prefer following a video tutorial, here you are:

We will be using a grid column. So let’s say you have a row container with 12 columns in it.

<div class="row">
	<div class="col">1</div>
	<div class="col">2</div>
	<div class="col">3</div>
	<div class="col">4</div>
	<div class="col">5</div>
	<div class="col">6</div>
	<div class="col">7</div>
	<div class="col">8</div>
	<div class="col">9</div>
	<div class="col">10</div>
	<div class="col">11</div>
	<div class="col">12</div>
</div>

First, we will tell that the row will be a grid having 12 columns in total. While having each column equally sized.

.row {
	display: grid;
	grid-template-columns: repeat(12, 1fr);
	grid-gap: 5px;
}

Media Query for Desktop

For desktop devices, you can write the following media query in CSS.

/* for desktop */
@media (min-width: 992px) {
	.col {
		grid-column: auto/span 1;
	}
}

It will check if the device width exceeds 992px, then it will divide the column into 12 parts. i.e. 12 / 1 = 12 columns

Media Query for Tablet

The following media query will be used for tablet devices.

/* for tablets */
@media (min-width: 768px) and (max-width: 991px) {
	.col {
		grid-column: auto/span 3; /* 12 / 3 = 4 columns for tablet */
	}
}

Media Queries for Mobile and Small Devices

For mobile devices, we will show 2 columns, and for small phones (having a resolution of less than 767px) we will show 1 column.

/* for mobile */
@media (max-width: 767px) {
	.col {
		grid-column: auto/span 6; /* 12 / 6 = 2 columns for mobile */
	}
}

/* for small devices */
@media (max-width: 320px) {
	.col {
		grid-column: auto/span 12; /* 12 / 12 = 1 column for small devices */
	}
}

That’s it. That’s how you can use media queries in CSS and make your website responsive for all devices. If you face any problem in following this, kindly do let me know.

Display ellipsis on long text HTML and CSS

In this article, I am going to show you how you can show an ellipsis on a long text using HTML and CSS. We will be using just 4 CSS properties.

Video tutorial:

For example, you have a paragraph like this:

<p>A quick brown fox jumps over the lazy dog.</p>

Then give it a width and you will start seeing it in wrap.

<p style="width: 200px;">A quick brown fox jumps over the lazy dog.</p>

To disable the wrap, you can give the following property:

<p style="width: 200px;
	white-space: nowrap;">A quick brown fox jumps over the lazy dog.</p>

Last thing you need to do, is to show ellipsis. To do that, just give the following 2 properties.

<p style="width: 200px;
	white-space: nowrap;
	text-overflow: ellipsis;
	overflow: hidden;
	display: block;">A quick brown fox jumps over the lazy dog.</p>

So that’s it. That’s how you can show an ellipsis on a long text using just HTML and CSS. If you face any problem in following this, kindly do let me know.

Ecommerce website in MEVN stack

It is a single-page ecommerce website developed in Vue JS. Vue JS is an open-source Javascript framework specifically built to design user interfaces and is widely used for creating single-page applications.

Node JS is a Javascript run-time environment developed for creating the backend of applications. Its framework Express is widely used for creating APIs. It is built to create scalable applications.

Mongo DB is a no-SQL database. It is a non-relational database system. It is easily scalable because the data is loosely coupled.

FeaturesFreePremium $100
Product management (admin panel)YesYes
Product listingYesYes
Product detailYesYes
Shopping cartYesYes
Checkout (PayPal & Stripe)YesYes
Order management (admin panel)YesYes
Order detailYesYes
Product specificationsYesYes
Stock managementYesYes
Search and sortYesYes
New order emailYesYes
Product reviewsNoYes
Shipping charges by countryNoYes
Product image compressionNoYes
Realtime chat between users and adminNoYes

Following is the feature list of this project:

  • Product management (admin panel)
  • Product listing
  • Product detail
  • Shopping cart
  • Checkout (PayPal & Stripe)
  • Order management (admin panel)
  • Order detail
  • Product specifications
  • Stock management
  • Product reviews
  • Search and sort
  • Shipping charges by country
  • Email to admin whenever a new order is placed
  • Product image compression
  • Realtime chat between users and admin

Add product

Admin will be able to add products. He will enter the name of the product. Add a little description of the product. And set the price of the product. The price will be in dollars. Along with them, the admin can upload one or more images of the product.

Product listing

Users will see the products added by the admin on their home page. The latest products are displayed first. Along with each product, a button will be displayed to view the detail of the product.

Product detail

Users can view the detail of the product. If the product contains more than one image, then it will be displayed as a beautiful slider. From this page, users will also be able to add products to the cart. If the product is already added to the cart, then he will be able to remove it from the cart.

Shopping cart

Users can add the product to the cart. From the cart’s page, he will be able to adjust the quantity of the product. There will be multiple products in the cart and you can set the quantity of each product separately. You will also see your total bill as soon as you change the quantity of any product added to the cart.

Checkout (Stripe & PayPal)

After adding the products to the cart and setting each product’s quantity, the user can go to the checkout page. Here, he can either pay via Stripe or PayPal. Both payment methods accept debit and master cards securely. When the user made the payment, his payment is verified from the server. If verified, then a new order will be created in the database.

Order management

Whenever a user made a payment via Stripe or PayPal, an order will be created. All orders will be displayed on the admin panel ordered by latest to oldest. By default, the status of the order is “Processing”. But admin can change the status of the order to “Completed” when the product is delivered to the customer.

Admin will also be able to view the total amount of the order, the payment method customer has used, and the user’s shipping details.

Order detail

Admin can view all the products that were included in each order. He can manually verify the payment by clicking the payment method. If he clicks “Stripe”, he will be redirected to the Stripe payments page. If he clicks “PayPal”, then he will be redirected to the PayPal business page. Admin can check the name, email, and phone of the user and also his shipping address.

Product specifications

Admin can add the specifications of each product and the user will be able to see it on the product detail page.

Stock management

Admin can set the number of units in stock for each product. When the user makes an order, then the number of units he has selected will be subtracted.

Product reviews

User can give reviews on a product and it will be displayed to all users who visit that product. Admin will have the ability to remove any review, for example, spamming, etc.

Search and sort

Users can search by product by its name, description, category, or specifications. Users can also sort the products by date or by price.

Shipping charges by country

Admin can set the shipping charges by each country because, in a global eCommerce website, users place orders from all over the world. For example, if your store is located in USA and someone places an order from the UK. Then you charge the shipping fee differently than other countries.

Email to admin whenever a new order is placed

Admin will receive an email whenever a new order has been placed by the user.

Product image compression

Now you can compress the product images from the admin panel. The 3 MB image can be reduced to just 182 KB and the image will still be of great quality.

Realtime chat between users and admin

Users will be able to have a chat with the admin to know more about a product before placing an order. I believe every eCommerce website should have this feature.

We are constantly updating this project and adding more features and enhancements to it. Following are all the builds that are released till today.

  • In the very first release, we build the basic E-commerce version where the admin can add products and users can see them. Add them to carts and order them. We also added Stripe and PayPal payment methods.
  • In the second release, we added more features. Allowing users to give reviews about a product. Allowing admin to charge a different shipping fee for each country and many more.
  • In the third release, we added functionality to compress product images and to have real-time chat between users and admin.

Free version

https://github.com/adnanafzal565/ecommerce-mevn-stack

CRUD in Vue JS and PHP

In this tutorial, we are going to perform a complete CRUD (create, read, update, delete) operation using Vue JS and PHP. We will be using Vue JS for the frontend and PHP for backend processing. We will also be using PDO prepared statements for preventing SQL injection.

First, you need to download Vue JS from here and Bootstrap from here. You will also need to download jQuery from here as well. After that, you need to copy-paste the JS file from Vue JS in your project. You also need to copy-paste the CSS and JS files from Bootstrap too.

After downloading and placing in your project, you need to include these files as well.

<script src="vue.min.js"></script>

<link rel="stylesheet" type="text/css" href="css/bootstrap.css" />
<script src="js/jquery-3.3.1.min.js"></script>
<script src="js/bootstrap.js"></script>

Create

First thing is that you need to insert the data into the database. For the sake of this tutorial, we have created a simple table in our MySQL database. The table name is users and it has the following columns:

  1. id (auto increment)
  2. name
  3. email
  4. password

Then you need to create a form from which you can enter the details to save in the database.

<div id="myApp">
	<div class="container">
		<h1 class="text-center">Create</h1>

		<div class="row">
			<div class="offset-md-3 col-md-6">
				<form method="POST" action="create.php" v-on:submit.prevent="doCreate">
					<div class="form-group">
						<label>Name</label>
						<input type="text" name="name" class="form-control" />
					</div>

					<div class="form-group">
						<label>Email</label>
						<input type="email" name="email" class="form-control" />
					</div>

					<div class="form-group">
						<label>Password</label>
						<input type="password" name="password" class="form-control" />
					</div>

					<input type="submit" value="Create User" class="btn btn-primary" />
				</form>
			</div>
		</div>
	</div>
</div>

After that, we need to create a Vue JS app and a method that will call an AJAX request to the server.

<script>
	// initialize Vue JS
	const myApp = new Vue({
		el: "#myApp",
		methods: {

			doCreate: function () {
				const self = this;
				const form = event.target;

				const ajax = new XMLHttpRequest();
				ajax.open("POST", form.getAttribute("action"), true);

				ajax.onreadystatechange = function () {
					if (this.readyState == 4) {
						if (this.status == 200) {
							console.log(this.responseText);
						}
					}
				};

				const formData = new FormData(form);
				ajax.send(formData);
			}
		},
	});
</script>

Now create a file named create.php that will handle the request.

<?php

// connect database
$conn = new PDO("mysql:host=localhost:8889;dbname=test", "root", "root");

// prepare insert statement
$sql = "INSERT INTO users (name, email, password) VALUES (:name, :email, :password)";
$result = $conn->prepare($sql);

// execute the query
$result->execute([
	":name" => $_POST["name"],
	":email" => $_POST["email"],
	// encrypt password in hash
	":password" => password_hash($_POST["password"], PASSWORD_DEFAULT),
]);

echo "Done";

Refresh the page now and enter new user details and hit submit. Then go to your phpMyAdmin and refresh the users table and you will see a new row inserted in the database.

Read

Now the data is being inserted in the database but you should all the inserted data when the page loads. To create an HTML table:

<h1 class="text-center">Read</h1>

<table class="table">
	<tr>
		<th>ID</th>
		<th>Name</th>
		<th>Email</th>
	</tr>

	<tr v-for="(user, index) in users">
		<td v-text="user.id"></td>
		<td v-text="user.name"></td>
		<td v-text="user.email"></td>
	</tr>
</table>

In your Vue JS instance, create a data object, and inside that object create a users array.

data: {
	users: []
},

And when the Vue JS instance is mounted, we need to call a method to call an AJAX request to get the data.

// call an AJAX to fetch data when Vue JS is mounted
mounted: function () {
	this.getData();
}

After that, create a method in your methods object in the Vue JS instance:

// get all users from database
getData: function () {
	const self = this;

	const ajax = new XMLHttpRequest();
	ajax.open("POST", "read.php", true);

	ajax.onreadystatechange = function () {
		if (this.readyState == 4) {
			if (this.status == 200) {
				const users = JSON.parse(this.responseText);
				self.users = users;
			}
		}
	};

	const formData = new FormData();
	ajax.send(formData);
},

This will send an AJAX request but we need to create a server file that will handle this request. So create a file named read.php and the following will be the code of this file:

<?php

// connect database
$conn = new PDO("mysql:host=localhost:8889;dbname=test", "root", "root");

// get all users from database sorted by latest first
$sql = "SELECT * FROM users ORDER BY id DESC";
$result = $conn->prepare($sql);
$result->execute([]);
$data = $result->fetchAll();

// send all records fetched back to AJAX
echo json_encode($data);

Refresh the page now and you will be able to view all the records added to the database. But if you insert the new record again, you again have to refresh the page to see this new entry. However, newly inserted records should automatically be prepended at the top of the table.

So you need to modify your create.php and first return the newly inserted record from the database.

// get the latest record inserted
$sql = "SELECT * FROM users WHERE id = :id";
$result = $conn->prepare($sql);
$result->execute(array(
    ":id" => $conn->lastInsertId()
));
$data = $result->fetch();

// send the newly inserted record back to AJAX
echo json_encode($data);

Then in your Vue JS instance inside the doCreate method when the response is successfully received, prepend the new user in the users array.

const user = JSON.parse(this.responseText);

// prepend in local array
self.users.unshift(user);

Refresh the page now and try to insert a new user again. Now you will see that it will be prepended at the top automatically.

Update

To update the user we first must show a button to edit the user. To create a new column in your table:

<th>Actions</th>

And inside the v-for loop create a button for edit.

<td>
	<button type="button" v-bind:data-id="user.id" v-on:click="showEditUserModal" class="btn btn-primary">Edit</button>
</td>

Then you need to create a method in the methods object of your Vue JS instance that will be called when this button is clicked.

showEditUserModal: function () {
	const id = event.target.getAttribute("data-id");
	
	// get user from local array and save in current object
	for (var a = 0; a < this.users.length; a++) {
		if (this.users[a].id == id) {
			this.user = this.users[a];
			break;
		}
	}

	$("#editUserModal").modal("show");
},

Then you need to create another variable in your data object that will hold the information of the selected user.

user: null

The above-created function will display a Bootstrap modal to edit the user. Now we need to create that model in our HTML.

<!-- Modal -->
<div class="modal" id="editUserModal">
	<div class="modal-dialog" role="document">
		<div class="modal-content">
			<div class="modal-header">
				<h5 class="modal-title">Edit User</h5>
				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
					<span aria-hidden="true">&times;</span>
				</button>
			</div>
			
			<div class="modal-body">
				<form method="POST" action="update.php" v-on:submit.prevent="doUpdate" id="form-edit-user" v-if="user != null">
					<input type="hidden" name="id" v-bind:value="user.id" />

					<div class="form-group">
						<label>Name</label>
						<input type="text" name="name" v-bind:value="user.name" class="form-control" />
					</div>

					<div class="form-group">
						<label>Email</label>
						<input type="email" name="email" v-bind:value="user.email" class="form-control" />
					</div>
				</form>
			</div>

			<div class="modal-footer">
				<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
				<button type="submit" name="submit" class="btn btn-primary" form="form-edit-user">Save changes</button>
			</div>
		</div>
	</div>
</div>

When the edit form is submitted, it called a Javascript function to call an AJAX request to the server. So we need to create that method in our Vue JS instance methods object.

// update the user
doUpdate: function () {
	const self = this;
	const form = event.target;

	const ajax = new XMLHttpRequest();
	ajax.open("POST", form.getAttribute("action"), true);

	ajax.onreadystatechange = function () {
		if (this.readyState == 4) {
			if (this.status == 200) {

				const user = JSON.parse(this.responseText);

				// update in local array
				// get index from local array
				var index = -1;
				for (var a = 0; a < self.users.length; a++) {
					if (self.users[a].id == user.id) {
						index = a;
						break;
					}
				}

				// create temporary array
				const tempUsers = self.users;

				// update in local temporary array
				tempUsers[index] = user;

				// update the local array by removing all old elements and inserting the updated users
				self.users = [];
				self.users = tempUsers;
			}
		}
	};

	const formData = new FormData(form);
	ajax.send(formData);

	// hide the modal
	$("#editUserModal").modal("hide");
},

Now we need to create a new file called update.php that will handle this AJAX request and will update the data in database.

<?php

// connect database
$conn = new PDO("mysql:host=localhost:8889;dbname=test", "root", "root");

// update user name and email using his unique ID
$sql = "UPDATE users SET name = :name, email = :email WHERE id = :id";
$result = $conn->prepare($sql);

$result->execute([
	":name" => $_POST["name"],
	":email" => $_POST["email"],
	":id" => $_POST["id"],
]);

// get the updated record
$sql = "SELECT * FROM users WHERE id = :id";
$result = $conn->prepare($sql);
$result->execute(array(
    ":id" => $_POST["id"]
));
$data = $result->fetch();

// send the updated record back to AJAX
echo json_encode($data);

Refresh the page now, and you will see an “Edit” button with each row. On click, you will see a modal to update the data. Once submitted, the data will be updated in the database, the bootstrap modal will be closed and you will also see the data updated in the HTML table too.

Delete

To complete the CRUD operation in Vue JS and PHP. The final step is to create a “Delete” button. In front of each edit button, we need to create another button that will delete the user from the database and from the local array as well.

<form method="POST" action="delete.php" v-on:submit.prevent="doDelete" style="display: contents;">
	<input type="hidden" name="id" v-bind:value="user.id" />
	<input type="submit" name="submit" class="btn btn-danger" value="Delete" />
</form>

Then we need to create a method in Vue JS that will call an AJAX request to delete the user.

// delete user
doDelete: function () {
	const self = this;
	const form = event.target;

	const ajax = new XMLHttpRequest();
	ajax.open("POST", form.getAttribute("action"), true);

	ajax.onreadystatechange = function () {
		if (this.readyState == 4) {
			if (this.status == 200) {
				
				// remove from local array
				for (var a = 0; a < self.users.length; a++) {
					if (self.users[a].id == form.id.value) {
						self.users.splice(a, 1);
						break;
					}
				}
			}
		}
	};

	const formData = new FormData(form);
	ajax.send(formData);
},

Lastly, we need to create a file named delete.php that will handle this request and will actually delete the user from the database.

<?php

// connect database
$conn = new PDO("mysql:host=localhost:8889;dbname=test", "root", "root");

// delete the user from database
$sql = "DELETE FROM users WHERE id = :id";
$result = $conn->prepare($sql);
$result->execute(array(
    ":id" => $_POST["id"]
));

// send the response back to AJAX
echo "Done";

Refresh the page now and you will see a “Delete” button too with each row. On clicking, will delete that record from the database, and also it will delete the HTML table row as well.

Congratulations! You just completed your CRUD operation in Vue JS and PHP.

Download CRUD in Vue JS and PHP source code:

[wpdm_package id=’1511′]

Create a single page application in MEVN stack

Free tutorial

Dynamic testimonial – PHP & MySQL, Vue JS

In this tutorial, we are going to teach you how you can add a dynamic testimonial to your website using PHP and MySQL in the backend and Vue JS in the frontend. Testimonials are displayed on a website to show the new users how your product satisfies your previous customers. We will be creating 2 pages, one for adding testimonials from the admin panel. And second to display all testimonials in a beautiful design.

If you do not have a dynamic testimonial, then you have to manually add, modify or delete a testimonial from your website. By going dynamic, you can perform all these actions from your admin panel.

Add Testimonial

First, download Bootstrap from here and Vue JS from here. Paste the CSS and JS files in your project, we will be using them in a moment. After that, we need to create a form from which we can add testimonials. Each testimonial will have a picture of the person, name, designation in his company, and his comments.

The following code goes in your admin panel from where you want to add testimonials.

<!-- include bootstrap -->
<link rel="stylesheet" type="text/css" href="bootstrap.min.css" />
 
<!-- include vue js -->
<script src="vue.min.js"></script>

<!-- container for vue js app -->
<div class="container" style="margin-top: 50px; margin-bottom: 50px;" id="addTestimonialApp">
    <div class="row">
        <!-- center align form -->
        <div class="offset-md-3 col-md-6">
            <h2 style="margin-bottom: 30px;">Add Testimonial</h2>
 
 			<!-- form to add testimonial -->
            <form v-on:submit.prevent="store" enctype="multipart/form-data">

            	<!-- picture of user -->
                <div class="form-group">
                    <label>Picture</label>
                    <input type="file" name="picture" accept="image/*" class="form-control" />
                </div>

                <!-- name of user -->
                <div class="form-group">
                    <label>Name</label>
                    <input type="text" name="name" class="form-control" />
                </div>

                <!-- designation of user -->
                <div class="form-group">
                    <label>Designation</label>
                    <input type="text" name="designation" class="form-control" />
                </div>

                <!-- comment -->
                <div class="form-group">
                    <label>Comment</label>
                    <textarea name="comment" class="form-control"></textarea>
                </div>

                <!-- submit button -->
                <input type="submit" name="submit" class="btn btn-info" value="Add Testimonial" />
            </form>
        </div>
    </div>

    [show all testimonials for deleting]
</div>

This will show a form with input fields. But when you click on the “Add Testimonial” button, nothing happens. This is because we need to render it using Vue JS.

<script>
    // initialize vue js app
    var addTestimonialApp = new Vue({
        el: "#addTestimonialApp", // id of container div
        data: {
            // all values used in this app
            testimonials: []
        },
        // all methods
        methods: {
            // [other methods goes here]

             // called when form is submitted
            store: function () {
                // get this app instance
                var self = this;
                var form = event.target;
             
                // call an AJAX to create a new entry in testimonials
                var ajax = new XMLHttpRequest();
                ajax.open("POST", "store.php", true);
             
                ajax.onreadystatechange = function () {
                    if (this.readyState == 4) { // response received
                        if (this.status == 200) { // response is successfull
                            // console.log(this.responseText);
             
                            // parse the response from JSON string to JS arrays and objects
                            var response = JSON.parse(this.responseText);
                            // console.log(response);

                            alert(response.message);
             
                            // if there is no error
                            if (response.status == "success") {
                                self.testimonials.unshift(response.testimonial);
                                form.reset();
                            } else {
                                // when there is any error
                            }
                        }
             
                        if (this.status == 500) {
                            console.log(this.responseText);
                        }
                    }
                };
             
                // create form data object and form to it
                var formData = new FormData(form);
             
                // actually sending the request
                ajax.send(formData);
            },
        },

        // [mount code goes here]
    });
</script>

Refresh the page now and you will be able to submit the form, this is because of Vue JS. An AJAX request will be sent to the server to store the picture attached and save the other fields in the MySQL database using PHP.

Create a new file named “store.php” and paste the following code in it:

<?php
 
// connect with database
$conn = new PDO("mysql:host=localhost;dbname=test", "root", "");

// create tables if not exists
$sql = "CREATE TABLE IF NOT EXISTS testimonials (
    id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
    picture TEXT NULL,
    name VARCHAR(255) NULL,
    designation VARCHAR(255) NULL,
    comment TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)";
$result = $conn->prepare($sql);
$result->execute();

$file_path = "";
if ($_FILES["picture"]["error"] == 0)
{
    $folder_name = "testimonials";
    mkdir($folder_name);
    $file_path = $folder_name . "/" . $_FILES["picture"]["name"];
    move_uploaded_file($_FILES["picture"]["tmp_name"], $file_path);
}

// insert in testimonials table
$sql = "INSERT INTO testimonials (picture, name, designation, comment, created_at) VALUES (?, ?, ?, ?, NOW())";
$result = $conn->prepare($sql);
$result->execute([
    $file_path,
    $_POST["name"],
    $_POST["designation"],
    $_POST["comment"],
]);
$testimonial_id = $conn->lastInsertId();

// get the testimonial just inserted in database
$sql = "SELECT * FROM testimonials WHERE id = ?";
$result = $conn->prepare($sql);
$result->execute([
    $testimonial_id
]);
$testimonial = $result->fetch();

echo json_encode([
    "status" => "success",
    "message" => "Testimonial has been added.",
    "testimonial" => $testimonial
]);
exit();

If you refresh the page now, upload the picture, enter the fields and hit submit, it will create a new table in the database if not already created. Then it will create a folder named “testimonials” and save the image file in it. Then it will insert a new row in it. And finally, it will return the new row back to the client (AJAX).

From there we will prepend it in our local testimonials array. Now we need to display all the added testimonials in an HTML table with a button to delete them.

Display all Testimonials to Admin

The following code goes in the [show all testimonials for deleting] section:

<!-- show all testimonials -->
<div class="row" style="margin-top: 50px;">
    <div class="col-md-12">
        <table class="table table-bordered">
            <!-- table heading -->
            <tr>
                <th>ID</th>
                <th>Picture</th>
                <th>Name</th>
                <th>Designation</th>
                <th>Comment</th>
                <th>Actions</th>
            </tr>
 
            <!-- loop through an array of testimonials -->
            <tr v-for="(testimonial, index) in testimonials">
                <td v-text="testimonial.id"></td>
                <td>
                    <img v-bind:src="testimonial.picture" style="width: 300px;" />
                </td>
                <td v-text="testimonial.name"></td>
                <td v-text="testimonial.designation"></td>
                <td v-text="testimonial.comment"></td>
                <td>
                    <!-- form to delete testimonial -->
                    <form v-on:submit.prevent="deleteTestimonial">
                        <input type="hidden" name="id" v-model="testimonial.id" required />
                        <input type="submit" class="btn btn-danger btn-sm" value="Delete" />
                    </form>
                </td>
            </tr>
        </table>
    </div>
</div>

This will create an empty HTML table because we need to load the in it first. We will call an AJAX to fetch all the stored testimonials using PHP and MySQL and display them using Vue JS. The following code goes in the [mount code goes here] section:

mounted: function () {
    this.getData();
}

Now we need to create a function named “getData” in our Vue JS instance. Replace the code in section [other methods goes here] with the following:

// [delete method]

getData: function () {
    // get this app instance
    var self = this;
 
    // call an AJAX to get all testimonials
    var ajax = new XMLHttpRequest();
    ajax.open("POST", "fetch.php", true);
 
    ajax.onreadystatechange = function () {
        if (this.readyState == 4) { // response received
            if (this.status == 200) { // response is successfull
                // console.log(this.responseText);
 
                // parse the response from JSON string to JS arrays and objects
                var response = JSON.parse(this.responseText);
                // console.log(response);
 
                // if there is no error
                if (response.status == "success") {
                    self.testimonials = response.data;
                } else {
                    // when there is any error
                }
            }
 
            if (this.status == 500) {
                console.log(this.responseText);
            }
        }
    };
 
    // create form data object
    var formData = new FormData();
 
    // actually sending the request
    ajax.send(formData);
},

Finally, we need to create a new file named “fetch.php” that will fetch all the testimonials from the MySQL database using PHP.

<?php

// connect with database
$conn = new PDO("mysql:host=localhost;dbname=test", "root", "");

// fetch all testimonials
$sql = "SELECT * FROM testimonials ORDER BY id DESC";
$statement = $conn->prepare($sql);
$statement->execute();
$data = $statement->fetchAll();

// create new field for full comment text
// because we will be displaying less text and display 'show more' button
for ($a = 0; $a < count($data); $a++)
{
	$data[$a]["comment_full"] = $data[$a]["comment"];
	$data[$a]["comment"] = substr($data[$a]["comment"], 0, 50);
}

// send the response back to client
echo json_encode([
    "status" => "success",
    "message" => "Testimonial has been fetched.",
    "data" => $data
]);
exit();

?>

Refresh the page now and you will be able to see all the testimonials added. Also, if you add a new testimonial, it will automatically be prepended in the HTML table. Now we need to make it able to delete the testimonial.

Delete Testimonial

We need to follow the following steps to delete the testimonial:

  1. Call an AJAX with an ID of testimonial.
  2. On server side, fetch the testimonial using ID.
  3. Delete the picture from the “testimonials” folder using PHP unlink() function.
  4. Delete the testimonial from MySQL database.
  5. Send the response back to client.
  6. The client will remove the testimonial from local array.
  7. It will automatically be removed from the HTML table.

Replace the section [delete method] with the following code:

// method to delete testimonial
deleteTestimonial: function () {
    // get this app instance
    var self = this;
 
    // get form
    var form = event.target;
 
    // call an AJAX to delete the testimonial
    var ajax = new XMLHttpRequest();
    ajax.open("POST", "delete.php", true);
 
    ajax.onreadystatechange = function () {
        if (this.readyState == 4) { // response received
            if (this.status == 200) { // response is successfull
                // console.log(this.responseText);
 
                // parse the response from JSON string to JS arrays and objects
                var response = JSON.parse(this.responseText);
                console.log(response);
 
                // remove from local array if deleted from server
                if (response.status == "success") {
                    for (var a = 0; a < self.testimonials.length; a++) {
                        var testimonial = self.testimonials[a];
                        if (testimonial.id == form.id.value) {
                            self.testimonials.splice(a, 1);
                            break;
                        }
                    }
                } else {
                    // display an error message
                    alert(response.message);
                }
            }
 
            if (this.status == 500) {
                console.log(this.responseText);
            }
        }
    };
 
    // append form in form data object
    var formData = new FormData(form);
 
    // call AJAX with form data
    ajax.send(formData);
},

Then we need to create a new file named “delete.php” that will handle this request. It will have the following code:

<?php
 
// connect with database
$conn = new PDO("mysql:host=localhost;dbname=test", "root", "");

// get the testimonial just inserted in database
$sql = "SELECT * FROM testimonials WHERE id = ?";
$result = $conn->prepare($sql);
$result->execute([
    $_POST["id"]
]);
$testimonial = $result->fetch();

if (!$testimonial)
{
    // send the response back to client
    echo json_encode([
        "status" => "error",
        "message" => "Testimonial not found."
    ]);
    exit();
}

// remove the picture from folder
unlink($testimonial["picture"]);

// create a query to delete the pricing table from database
$sql = "DELETE FROM testimonials WHERE id = ?";
 
// prepare the query
$result = $conn->prepare($sql);
 
// execute the query
$result->execute([
    $_POST["id"]
]);
 
// send the response back to client
echo json_encode([
    "status" => "success",
    "message" => "Testimonial has been deleted."
]);
exit();

Refresh the page now and you will be able to delete the testimonials as well. The only thing left is to show the testimonials on the user side.

Display Testimonials on User Side

To display testimonials on the user side, you need to download font-awesome and slick, you already have the Bootstrap and Vue JS files in your project folder. You can download font-awesome from here and slick from here. After downloading, paste both folders into your project, we will include them on your user side.

The following code will display the layout for each testimonial using HTML. Which we will render using Vue JS in the next step.

<!-- include bootstrap CSS -->
<link rel="stylesheet" type="text/css" href="bootstrap.min.css" />

<!-- include font awesome -->
<link rel="stylesheet" type="text/css" href="font-awesome/css/font-awesome.min.css" />

<!-- include slick -->
<link rel="stylesheet" type="text/css" href="slick.css" />
<link rel="stylesheet" type="text/css" href="slick-theme.css" />

<!-- include vue js -->
<script src="vue.min.js"></script>

<div class="container" id="testimonialApp" style="margin-top: 30px;">
	<div class="row">
		<div class="col-md-12">
			<h2 class="text-center">Testimonials</h2>
		</div>
	</div>

	<div class="row">
		<div class="col-md-12">
			<div class="items">

				<div class="card" v-for="(testimonial, index) in testimonials">
			        <div class="card-body">
			            <h4 class="card-title">
			            	<img src="https://img.icons8.com/ultraviolet/40/000000/quote-left.png" />
			            </h4>
			            
			            <div class="template-demo">
			                <p>
			                	<span v-text="testimonial.comment"></span>

			                	<span class="show-more-text" v-on:click="loadMoreContent" v-bind:data-index="index">show more</span>
			                </p>
			            </div>

			            <h4 class="card-title">
			            	<img src="https://img.icons8.com/ultraviolet/40/000000/quote-right.png" style="margin-left: auto;" />
			            </h4>
			            
			            <hr />
			            
			            <div class="row">
			                <div class="col-sm-3">
			                	<img class="profile-pic" v-bind:src="testimonial.picture" />
			                </div>
			                
			                <div class="col-sm-9">
			                    <div class="profile">
			                        <h4 class="cust-name" v-text="testimonial.name"></h4>
			                        <p class="cust-profession" v-text="testimonial.designation"></p>
			                    </div>
			                </div>
			            </div>
			        </div>
			    </div>
			    
			</div>
		</div>
	</div>
</div>

<!-- include jquery -->
<script src="jquery-3.3.1.min.js"></script>

<script src="slick.min.js"></script>

<!-- include bootstrap JS -->
<script src="bootstrap.min.js"></script>

<!-- your JS code -->
<script src="script.js?v=<?php echo time(); ?>"></script>

Create a new file named “script.js” and paste the following code in it:

var mainURL = window.location.origin + "/" + window.location.pathname + "/";

var testimonialApp = new Vue({
    el: "#testimonialApp",
    data: {
        testimonials: []
    },
    methods: {
        loadMoreContent: function () {
            var node = event.target;
            var index = node.getAttribute("data-index");

            if (this.testimonials[index].comment.length > 50) {
                // it needs to display less
                node.innerHTML = "show more";
                this.testimonials[index].comment = this.testimonials[index].comment_full.substr(0, 50);
            } else {
                // it needs to display more
                node.innerHTML = "show less";
                this.testimonials[index].comment = this.testimonials[index].comment_full;
            }
        },

        getData: function () {
            // get this app instance
            var self = this;
         
            // call an AJAX to get all testimonials
            var ajax = new XMLHttpRequest();
            ajax.open("POST", "fetch.php", true);
         
            ajax.onreadystatechange = function () {
                if (this.readyState == 4) { // response received
                    if (this.status == 200) { // response is successfull
                        // console.log(this.responseText);
         
                        // parse the response from JSON string to JS arrays and objects
                        var response = JSON.parse(this.responseText);
                        // console.log(response);
         
                        // if there is no error
                        if (response.status == "success") {
                            self.testimonials = response.data;

                            setTimeout(function () {
                                $('.items').slick({
                                    dots: true,
                                    infinite: true,
                                    speed: 800,
                                    autoplay: false,
                                    slidesToShow: 2,
                                    slidesToScroll: 2,
                                    responsive: [{
                                            breakpoint: 1024,
                                            settings: {
                                                slidesToShow: 3,
                                                slidesToScroll: 3,
                                                infinite: true,
                                                dots: true
                                            }
                                        }, {
                                            breakpoint: 600,
                                            settings: {
                                                slidesToShow: 2,
                                                slidesToScroll: 2
                                            }
                                        }, {
                                            breakpoint: 480,
                                            settings: {
                                                slidesToShow: 1,
                                                slidesToScroll: 1
                                            }
                                        }
                                    ]
                                });
                            }, 100);
                        } else {
                            // when there is any error
                        }
                    }
         
                    if (this.status == 500) {
                        console.log(this.responseText);
                    }
                }
            };
         
            // create form data object
            var formData = new FormData();
         
            // actually sending the request
            ajax.send(formData);
        }
    },
    mounted: function () {
        this.getData();
    }
});

At this point, you will be able to view the testimonials added from the admin panel on your website. It will also have a carousel slider. Finally, you can apply some CSS styles to make it look better for the users.

<style>
	.show-more-text {
		background-color: #72a4d5;
	    color: white;
	    padding: 3px 5px;
	    border-radius: 5px;
	    margin-left: 3px;
	    cursor: pointer;
	}
	.more {
		display: none;
	}

	@media (max-width:991.98px) {
		.padding {
			padding: 1.5rem
		}
	}

	@media (max-width:767.98px) {
		.padding {
			padding: 1rem
		}
	}

	.padding {
		padding: 5rem
	}

	.card {
		position: relative;
		display: flex;
		width: 350px;
		flex-direction: column;
		min-width: 0;
		word-wrap: break-word;
		background-color: #fff;
		background-clip: border-box;
		border: 1px solid #d2d2dc;
		border-radius: 11px;
		-webkit-box-shadow: 0px 0px 5px 0px rgb(249, 249, 250);
		-moz-box-shadow: 0px 0px 5px 0px rgba(212, 182, 212, 1);
		box-shadow: 0px 0px 5px 0px rgb(161, 163, 164)
	}

	.card .card-body {
		padding: 1rem 1rem
	}

	.card-body {
		flex: 1 1 auto;
		padding: 1.25rem
	}

	p {
		font-size: 0.875rem;
		margin-bottom: .5rem;
		line-height: 1.5rem
	}

	h4 {
		line-height: .2 !important
	}

	.profile {
		margin-top: 16px;
		margin-left: 11px
	}

	.profile-pic {
		width: 100px;
	}

	.cust-name {
		font-size: 18px
	}

	.cust-profession {
		font-size: 10px
	}

	.items {
		width: 90%;
		margin: 0px auto;
		margin-top: 30px
	}

	.slick-slide {
		margin: 10px;
		height: auto !important;
	}
</style>

So that’s it, you have a dynamic testimonial section fully manageable from the admin panel. If you face any problems in following this, kindly do let us know in the comments section below.

Also learn how to add dynamic pricing table in your website.

Download

[wpdm_package id=’1399′]

Picture Competition Web App – Node JS, Mongo DB, Vue JS

Picture Competition is a mobile responsive real-time web application developed in Node JS and Mongo DB. Its frontend is designed in Bootstrap and Vue JS. You can create competition between 2 people and the others can vote on the person based on looks or skills etc.

Demo:

https://github.com/adnanafzal565/picture-competition-web-app-node-js

FeaturesFreePremium $100
Login and registrationYesYes
Create competitionsYesYes
Search & sortYesYes
Vote on CompetitionYesYes
Delete CompetitionYesYes
Reset PasswordNoYes
Email VerificationNoYes
Adult Image ValidationNoYes
SMTP Configurations Admin PanelNoYes
Real-time VotesNoYes
Real-time Admin Panel StatisticsNoYes
Real-time CommentsNoYes
User ProfileNoYes
Realtime update via socketsNoYes
NotificationsNoYes
Load more buttonNoYes
Admin panelNoYes
Manage competitionsNoYes
Free customer supportNoYes

Mongo DB Backend

competition’s collection

1. Login and Registration

It uses login authentication using JWT (JsonWebToken). It does not use the browser session due to the following reasons:

  1. Sessions are destroyed once your app is restarted from terminal.
  2. Sessions are not linked with the server, they are stored in browser only.

The benefit of using JWT is that you can always log out a user from your Mongo DB. Just go to the “users” collection and find the specific user. Then empty the “accessToken” field of that user’s document.

Login and Registration

2. Create Competitions

Registered users can create competitions between 2 users. You can enter name and upload 1 picture of each competitor. There is no limit in the number of competitions to create, you can create as many as you want. You can view your created competitions on your “My Competitions” page.

As soon as you create a competition, it will automatically be displayed to all the users as well as to the admin in real-time. Users do not have to refresh the page to see new competitions. It uses Socket IO to display data in real-time.

Create Competition

3. Search

Users can search competitions by the name of competitors. Data is filtered and rendered using Vue JS. There is no need to press the enter key, the data is filtered as soon as the user starts typing in the search box.

Search and Sort

4. Sort

Users can also sort the competitions by any of the following for sorting functions:

  1. Highest votes to lowest.
  2. Lowest votes to highest.
  3. Oldest to newest.
  4. Newest to oldest (default).

5. Vote on Competition

Logged-in users can vote on the competition. You can either vote on one of the competitors. Once the vote is cast on one competitor, it cannot be removed. Also, you can cast your vote on only one of the competitors, not on both of them. It is also real-time, as soon as the vote is cast, it will automatically be displayed to all the users and the counter is incremented. The counter displays the number of votes cast on each competitor.

Votes

6. Delete Competition

Competitions can only be deleted by either of the users who created the competition, or by the admin. Once the competition is deleted, all the uploaded images will be deleted too. As soon as the competition is deleted, it will automatically be removed from all the other users too, without having them refresh the page.

7. Realtime Update using Sockets

Sockets are used for real-time communication. Instead of fetching the data from the server after regular intervals, sockets attach listeners to the client-side. Listeners are listening to the events sent from the server. The server will emit the event and the client will listen to that event and respond accordingly. In this project, sockets are used for the following features:

  1. When competition is created.
  2. When competition is deleted.
  3. To increase the counter after vote is casted to the competition.
  4. Notifications.

8. Notifications

When a competition is deleted by the admin, the admin will write the reason for the deletion. Thus, a notification will be sent to the user along with the reason why his competition was removed. By default, notification status is “unread” and they are highlighted. As soon as the user clicks on any of the notifications, that notification will be marked as “read” and it will no longer be highlighted.

Notifications

9. Load More Button

When the data in the database increases, it is not feasible to load all the data in a single AJAX request. So a “load more” button is created to solve this problem. For example, 10 records are fetched in the first AJAX request. The next 10 records will be fetched when the “load more” button is clicked, and so on.

Load More

10. Admin Panel

Admin panel is created so you (administrator) can delete any competition you find offensive. The default email and password of admin are:

email = admin@gmail.com
password = admin

11. Manage Competitions

Admin can delete competitions that he finds offensive. However, the admin must give the reason why that competition is deleted. A notification will be sent to the user who created that competition and he will be able to view it from the top navigation bar.

12. Reset Password

Now you will be able to reset your password if you ever forgot. You just need to enter your email address and an email will be sent to you with a link to reset the password. We are using the nodemailer module to send an email.

Forgot Password
Reset Password

13. Email Verification

When a new user registers, we are sending a verification email to the user’s entered email address. The user will not be able to log in until he verifies his email address. When a user clicks the link on his email address, he will receive a message that says that his account is verified. Then he will be able to log in successfully.

Email Verification

14. SMTP Configurations from Admin Panel

To send an email, you will need an SMTP server. Every SMTP server requires some configurations to set up that include, host, port, email, and password. You can write these values directly hardcoded in your code, but to update these values in the future, you have to find these values in the code and update them.

In this project, you can set these configurations directly from the admin panel. Once the values are set, new emails will be sent using the new configurations.

SMTP configurations from admin panel

15. Adult Image Validation

This is a must-have feature if you are creating a website that allows users to upload pictures and they will be seen to the world. Anyone can upload an image that contains adult content, and it will not be good for your business. So when the user is uploading pictures while creating competition, the system will automatically check if the image is safe to upload.

If the image is an adult image, then an error will be shown to the user and it will not be uploaded.

16. Admin Panel Stats

Admin can see total users, total competitions, and total votes cast so far. They are also real-time, so when a new user is registered, or new competition is created, or event a new vote is cast, it will automatically be incremented here.

Also, when competition is deleted, the number will automatically get decremented as well, without having the admin refresh the page.

Admin Panel Stats

17. Real-time Comments

Users can comment on each competition. And they are also real-time as well. Once a new comment is added, it will immediately be displayed to all the other users as well. They do not have to refresh the page to see new comments.

Real-time Comments

18. User Profile

Users can now update their names and profile pictures. We are using the fs (file system) module to upload the picture. User can also add their bio, date of birth, country, and social media links. Media links include Facebook, Instagram, google plus, Twitter, and LinkedIn.

User can also change their account password. In order to change the password, the user must enter the current password. The new password should be entered twice for confirmation.

User Profile

19. Free Customer Support

This is not a feature of the project, but it is a free service provided for the pro version only. That means if you find any difficulty in installing or configuring the project, we will help you install it. Also, if you encounter any error or a bug in the released version, then it can be fixed too.

These are all the features we have right now in the picture competition web app. We are open to more ideas. If you have more ideas to add, kindly do let us know.

Social networking site in Laravel – Node JS, Socket IO

When you hear the word “social networking site”, the first name that came to your mind must be Facebook. Then Instagram, Twitter, etc. We create a social networking site project just to give you an idea of how things work. It is not the exact code used in tech giants like Facebook. But it gives you an idea of how they do it.

Demo

FeaturesFreePremium ($100)
AuthenticationYesYes
Profile UpdateYesYes
Create posts (with images and videos)YesYes
Like, comments and repliesYesYes
Share postsYesYes
Friends post on your wallYesYes
Search usersYesYes
Create friendsYesYes
Admin panelYesYes
Email verificationNoYes
Reset passwordNoYes
NotificationsNoYes
Realtime post update and deleteNoYes
NoYes
Search pages, and groupsNoYes
Create pages, and groupsNoYes
Private user-to-user chatNoYes
List of people who viewed your profileNoYes
Infinite scrollNoYes
people who liked and shared your postNoYes
Chat messages encryptionNoYes
Customer supportNoYes
Ban users, posts, pages, and groupsNoYes
Adult image validationNoYes
Show emails generated by systemNoYes
Online / offline statusNoYes
User last sceneNoYes
User activity logsNoYes

Previously, we create a Social Networking Site in Node JS and Mongo DB. But many people were asking that we create the same project in Laravel too, for PHP lovers. So here it is.

We have added 4 more features to it.

  1. Track system mails: All the emails sent from the system (Mail::to) function will be stored in a database. And the admin can view all the emails from the admin panel.
  2. Online/offline status: Now you can know when your friends are online and when they are offline. This will be helpful because you can just leave a message if the person is currently offline.
  3. Last scene of the user: You can also see your friend’s last scene active. This will help in situations like earthquakes etc. you can know which friends are not active in recent times.
  4. Track user activity: Admin can view the activities of users like when they are logged in when they created a page when they joined a group etc.

Laravel Blog (Website + Android app) with Admin Panel

A Laravel blog website along with an Android app is created with an admin panel. It uses the design template from https://bootstrapmade.com/. It has the following key features:

Google Adsense approved

The project is tested with Google Adsense and it was approved by Google for monetization. You just have to link with your Google account and you will start receiving money once you reach the Google payment threshold.

User side

  1. 70 built-in blog posts.
  2. Random quotations.
  3. Total users display.
  4. Custom advertisement to generate revenue.
  5. Share posts on Twitter and Facebook.
  6. Limit access to some features for registered users only.
  7. Registration with Email Verification.
  8. Secure Login.
  9. Comment on Post.
  10. Reply to the comment.
  11. Related Posts.
  12. Subscribe to the newsletter.
  13. Social Links.
  14. A section to sell items directly.
  15. Amazon affiliate links.
  16. Realtime Chat with admin (Firebase).
  17. Manage Profile.
  18. Change Password.
  19. Custom Advertisement.

Admin panel

  1. Dashboard Statistics.
  2. Add/Edit blog posts.
  3. Add/Edit items that sell directly.
  4. Manage Inbox.
  5. Manage Comments.
  6. Realtime Chat with users (Firebase).

Android app

We also developed an Android App for this project which your users can download from Google Play Store and read your blog posts from that app. Here is the demo of the Laravel blog android app:

Our TrustPilot reviews

TrustPilot-reviews
TrustPilot-reviews

Custom keyboard view for typing secure passwords using HTML and CSS

When people use someone else’s computer and try to log in to their account on any website, they are afraid that the other person might have installed a Keylogging software that tracks all the keys pressed, and hence they will know his password. But if you show a custom keyboard view for a password field on your website, it will create a whole new level of confidence in users towards your website.

So let’s learn how you can do this.

This is what we are going to create:

First, you need to create a password field, you probably also have a username or email field as well.

<input type="password" name="password" id="password">

Then create a table with a full keyboard view.

Numeric keyboard:

First, we will create a full numeric keyboard that appears on top of most laptops.

<table class="keyboard">
    <tr>
        <td>
            ~<br><span data-shift="~" data-initial="`">`</span>
        </td>
        <td>
            !<br><span data-shift="!" data-initial="1">1</span>
        </td>
        <td>
            @<br><span data-shift="@" data-initial="2">2</span>
        </td>
        <td>
            #<br><span data-shift="#" data-initial="3">3</span>
        </td>
        <td>
            $<br><span data-shift="$" data-initial="4">4</span>
        </td>
        <td>
            %<br><span data-shift="%" data-initial="5">5</span>
        </td>
        <td>
            ^<br><span data-shift="^" data-initial="6">6</span>
        </td>
        <td>
            &<br><span data-shift="&" data-initial="7">7</span>
        </td>
        <td>
            *<br><span data-shift="*" data-initial="8">8</span>
        </td>
        <td>
            (<br><span data-shift="(" data-initial="9">9</span>
        </td>
        <td>
            )<br><span data-shift=")" data-initial="0">0</span>
        </td>
        <td>
            _<br><span data-shift="_" data-initial="-">-</span>
        </td>
        <td>
            +<br><span data-shift="+" data-initial="=">=</span>
        </td>
        <td><span>delete</span></td>
    </tr>
</table>

We have written the normal key in the <span> tag and the shift key directly in <td>. Also, we have given data-shift and data-initial attribute so we can know which character to use when the shift key is pressed and which character to use when the shift key is released.

1st row (QWERTY):

This will be the very first row on your keyboard. You can compare it with your keyboard. Create a new <tr> tag in your table:

<tr>
    <td></td>
    <td data-shift="Q"><span>q</span></td>
    <td data-shift="W"><span>w</span></td>
    <td data-shift="E"><span>e</span></td>
    <td data-shift="R"><span>r</span></td>
    <td data-shift="T"><span>t</span></td>
    <td data-shift="Y"><span>y</span></td>
    <td data-shift="U"><span>u</span></td>
    <td data-shift="I"><span>i</span></td>
    <td data-shift="O"><span>o</span></td>
    <td data-shift="P"><span>p</span></td>
    <td>
        {<br><span data-shift="{" data-initial="[">[</span>
    </td>
    
    <td>
        }<br><span data-shift="}" data-initial="]">]</span>
    </td>

    <td>
        |<br><span data-shift="|" data-initial="\">\</span>
    </td>
</tr>

It also has data-shift and data-initial attributes that tell that the character will be uppercase when the shift key is pressed, and lowercase when the shift key is released.

2nd row (ASDF):

Similarly, we are going to create a second row of the keyboard.

<tr>
    <td></td>
    <td data-shift="A"><span>a</span></td>
    <td data-shift="S"><span>s</span></td>
    <td data-shift="D"><span>d</span></td>
    <td data-shift="F"><span>f</span></td>
    <td data-shift="G"><span>g</span></td>
    <td data-shift="H"><span>h</span></td>
    <td data-shift="J"><span>j</span></td>
    <td data-shift="K"><span>k</span></td>
    <td data-shift="L"><span>l</span></td>
    <td>
        :<br><span data-shift=":" data-initial=";">;</span>
    </td>

    <td>
        "<br><span data-shift='"' data-initial="'">'</span>
    </td>
</tr>

3rd row:

This will be the last row of the keyboard, usually right above the space bar.

<tr>
    <td id="key-shift"><span>shift</span></td>
    <td data-shift="Z"><span>z</span></td>
    <td data-shift="X"><span>x</span></td>
    <td data-shift="C"><span>c</span></td>
    <td data-shift="V"><span>v</span></td>
    <td data-shift="B"><span>b</span></td>
    <td data-shift="N"><span>n</span></td>
    <td data-shift="M"><span>m</span></td>
    
    <td>
        <<br><span data-shift="<" data-initial=",">,</span>
    </td>

    <td>
        ><br><span data-shift=">" data-initial=".">.</span>
    </td>
    
    <td>
        ?<br><span data-shift="?" data-initial="/">/</span>
    </td>
</tr>

Note that it has an ID attribute to the shift key, that is so we can highlight the shift key when it is pressed.

Applying styles

To make the keyboard view looks nice, we are going to apply some styles. But you can style the keyboard as you want.

.keyboard {
    margin-top: 10px;
    table-layout: fixed;
    width: 200px;
}
.keyboard,
.keyboard td {
    border: 1px solid black;
    border-collapse: collapse;
}
.keyboard td {
    padding: 25px;
    cursor: pointer;

    width: 30px;
    overflow: hidden;
}
.keyboard td:hover,
.keyboard td.active {
    background: black;
    color: white;
}

This will make your keyboard look nice and readable.

Javascript

The real power of this keyboard view comes with Javascript. Following is the complete Javascript code that will make this static keyboard view functional.

var isShift = false;

window.addEventListener("load", function () {
    var tds = document.querySelectorAll(".keyboard td");
    for (var a = 0; a < tds.length; a++) {
        tds[a].addEventListener("click", function () {

            /* get key node */
            var node = this.querySelector("span");
            if (node == null) {
                return;
            }

            if (node.innerHTML == "delete") {
                simulateBackspace(document.getElementById("password"));
            } else if (node.innerHTML == "shift") {
                /* toggle the isShift variable */
                isShift = !isShift;
                toggleShift();
            } else {
                /* check if it is an alphabet */
                if ((/[a-zA-Z]/).test(node.innerHTML)) {
                    document.getElementById("password").value += node.innerHTML;
                } else {

                    /* show initial value if the shift is off.
                     * show shift value if shift is on. */
                    if (isShift) {
                        document.getElementById("password").value += node.getAttribute("data-shift");
                    } else {
                        document.getElementById("password").value += node.getAttribute("data-initial");
                    }
                }
                
                if (isShift) {
                    isShift = false;
                    toggleShift();
                }
            }
        });
    }
});

function toggleShift() {
    /* make all alphabets capital or small based on new isShift value */
    var keys = document.querySelectorAll(".keyboard span");
    for (var b = 0; b < keys.length; b++) {
        /* keys must not be shift or delete */
        if (keys[b].innerHTML != "shift" && keys[b].innerHTML != "delete" && keys[b].innerHTML != "") {

            /* check if it is an alphabet */
            if ((/[a-zA-Z]/).test(keys[b].innerHTML)) {
                if (isShift) {
                    keys[b].innerHTML = keys[b].innerHTML.toUpperCase();
                } else {
                    keys[b].innerHTML = keys[b].innerHTML.toLowerCase();
                }
            }
        }
    }

    /* highlight the shift button if on. */
    if (isShift) {
        document.getElementById("key-shift").className = "active";
    } else {
        document.getElementById("key-shift").className = "";
    }
}

function simulateBackspace(element) {
    var start = element.selectionStart, end = element.selectionEnd, event;

    if (!element.setRangeText) { return; }
    if (start >= end) {
      if (start <= 0 || !element.setSelectionRange) { return; }
      element.setSelectionRange(start - 1, start);
    }

    element.setRangeText("");
    event = document.createEvent("HTMLEvents");
    event.initEvent("input", true, false);
    element.dispatchEvent(event);
}

That’s how you can create a fully functional custom numeric keyboard view. Comments have been added with each line for an explanation. You can customize this as much as you want. If you face any problems feel free to ask in the comment section below.

[wpdm_package id=’1173′]