In this tutorial, we are going to teach you how you can implement events and listeners in Laravel. Events are created and multiple listeners can be assigned to them. Listeners are responsible for performing an action like saving the data in the database, sending an email, etc. In your controllers, you only need to emit an event and all the listeners associated with it will be called automatically.
Video tutorial:
Creating an event
First, we are going to create an event. Open terminal or command prompt at the root of your Laravel project and run the following command:
php artisan make:event MyEvent
A new file will be created at app/Events/MyEvent.php. Now place all the variables in it that you will be passed while calling this event.
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class MyEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $my_value;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($my_value)
{
$this->my_value = $my_value;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
Creating a Listener
Now we need to create a listener that will be called when the above event is emitted. Open the terminal and run the following command in it:
php artisan make:listener MyListener
A new file will be created at app/Listeners/MyListener.php. It will have the following content:
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class MyListener
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
dd($event->my_value);
}
}
That $event variable will hold all the values sent from the event class (MyEvent, in this case).
Attach Listeners to Event
The event and listeners are created. Now is the time to tell the Laravel framework to which listeners should be called when a specific event is called. Goto app/Providers/EventServiceProvider.php and include the event and listener at the top:
use App\Events\MyEvent;
use App\Listeners\MyListener;
Then update the $listen array in it:
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
MyEvent::class => [
MyListener::class
]
];
Now, whenever a MyEvent event is emitted, the MyListener listener will automatically be called.
Emitting an Event
Emitting an event in your Laravel project is very easy. You just need to include your event at the top:
use App\Events\MyEvent;
Then whenever you need to call it, you can simply call it like this:
event(new MyEvent("adnan-tech.com"));
At this point, the event will be emitted and the listener will be called. But we need to find a way to check if the listener is actually being called. We can simply do dd() inside listener’s handle() function.
A Financial Ledger script is created in Node JS, Mongo DB, and Vue JS. A financial ledger is used to track your daily finances, for example, your income, your expenses, etc. This script allows you to track your total income, total spending, and your current total balance. They are referred to as “stats” in the script. You can debit the entry by entering the amount that is negative. And you can credit an entry by simply writing its amount.
We have used the HTML and CSS code from this codepen. We have made it dynamic by using the latest technologies like, Vue JS for front-end, Node JS for back-end, and Mongo DB as the database. It has complete CRUD (Create, Read, Update, Delete) operation. It has the following features:
Insert data in Mongo DB using Node JS.
Fetch all documents from Mongo DB.
Update specific document in Mongo DB.
Delete documents from Mongo DB.
Case-insensitive search in Mongo DB documents.
Search by sub-string from Mongo DB document’s keys.
Realtime data insert.
Real-time update.
Realtime deletes.
“Load more” capability.
Datetimepicker library.
EJS templating engine.
Express-formidable for handling form fields.
Skip, limit and sort in Mongo DB.
Video tutorial:
1. Insert Data in Mongo DB using Node JS
An interface that allows you to enter the values (time, description, and amount), created in Vue JS. It binds those input field values to the Vue JS model. To enter the time, we are using a library datetimepicker. We are calling an AJAX request to the Node JS server with the input field values as FormData object when the form submits. That will simply insert a new document in Mongo DB collection “entries”. When the data is inserted, it is also inserted in the Vue JS array. This allows the new data to be appended automatically without having to refresh the page. The stat values automatically are updated based on if the entry is debit or credit.
2. Fetch All Documents from Mongo DB
An AJAX request is sent from the client to view all the data from the database. The server will return all the documents sorting from latest to oldest i.e. newest records will be shown first. The sorting is performed based on the date field selected during insertion. When the data is returned, it is concatenated in the Vue JS array that allows the user to view the data when the page loads. When the data is fetched, the stats are also updated automatically.
3. Update Specific Document in Mongo DB
To update specific documents, we are using Mongo DB auto-generated ObjectId field “_id”. This allows you to search the document, then we can perform the update query. To update the document, we are using HTML’s contenteditable attribute that allows you to edit the HTML node innerHTML by just typing in it. When the content of that div changes, we are sending an AJAX request to the Node JS server that performs the update query. If you update the amount value, then the stats will also get a change in real-time.
4. Delete Documents from Mongo DB
Deleting an entry will remove the document from the Mongo DB collection too. The row will be removed and the stats will be updated too.
5. Case insensitive Search Mongo DB
You can search the entries by description you have put during the insertion, or you can search the entries by either they are debited or credited. For example, you can write “debit” in the search box and it will show all the entries that are debited. The same goes for the “credit” search query.
6. Sub-string Search Mongo DB
While searching by description, you do not need to know the exact words. You can type the part of the description you have remembered and it will search the record anyway.
7. Realtime Mongo DB Data Insert
When a new entry is added, it is automatically prepended in the list using Vue JS. You do not have to refresh the page to view the new entries.
8. Realtime Update in Mongo DB
The same goes for the updation. You can update the entry’s description or the amount by simply typing in its box. When you start typing it automatically gets updated in the Mongo DB too. Also, the stats values also get updated if there is any change in the amount.
9. Realtime Delete in Mongo DB
With each entry, there is a minus (-) sign. On clicking that button, it will remove the entry from the list and also from the Mongo DB. The stats also gets updated accordingly.
10. Load More Capability
In the real-world, when you are creating a financial ledger, there will be hundreds of entries. So loading all entries when the page loads will slow down your application. So what we do is, load a few entries when the page loads. Then we show a button called “Load more”. Upon clicking that button, we will fetch the next entries from Mongo DB and so on. We will be using AJAX to fetch more entries.
11. Datetimepicker Javascript
In the financial ledger, it is important to enter the date the entry was added. So on the web, we have a library called datetimepicker by XDSoft. We are using this library to enter dates and times easily.
12. EJS Templating Engine in Node JS
In Node JS, to render files, there is an engine called EJS. It is used to render HTML files. The rendered files will have an extension “.ejs” instead of “.html” or “.php”.
13. Express Formidable Module in Node JS
In Node JS, to handle FormData object sent using AJAX, we are using a Node JS module named express-formidable. As the complete app is in Javascript, so there are a lot of AJAX requests in this app. Each AJAX request will send a FormData object to send values that the server will process.
14. Skip, Limit and Sort in Mongo DB
While fetching the records from Mongo DB, you can skip the records that are already been displayed to the user. Similarly, to load the data faster, you can limit the number of records to be fetched from Mongo DB in one request. To display the latest entries added, you can sort the records fetched from Mongo DB.
Google one tap sign in allows your website users to quickly log in to your site without having to enter a lot of form fields.
Video tutorial:
First, you need to go to Google Developer Console, from there you need to select or create a new project.
After a project is selected, you need to create its credentials.
Create credentials – Google one tap signin
In the credentials screen, you need to create OAuth Client ID credentials.
OAuth client ID create credentials
After that, you can specify:
The type of application as “Web Application”.
Set the name of the credential as per your desire.
Add your website domain in “Authorized Javascript origins”.
Goto OAuth Consent Screen.
Create oauth client id – Google one tap signin
OAuth Consent Screen will show you a form where you can enter the text you want to show to the user in the login prompt.
After everything is done, click “Save and Continue”. It will show you your Client ID and Client Secret.
OAuth Client Created – Google one-tap sign in
Install Google OAuth Library
You need to copy both Google Client ID and Client Secret values as your PHP variables.
<?php
// use sessions, to show the login prompt only if the user is not logged-in
session_start();
// paste google client ID and client secret keys
$google_oauth_client_id = "";
$google_oauth_client_secret = "";
?>
We will be using PHP sessions because we will show this prompt only if the user is not logged in.
Make sure you have Composer installed. You can download and install Composer from here.
After that, you need to run the following command at your root folder:
composer require google/apiclient
Display Google One-tap Sign In Prompt
Paste the following lines in the file where you want to show the one-tap sign-in prompt. If you want to show it to all the pages of your website, simply paste it into your header or footer file.
<!-- check if the user is not logged in -->
<?php if (!isset($_SESSION["user"])): ?>
<!-- display the login prompt -->
<script src="https://accounts.google.com/gsi/client" async defer></script>
<div id="g_id_onload"
data-client_id="<?php echo $google_oauth_client_id; ?>"
data-context="signin"
data-callback="googleLoginEndpoint"
data-close_on_tap_outside="false">
</div>
<?php endif; ?>
This will make sure to show the prompt to the user only if he is not logged in. data-callback will be our Javascript function that will be called when the user taps the login button.
<script>
// callback function that will be called when the user is successfully logged-in with Google
function googleLoginEndpoint(googleUser) {
// get user information from Google
console.log(googleUser);
// send an AJAX request to register the user in your website
var ajax = new XMLHttpRequest();
// path of server file
ajax.open("POST", "google-sign-in.php", true);
// callback when the status of AJAX is changed
ajax.onreadystatechange = function () {
// when the request is completed
if (this.readyState == 4) {
// when the response is okay
if (this.status == 200) {
console.log(this.responseText);
}
// if there is any server error
if (this.status == 500) {
console.log(this.responseText);
}
}
};
// send google credentials in the AJAX request
var formData = new FormData();
formData.append("id_token", googleUser.credential);
ajax.send(formData);
}
</script>
We are sending an AJAX request to our server so we can verify the user using his Google Credentials Token. The server must do the verification because anyone can exploit the client-side variables.
Verify Google Credentials Token – PHP
We are creating a file named “google-sign-in.php” where we will do this verification.
<?php
// use sessions
session_start();
// include google API client
require_once "vendor/autoload.php";
// set google client ID
$google_oauth_client_id = "";
// create google client object with client ID
$client = new Google_Client([
'client_id' => $google_oauth_client_id
]);
// verify the token sent from AJAX
$id_token = $_POST["id_token"];
$payload = $client->verifyIdToken($id_token);
if ($payload && $payload['aud'] == $google_oauth_client_id)
{
// get user information from Google
$user_google_id = $payload['sub'];
$name = $payload["name"];
$email = $payload["email"];
$picture = $payload["picture"];
// login the user
$_SESSION["user"] = $user_google_id;
// send the response back to client side
echo "Successfully logged in. " . $user_google_id . ", " . $name . ", " . $email . ", " . $picture;
}
else
{
// token is not verified or expired
echo "Failed to login.";
}
?>
Here, you need to place your Google Client ID again. This will verify the Google Credentials Token. This will also start the user session so the next time user refreshes the page, it will not show the login prompt.
You can learn about saving the data in the database from here.
Sockets are used for real-time communication. They are now being used in chat apps, team collaboration tools, and many more. Socket IO emit events to the receivers and the receivers are constantly listening to that event. When the event is received on the client-side, they can perform the necessary action. You can attach as many event listeners as you want and perform different actions for each event.
Users are connected with a Node JS server using a client-side library called Socket IO. Users can also join the room which will be helpful if you are creating a group chat app. There are 4 ways in which socket events are fired.
Send event to all connected users, including the sender.
Send event to all users, except the sender.
Emit event to all users in a room.
Send event to specific users.
In this tutorial, we will be covering the 4th part i.e. send socket events to specific users.
Video tutorial:
Problem
Suppose you have a chat app where you want 2 people to have a private chat. Now you want to have a real-time effect i.e. to show the new messages without having to refresh the page. This requires sockets that send the data in real-time. And we can show the data on the client-side in real-time too. Now when a sender sends a message to a specific user, we need to send the socket event to that specific user only.
Solution
We will create a simple script that allows us to send events to a specific user only. You can then integrate and customize that logic in your project. First, you need to download and install Node JS. You also need to download the Socket IO JS client-side library. We will have a simple database from where we can show all the users in a list, with a button to send an event to that user only. So we need to create a database with a simple users table, you can use your own database as well.
Database
In your phpMyAdmin, create a database named “send_socket_event_to_specific_users”. In that database, create a users table:
CREATE TABLE `users` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name` text NOT NULL
);
Add few rows in that table so we can show them in a list or a table.
It will show all users in a table with a button to send message. Now when the page loads, we need to get the ID of the user, you can also get it from PHP sessions.
Include Socket IO library
Before that, we need to include the Socket IO JS library. You can download it from here.
<script src="socket.io.js"></script>
<script>
var userId = prompt("Enter user ID");
var socketIO = io("http://localhost:3000");
socketIO.emit("connected", userId);
</script>
It will store your ID in userId variable. And it will connect with Node JS server and emit an event “connected” with your ID.
Now we need to create a simple Node server. Create an empty folder and create a file named “server.js” in it. Then open the CMD in that folder and run the following commands one-by-one:
var express = require("express");
var app = express();
var http = require("http").createServer(app);
var socketIO = require("socket.io")(http, {
cors: {
origin: "*"
}
});
var users = [];
socketIO.on("connection", function (socket) {
socket.on("connected", function (userId) {
users[userId] = socket.id;
});
// socket.on("sendEvent") goes here
});
http.listen(process.env.PORT || 3000, function () {
console.log("Server is started.");
});
This will start the server at port 3000, creates a users array and store all connected user’s socket ID in it.
Send event using socket IO emit function
Back in index.php, we need to create a JS function to send the event when the user click the “Send message” button:
function sendEvent(form) {
event.preventDefault();
var message = prompt("Enter message");
socketIO.emit("sendEvent", {
"myId": userId,
"userId": form.id.value,
"message": message
});
}
Now in server.js, we need to listen to that event and send the message to that user only. But before that, we need to include mysql module because the user’s names are stored in mysql database. At the top of your server.js:
var mysql = require("mysql");
var connection = mysql.createConnection({
host: "localhost",
port: 3306,
user: "root",
password: "",
database: "send_socket_event_to_specific_users"
});
connection.connect(function (error) {
console.log("Database connected: " + error);
});
And after the socket connected event:
socket.on("sendEvent", async function (data) {
connection.query("SELECT * FROM users WHERE id = " + data.userId, function (error, receiver) {
if (receiver != null) {
if (receiver.length > 0) {
connection.query("SELECT * FROM users WHERE id = " + data.myId, function (error, sender) {
if (sender.length > 0) {
var message = "New message received from: " + sender[0].name + ". Message: " + data.message;
socketIO.to(users[receiver[0].id]).emit("messageReceived", message);
}
});
}
}
});
});
This will search the sender and receiver by ID, and emit the event to the receiver with the name of the sender.
Listen to socket IO events
Now we need to listen to that event in our index.php and show a message in a list when that event is received. First, create a ul where all messages will be displayed:
<ul id="messages"></ul>
Then attach that event in JS:
socketIO.on("messageReceived", function (data) {
var html = "<li>" + data + "</li>";
document.getElementById("messages").innerHTML = html + document.getElementById("messages").innerHTML;
});
So that’s how you can use the socket IO emit function to send the event to a specific user only.
Check out realtime chat app tutorial using socket IO.
In this tutorial, we are going to show you, how you can show a sweetalert confirmation dialog when submitting a form. For example, if you have a form that when submits delete the data from the database. In that case, you must verify with the user because he might click that button by accident. So you can show a nice dialog using the Sweetalert library. Suppose you have the following form:
When the form submits, we are calling a Javascript function submitForm and passing the form as a parameter. Then you need to download the Sweetalert library from here. After downloading, paste that into your project and include it in your HTML file:
<script src="sweetalert.min.js"></script>
Now, we can create that Javascript function that will ask for confirmation. Once confirmed, it will submit the form.
<script>
function submitForm(form) {
swal({
title: "Are you sure?",
text: "This form will be submitted",
icon: "warning",
buttons: true,
dangerMode: true,
})
.then(function (isOkay) {
if (isOkay) {
form.submit();
}
});
return false;
}
</script>
At this point, if you submit the form, you will see a SweetAlert confirmation dialog first. All the form fields will be submitted correctly on the server-side. You can check it by printing out all the values received from the form:
In this tutorial, we are going to show you, how you can email a download link of a file to a user when they request to download files from your website. We are going to create a system where you can upload files and the user will be able to download them. But before downloading, they must enter their email address. The link to download the file will be emailed to the user. In this way, you will get a large number of emails. This will help you with a very large email list.
Prevent direct access to files
First, we are going to prevent users from downloading the files directly from the URL. We will be storing all our uploaded files in the “uploads” folder. So, create a new folder named “uploads” at the root of your project. Create a “.htaccess” file in this folder. In this file, write the following single line:
deny from all
This will gives a “403 Forbidden” error whenever someone tries to access the file directly from the browser.
Upload files
Now we are going to create a form that will allow you to upload files. You can create this form in your admin panel because usually, the administrator of the website can upload files.
Create a database named “collect_emails_while_downloading_files” in your phpMyAdmin. Or you can use your own database if you already have one. In this database, you need to create a table where we will store the path and name of all uploaded files.
CREATE TABLE files (
id INTEGER(11) PRIMARY KEY AUTO_INCREMENT NOT NULL,
file_name TEXT NOT NULL,
file_path TEXT NOT NULL
);
Then we need to save the selected file in the uploads folder and its path in the files table in the MySQL database. We will be using PHP PDO prepared statements that will help us from preventing the SQL injection.
<?php
// connect with database
$conn = new PDO("mysql:host=localhost:8889;dbname=collect_emails_while_downloading_files", "root", "root");
// check if form is submitted, for admin panel only
if (isset($_POST["upload"]))
{
// get the file
$file = $_FILES["file"];
// make sure it does not have any error
if ($file["error"] == 0)
{
// save file in uploads folder
$file_path = "uploads/" . $file["name"];
move_uploaded_file($file["tmp_name"], $file_path);
// save file path in database, prevent SQL injection too
$sql = "INSERT INTO files(file_name, file_path) VALUES (:file_name, :file_path)";
$result = $conn->prepare($sql);
$result->execute([
":file_name" => $file["name"],
":file_path" => $file_path
]);
}
else
{
die("Error uploading file.");
}
}
// get all files
$sql = "SELECT * FROM files ORDER BY id DESC";
$result = $conn->query($sql);
$files = $result->fetchAll();
?>
Refresh the page and try uploading a file. You will see it will be saved in your uploads folder and its path and name will be stored in the files table. Also, try accessing that from the browser directly, it will give you a 403 Forbidden error.
Show all uploaded files
In the previous step, we ran a query to fetch all files sorting from latest to oldest. Now we need to show them on a table.
This will show all files in a table with a button to download. When that button is clicked, we need to get the user’s email address so we can email him a download link for that file. That link will be valid for that file and for that email only.
<script>
function onFormSubmit(form) {
// get email address and submit
var email = prompt("Enter your email:", "");
if (email != null && email != "") {
form.email.value = email;
return true;
}
return false;
}
</script>
Send download link in email
We will be using the PHPMailer library to send emails. Open CMD at the root folder of your project and run the following command. Make sure you have the composer downloaded and installed in your system.
composer require phpmailer/phpmailer
Create a table in your database that will store all the download requests of files sent by users. Run the following query in your database in phpMyAdmin:
CREATE TABLE download_requests (
id INTEGER(11) PRIMARY KEY AUTO_INCREMENT NOT NULL,
file_id INTEGER(11) NOT NULL,
email TEXT NOT NULL,
token TEXT NOT NULL,
CONSTRAINT fk_file_id FOREIGN KEY (file_id) REFERENCES files (id) ON DELETE CASCADE ON UPDATE CASCADE
);
Create a file named “check-email.php” and write the following code in it. It will send the email to the user and also will store the data in the above created table.
<?php
// composer require phpmailer/phpmailer
// include PHPMailer library
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
require 'vendor/autoload.php';
// connect with database
$conn = new PDO("mysql:host=localhost:8889;dbname=collect_emails_while_downloading_files", "root", "root");
// get all form values
$id = $_POST["id"];
$email = $_POST["email"];
// generate a unique token for this email only
$token = time() . md5($email);
// get file from database
$sql = "SELECT * FROM files WHERE id = :id";
$result = $conn->prepare($sql);
$result->execute([
":id" => $id
]);
$file = $result->fetch();
if ($file == null)
{
die("File not found");
}
// insert in download requests, prevent SQL injection too
$sql = "INSERT INTO download_requests(file_id, email, token) VALUES (:id, :email, :token)";
$result = $conn->prepare($sql);
$result->execute([
":id" => $id,
":email" => $email,
":token" => $token
]);
// send email to user
$mail = new PHPMailer(true);
try
{
$mail->SMTPDebug = 0;
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com';
$mail->SMTPAuth = true;
$mail->Username = 'your_email@gmail.com';
$mail->Password = 'your_password';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->setFrom('adnan@gmail.com', 'Adnan');
$mail->addAddress($email); // Add a recipient
$mail->addReplyTo('adnan@gmail.com', 'Adnan');
// Content
$mail->isHTML(true);
$mail->Subject = 'Download your files';
// mention download link in the email
$email_content = "Kindly click the link below to download your files: <br />";
$base_url = "http://localhost:8888/tutorials/collect-emails-while-downloading-files-php-mysql";
$email_content .= "<a href='" . $base_url . "/download.php?email=" . $email . "&token=" . $token . "'>" . $file['file_name'] . "</a>";
$mail->Body = $email_content;
$mail->send();
echo '<p>Link to download files has been sent to your email address: ' . $email . '</p>';
}
catch (Exception $e)
{
die("Message could not be sent. Mailer Error: " . $mail->ErrorInfo);
}
Make sure to change the base URL at line 68. Also, change your email address and password on lines 53 and 54 respectively. This email will be used to send the emails. Goto this link and enable a less secure apps option for that email address.
Enable less secure apps – Gmail
Test the code now. You will be able to see a list of all uploaded files with a button to download. When clicked, it will show a prompt where you can enter your email address. When clicked “OK”, it will send an email with a download link and also it will store it in the “download_requests” table in the MySQL database.
In your email, you will see a link to download the file. But right now it will give a 404 Not found error because the file is not created yet.
Download the file from email download link
Create a file named “download.php” and write the following code in it. This will directly download the file into your system.
<?php
// connect with database
$conn = new PDO("mysql:host=localhost:8889;dbname=collect_emails_while_downloading_files", "root", "root");
// get variables from email
$email = $_GET["email"];
$token = $_GET["token"];
// check if the download request is valid
$sql = "SELECT *, download_requests.id AS download_request_id FROM download_requests INNER JOIN files ON files.id = download_requests.file_id WHERE download_requests.email = :email AND download_requests.token = :token";
$result = $conn->prepare($sql);
$result->execute([
":email" => $email,
":token" => $token
]);
$file = $result->fetch();
if ($file == null)
{
die("File not found.");
}
// download the file
$url_encoded_file_name = rawurlencode($file["file_name"]);
$file_url = "http://localhost:8888/tutorials/collect-emails-while-downloading-files-php-mysql/uploads/" . $url_encoded_file_name;
// die($file_url);
// headers to download any type of file
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $file["file_name"] . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file["file_path"]));
readfile($file["file_path"]);
Make sure to change your base URL at line 26. Now you can run a complete test cycle again. Upload a file, click the download button, and enter your email. Check your email and then click the download link from the email and the file will be downloaded. Verify the file that is downloaded correctly.
So that’s how you can collect a large number of emails by allowing people to simply download files. You can create a very large email list from it.
Learn how to send attachment with an email using PHP.
In this article, we are going to create a web crawler using Node JS and Mongo DB. It will take a URL as an input and fetch all the anchor tags, headings, and paragraphs. You can add more features to it if you want.
Requirements
Make sure you have the following things installed in your system:
Node JS
Mongo DB
Code Editor (Sublime Text etc.)
Video tutorial:
Setup the Project
First, create an empty folder anywhere in your system. Create a file named server.js in that folder. Open CMD in that folder by running the following command:
cd "path_of_your_folder"
We are going to need multiple modules for this web crawler. So, install them from the command:
Now we explain the reason for the installation of the above modules.
express framework is used for routing.
http is used to run HTTP requests.
ejs is a template engine used for rendering HTML files.
socket.io is used for realtime communication.
request is used to fetch content of web page.
cheerio is used for jQuery DOM manipulation.
express-formidable to get values from FormData object.
mongodb will be our database.
htmlspecialchars is used to convert HTML tags into entities.
node-html-parser to convert the HTML string into DOM nodes.
After all the modules are installed, run the following command to start the server:
npm install -g nodemon
nodemon server.js
Start the server
Open your server.js and write the following code in it to start the server at port 3000.
var express = require("express");
var app = express();
var http = require("http").createServer(app);
http.listen(3000, function () {
console.log("Server started running at port: 3000");
});
Now your project will be up and running at http://localhost:3000/
Connect Node JS with Mongo DB
To connect Node JS with Mongo DB, first, we need to create an instance of Mongo DB client in our server.js. Place following lines before http.listen function.
var mongodb = require("mongodb");
var mongoClient = mongodb.MongoClient;
var ObjectID = mongodb.ObjectID;
var database = null;
Now write the following code inside the http.listen callback function.
If you check your CMD now, you will see the message “Database connected”.
Crawl the web page
Now we need to create a form to get the URL as an input. So first we will tell our express app that we will be using EJS as our templating engine. And all our CSS and JS files will be inside the public folder. Place following lines before http.listen function.
Now create 2 folders at the root of your project, “public” and “views”. Download the latest jQuery, Bootstrap, DataTable, and Socket IO libraries and placed their files inside the public folder. Create a new file named index.ejs inside views folder. Create a GET route in our server.js when the Mongo DB is connected.
app.get("/", async function (request, result) {
result.render("index");
});
If you access your project from the browser now, you will see an empty screen. Open your index.ejs and write the following code in it:
You will now see a simple form with an input field and a submit button. In that input field, you can enter the URL of the page you wanted to crawl. Now we need to create a Javascript function that will be called when the form is submitted. In that function, we will call an AJAX request to the Node JS server.
<script>
function crawlPage(form) {
var ajax = new XMLHttpRequest();
ajax.open("POST", "/crawl-page", true);
ajax.onreadystatechange = function () {
if (this.readyState == 4) {
if (this.status == 200) {
// console.log(this.responseText);
var data = JSON.parse(this.responseText);
// console.log(data);
}
}
};
var formData = new FormData(form);
ajax.send(formData);
return false;
}
</script>
Get the web page content
To fetch the content of the web page, first, we will use express-formidable as our middleware. Also, we will require the modules required to read the web page and convert its HTML into DOM nodes. Write the following lines before http.listen function.
After that, we will create a POST route to crawl the web page.
app.post("/crawl-page", async function (request, result) {
var url = request.fields.url;
crawlPage(url);
result.json({
"status": "success",
"message": "Page has been crawled",
"url": url
});
});
Our web crawler runs in a separate function to crawl the web page. Then we will create the functions to crawl the web page and save its content in Mongo DB. Write the following functions at the top of your server.js file.
function getTagContent(querySelector, content, pageUrl) {
var tags = content.querySelectorAll(querySelector);
var innerHTMLs = [];
for (var a = 0; a < tags.length; a++) {
var content = "";
var anchorTag = tags[a].querySelector("a");
if (anchorTag != null) {
content = anchorTag.innerHTML;
} else {
content = tags[a].innerHTML;
}
content = content.replace(/\s+/g,' ').trim();
if (content.length > 0) {
innerHTMLs.push(content);
}
}
return innerHTMLs;
}
function crawlPage(url, callBack = null) {
var pathArray = url.split( '/' );
var protocol = pathArray[0];
var host = pathArray[2];
var baseUrl = protocol + '//' + host;
io.emit("crawl_update", "Crawling page: " + url);
requestModule(url, async function (error, response, html) {
if (!error && response.statusCode == 200) {
var $ = cheerio.load(html);
// Get text
// console.log("------- with request module -------")
// console.log($.text());
// Get HTML
// console.log($.html());
var page = await database.collection("pages").findOne({
"url": url
});
if (page == null) {
var html = $.html();
var htmlContent = HTMLParser.parse(html);
var allAnchors = htmlContent.querySelectorAll("a");
var anchors = [];
for (var a = 0; a < allAnchors.length; a++) {
var href = allAnchors[a].getAttribute("href");
var title = allAnchors[a].innerHTML;
var hasAnyChildTag = (allAnchors[a].querySelector("div") != null)
|| (allAnchors[a].querySelector("img") != null)
|| (allAnchors[a].querySelector("p") != null)
|| (allAnchors[a].querySelector("span") != null)
|| (allAnchors[a].querySelector("svg") != null)
|| (allAnchors[a].querySelector("strong") != null);
if (hasAnyChildTag) {
continue;
}
if (href != null) {
if (href == "#" || href.search("javascript:void(0)") != -1) {
continue;
}
var first4Words = href.substr(0, 4);
if (href.search(url) == -1 && first4Words != "http") {
if (href[0] == "/") {
href = baseUrl + href;
} else {
href = baseUrl + "/" + href;
}
}
anchors.push({
"href": href,
"text": title
});
}
}
io.emit("crawl_update", htmlspecialchars("<a>") + " tags has been crawled");
var titles = await getTagContent("title", htmlContent, url);
var title = titles.length > 0 ? titles[0] : "";
io.emit("crawl_update", htmlspecialchars("<title>") + " tag has been crawled");
var h1s = await getTagContent("h1", htmlContent, url);
io.emit("crawl_update", htmlspecialchars("<h1>") + " tags has been crawled");
var h2s = await getTagContent("h2", htmlContent, url);
io.emit("crawl_update", htmlspecialchars("<h2>") + " tags has been crawled");
var h3s = await getTagContent("h3", htmlContent, url);
io.emit("crawl_update", htmlspecialchars("<h3>") + " tags has been crawled");
var h4s = await getTagContent("h4", htmlContent, url);
io.emit("crawl_update", htmlspecialchars("<h4>") + " tags has been crawled");
var h5s = await getTagContent("h5", htmlContent, url);
io.emit("crawl_update", htmlspecialchars("<h5>") + " tags has been crawled");
var h6s = await getTagContent("h6", htmlContent, url);
io.emit("crawl_update", htmlspecialchars("<h6>") + " tags has been crawled");
var ps = await getTagContent("p", htmlContent, url);
io.emit("crawl_update", htmlspecialchars("<p>") + " tags has been crawled");
var object = {
"url": url,
"anchors": anchors,
"title": title,
"h1s": h1s,
"h2s": h2s,
"h3s": h3s,
"h4s": h4s,
"h5s": h5s,
"h6s": h6s,
"ps": ps,
"time": new Date().getTime()
};
try {
await database.collection("pages").insertOne(object);
} catch (e) {
console.log(e);
}
io.emit("page_crawled", object);
io.emit("crawl_update", "Page crawled.");
} else {
io.emit("crawl_update", "Page already crawled.");
}
if (callBack != null) {
callBack();
}
}
});
}
If you refresh the page now and enter the URL of any web page and hit enter, you will see its content is stored in the Mongo DB database named web_crawler. To check the data from Mongo DB, you can download a software named Mongo DB Compass.
Show data in DataTable
Now whenever a new web page is crawled, we will display that in a table. We will be using a library called DataTable. We will also include the socket IO library for real-time communication. So include those files in your index.ejs:
Then we will create a row with 2 columns. On the left column, we will create a table to display all crawled tables. And on the right column, we will display all crawled updates e.g. “headings has been crawled”, “paragraphs” has been crawled” etc.
Then we need to initialize the data table library. Also, attach event listeners for crawl updates. Crawl updates will ab prepended in the <ul> list. The complete crawled web pages will be appended in the data table.
var table = null;
var socketIO = io("http://localhost:3000/");
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
window.addEventListener("load", function () {
table = $('#my-table').DataTable({
"order": [[ 2, "asc" ]]
});
});
socketIO.on("crawl_update", function (data) {
// console.log(data);
var html = "";
html += `<li class="list-group-item">` + data + `</li>`;
document.getElementById("my-updates").innerHTML = html + document.getElementById("my-updates").innerHTML;
document.getElementById('my-updates').scrollTop = 0;
});
socketIO.on("page_crawled", function (data) {
// console.log(data);
var date = new Date(data.time);
var time = date.getDate() + " " + months[date.getMonth() + 1] + ", " + date.getFullYear() + " - " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
table.row.add( [
"<a href='/page/" + encodeURIComponent(data.url) + "'>" + data.url + "</a>",
data.title,
time
] ).draw( false );
});
Now you will see the data in the table when you crawl some page. You can crawl as many pages as you want.
Fetch data from Mongo DB
At this point, data in the data table is only displayed when you crawl some page. But when you reload the page, the data table will be empty. However, the data is still stored in the database. Our web crawler has saved all the crawled pages in a Mongo DB collection named “pages”. So we need to populate the previously saved pages from the database in the data table when the page loads.
First, change our “/” GET route in the server.js to the following:
app.get("/", async function (request, result) {
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var pages = await database.collection("pages").find({})
.sort({
"time": -1
}).toArray();
for (var index in pages) {
var date = new Date(pages[index].time);
var time = date.getDate() + " " + months[date.getMonth() + 1] + ", " + date.getFullYear() + " - " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
pages[index].time = time;
}
result.render("index", {
"pages": pages
});
});
And in our index.ejs inside the <tbody> tag, we will display all the pages.
If you refresh the page now, you will see all pages in the data table. You will only see the URL, title, and the time when the page was recently crawled. But we need to know the anchor tags on that page, all the headings, and paragraphs in it.
Show page content
Click on any of the links from the data table and it will take you to an error page. We need to convert that error page into a detailed page. Create a GET route in our server.js that will fetch the page from the database and send it to an HTML file.
app.get("/page/:url", async function (request, result) {
var url = request.params.url;
var page = await database.collection("pages").findOne({
"url": url
});
if (page == null) {
result.render("404", {
"message": "This page has not been crawled"
});
return false;
}
result.render("page", {
"page": page
});
});
In your views folder, create a file named 404.ejs that will be displayed when the URL is not been crawled yet.
Along with all the data on the web page, it will also show 2 buttons to “delete” and to “reindex”. Delete simply means to delete the page from the database. “Reindex” means to re-crawl the web page to fetch updated content. First, we will create a POST route for deleting the page in our server.js file.
app.post("/delete-page", async function (request, result) {
var url = request.fields.url;
await database.collection("pages").deleteOne({
"url": url
});
io.emit("page_deleted", url);
var backURL = request.header('Referer') || '/';
result.redirect(backURL);
});
And in our index.ejs we will attach an event listener that will be called when the page is deleted. In that function, we will simply remove that row from the data table.
Remove specific row from DataTable.js
socketIO.on("page_deleted", function (url) {
table
.rows( function ( idx, data, node ) {
return data[0].includes(url);
} )
.remove()
.draw();
});
This will search for the first row with the URL in its content and remove it. After removal, it will re-render the data table to reload the table.
Re-index the page
Now we need to add a function to re-index the page, which means to get the updated content of the page. As we did for delete, we will also create a form for re-indexing.
<div class="col-md-1">
<form method="POST" action="/reindex" onsubmit="return confirm('Are you sure you want to re-index this page ?');">
<input type="hidden" name="url" value="<%= page.url %>" required />
<input type="submit" class="btn btn-primary" value="Re-index" />
</form>
</div>
This will show a “Re-index” button along with a delete button. Then we need to create a POST route in our server.js:
app.post("/reindex", async function (request, result) {
var url = request.fields.url;
await database.collection("pages").deleteOne({
"url": url
});
io.emit("page_deleted", url);
crawlPage(url, function () {
var backURL = request.header('Referer') || '/';
result.redirect(backURL);
});
});
That’s it. If you run the code now, you will be able to re-index the page and update your database. You can try this feature by first crawling a URL today. Then try to crawl the same URL again a few days or weeks later.
So that’s how you can create a simple web crawler in Node JS and Mongo DB. You can check our financial ledger tutorial to learn how to create a financial ledger in Node JS and Mongo DB.
In this tutorial, we are going to teach you how you can track your hand rotation using Leap Motion Controller. Leap Motion Controller is a device that allows you to detect your hand motions and gestures, and perform any desired action on them. It provides SDK in Java, Javascript, Python, C++, and Unreal Engine. We will be using Javascript in this tutorial.
You can buy the Leap Motion Controller from their official website. After buying, connect the device with your computer using a USB or USB-C port. Then download the SDK from here and do the installation.
If you prefer the video tutorial:
From the above video, you can see what we are going to create. So you will need a Doctor Strange time-spell video and some images. You will also need the Leap JS library to connect with Leap Motion Controller. You can find all 3 in the source files below.
Setup the Project
The following code helps you to reverse a video and concatenate 2 videos using FFmpeg:
Then apply some CSS styles to display the video, image, and text properly.
<style>
/* remove margin and set background color as black */
body {
margin: 0px;
background-color: black;
}
/* set width of time spell and its position */
#time-spell {
width: 1200px;
position: absolute;
left: -275px;
top: 30px;
}
/* set width and height of image and fit inside container */
#image {
width: 800px;
height: 600px;
object-fit: contain;
}
/* set color, position, alignment, font size and background color of image text */
#title {
color: white;
position: relative;
bottom: 129px;
text-align: center;
font-size: 70px;
background-color: rgba(0, 0, 0, 0.7);
}
</style>
Then create an array of images and their text in Javascript.
<script>
// list of images and their text
var images = [{
"src": "images/2021.JPG",
"title": "2021"
}];
// index is out of bound right now
var index = images.length;
</script>
After that, initialize some variables that we will be using throughout the tutorial.
// total duration of time spell video
var totalDuration = 40;
// state of time spell (back or forward)
var state = "";
// previous state of time spell
var previousState = "";
// hand rotation from Leap Motion Controller
var handRotation = "";
// is time spell start rotating
var isMediaPlaying = false;
// video tag
var media = document.getElementById("time-spell");
Leap motion controller connection
Now is the time to connect our Javascript app with Leap Motion Controller.
// setup Leap loop with frame callback function
var controllerOptions = {};
Leap.loop(controllerOptions, function(frame) {
// get all hands in frame
var hands = frame.hands;
// if there is any hand
if (hands.length > 0) {
// get the first hand
var hand = hands[0];
// ignore if the hand is left
if (hand.isLeft) {
return false;
}
// get direction of hand (x,y,z co-ordinates)
var axis = hand.direction;
// get x-axis
axis = axis[0];
if (axis > 0 && axis < 0.9) {
// if hand is rotated to right
handRotation = "right";
} else if (axis > -0.09 && axis < -0.01) {
// if hand is in center (no rotation)
handRotation = "center";
media.pause();
} else if (axis < 0 && axis < -0.5) {
// if hand is rotated to left
handRotation = "left";
}
} else {
// if there is no hand in the frame, simply pause the time spell
if (media != null) {
media.pause();
}
}
});
For explanation, comments have been added with each line. Now we need to call a function every 0.5 seconds and rotate the time spell to the left if the hand is rotated to left, and right if the hand is rotated to right. Also, it will call a function to change the image as per hand rotation.
// called every 0.5 seconds
setInterval(function () {
// if hand is rotating to right
if (handRotation == "right") {
// only called once to rotate time spell
if (state == "") {
// rotate to right
mediaBackward();
}
// if time spell starts rotating
if (isMediaPlaying) {
// show next image
nextImage();
}
// make it empty to give a delay to show next image
handRotation = "";
} else if (handRotation == "left") {
// only called once to rotate time spell
if (state == "") {
// rotate to left
mediaForward();
}
// if time spell starts rotating
if (isMediaPlaying) {
// show previous image
previousImage();
}
// make it empty to give a delay to show previous image
handRotation = "";
} else if (handRotation == "center") {
// pause the time spell, when hand is in center of Leap Motion Controller
media.pause();
}
}, 500);
Now we just need to create these 4 functions called from the above code.
// when hand is rotated right
function mediaBackward() {
// "back" means reverse
state = "back";
// starts rotating time spell
media.play();
// current time of video
var currentTime = media.currentTime;
// if video stops playing
if (currentTime <= 0) {
// starts from middle to move right
currentTime = totalDuration / 2;
}
// this condition becomes true only when the
// time spell was rotating forward (left side)
if (previousState != state) {
// change direction of time spell
var newTime = totalDuration - currentTime;
media.currentTime = newTime;
}
}
// when hand is rotated left
function mediaForward() {
// "forward" means original video
state = "forward";
// starts rotating time spell
media.play();
// current time of video
var currentTime = media.currentTime;
// if video stops playing
if (currentTime <= 0) {
// starts from zero to move left
currentTime = totalDuration;
}
// this condition becomes true only when the
// time spell was rotating backward (right side)
if (previousState != state) {
// change direction of time spell
var newTime = totalDuration - currentTime;
media.currentTime = newTime;
}
}
// show next image from images array
function nextImage() {
// increment the index
index++;
// check if the index is out of bound
if (index >= images.length || index < 0) {
return false;
}
// get source of image
var src = images[index].src;
// get text of image
var title = images[index].title;
// update image
document.getElementById("image").setAttribute("src", src);
// update title
document.getElementById("title").innerHTML = title;
}
// show previous image from images array
function previousImage() {
// decrement the index
index--;
// check if the index is out of bound
if (index < 0 || index >= images.length) {
return false;
}
// get source of image
var src = images[index].src;
// get text of image
var title = images[index].title;
// update image
document.getElementById("image").setAttribute("src", src);
// update title
document.getElementById("title").innerHTML = title;
}
Finally, 2 event listeners are added to check when the time spell is rotating and when is paused. Based on that, we will set our variables to change images.
// called when the time spell starts rotating
media.onplaying = function () {
// set the variable to true
setTimeout(function () {
isMediaPlaying = true;
}, 100);
};
// called when the time spell is paused
media.onpause = function () {
// get the previous state
previousState = state;
// set the current state as empty
state = "";
// set the variable to false
isMediaPlaying = false;
};
Following this tutorial, you will be able to track hand rotations using Leap motion controller and Javascript.
This tutorial will discuss how you can create a JWT authentication system (Login, Registration, and Logout) using JSON Web Token in Node JS and Mongo DB. You can follow the video below if you prefer a video tutorial:
First, make sure you have downloaded and installed Node JS and Mongo DB. You can also download Mongo DB Compass to view the database.
Setup the Project
First, create an empty folder anywhere on your computer and open the command prompt in it. Once CMD is open inside the folder, run the following command:
express: A framework used for better routing, middleware etc.
express-formidable: To get the values from AJAX.
mongodb: To interact with database.
http: To handle HTTP requests.
bcrypt: To encrypt and decrypt the password.
jsonwebtoken: To generate JSON Web Token used for authentication.
ejs: View engine to display HTML pages.
Now, create a file named “server.js” at the root of your folder. To start the server you can use the following command:
node server.js
But every time you made some changes in the server.js file, you have to run that command again. To prevent this, you can install the nodemon module using the following command:
npm install -g nodemon
Then you can use the following command to keep the server running:
nodemon server.js
Start the Server
Open your server.js file and write the following code in it:
// include express module
var express = require("express");
// create instance of express
var app = express();
// use express-formidable middleware
var formidable = require("express-formidable");
app.use(formidable());
// include mongodb module
var mongodb = require("mongodb");
// get mongodb client
var mongoClient = mongodb.MongoClient;
// get ObjectId, it is unique for each document
var ObjectId = mongodb.ObjectId;
// create http server from express instance
var http = require("http").createServer(app);
// include bcrypt module
var bcrypt = require("bcrypt");
// include jsonwebtoken module
var jwt = require("jsonwebtoken");
// random secret string
var accessTokenSecret = "myAccessTokenSecret1234567890";
// use public folder for css and js files
app.use(express.static(__dirname + "public"));
// use ejs engine to render html files
app.set("view engine", "ejs");
var mainURL = "http://localhost:3000";
// start the server at port 3000
http.listen(3000, function () {
console.log("Server started.");
// connect with mongodb
mongoClient.connect("mongodb://localhost:27017", function (error, client) {
// database name will be "jwt_authentication"
var database = client.db("jwt_authentication");
console.log("Database connected");
});
});
This will include all the necessary modules and start the server at port 3000. The module used for JWT authentication is named jsonwebtoken. You can access it from your browser with the following link:
http://localhost:3000/
You will see an error because there isn’t any route for the home page. Open your CMD and you will see 2 messages:
“Server started.”
“Database connected”
If you open the Mongo DB Compass you will not see a database. Because database will be created only there is at least one collection in it. So our database will be created when there is at least one user in our database.
User Registration
Go to the following address in your browser:
http://localhost:3000/signup
First, we are going to create a route in our server.js right after the database is connected.
server.js
// route for signup requests
app.route("/signup")
// get request accessed from browser
.get(function (request, result) {
// render signup.ejs file inside "views" folder
result.render("signup");
});
Now we need to create a folder named views at our root folder. Inside this folder, create a file named signup.ejs (note the extension). Also create 2 more files, header.ejs and footer.ejs.
header.ejs
<!-- menu items will be created using Javascript -->
<ul id="main-menu">
</ul>
<script>
// variables used in all files
var mainURL = "http://localhost:3000";
var accessTokenKey = "accessToken";
var user = null;
</script>
<script>
function doRegister(form) {
// disable the submit button and show "Loading..." text
form.submit.setAttribute("disabled", "disabled");
form.submit.value = "Loading...";
// create AJAX object
var ajax = new XMLHttpRequest();
// 1. method is POST
// 2. path where request will be sent
// 3. request will be asynchronous
ajax.open("POST", mainURL + "/signup", true);
// called everytime status of request changes
ajax.onreadystatechange = function () {
// when response is received from server
if (this.readyState == 4) {
// if the request is OK
if (this.status == 200) {
// response received from server
console.log(this.responseText);
// enable the submit button
form.submit.removeAttribute("disabled");
form.submit.value = "Register";
// convert the JSON string into Javascript object
var response = JSON.parse(this.responseText);
// display message
alert(response.message);
// if the user is created, then redirect to login
if (response.status == "success") {
window.location.href = "/login";
}
}
// if there is an internal server error
if (this.status == 500) {
console.log(this.responseText);
}
}
};
// create form data object from form
var formData = new FormData(form);
// actually sending the AJAX request
ajax.send(formData);
// prevent the form from submitting
return false;
}
</script>
This will show a form with input fields for name, email and password. Now we need to create a POST route in our server.js. We will chain the POST route after our “/signup” GET route.
// route for signup requests
app.route("/signup")
// get request accessed from browser
.get(function (request, result) {
// render signup.ejs file inside "views" folder
result.render("signup");
})
// post request called from AJAX
.post(async function (request, result) {
// get values from signup form
var name = request.fields.name;
var email = request.fields.email;
var password = request.fields.password;
// check if email already exists
var user = await database.collection("users").findOne({
"email": email
});
if (user != null) {
result.json({
"status": "error",
"message": "Email already exists."
});
return true;
}
// encrypt the password
bcrypt.hash(password, 10, async function (error, hash) {
// insert in database
await database.collection("users").insertOne({
"name": name,
"email": email,
"password": hash,
"accessToken": ""
});
// send the response back to client
result.json({
"status": "success",
"message": "Signed up successfully. You can login now."
});
});
});
If you run the code now, you will see a registration form. You can enter your name, email, and password and hit enter. The first time, you will see a success message and you will be redirected to the login route. You can refresh your Mongo DB Compass, and you will see your database and one collection of “users”. Inside this collection, you will see your document inserted. If you enter the same email again, you will get an error.
User Login – JWT authentication
Go to the following address in your browser:
http://localhost:3000/login
First, we are going to create a route in our server.js right after the signup route.
server.js
// route for login requests
app.route("/login")
// get request accessed from browser
.get(function (request, result) {
// render login.ejs file inside "views" folder
result.render("login");
});
Then create a file named login.ejs and write the following code in it:
<script>
function doLogin(form) {
// disable the submit button and show "Loading..." text
form.submit.setAttribute("disabled", "disabled");
form.submit.value = "Loading...";
// create AJAX object
var ajax = new XMLHttpRequest();
// 1. method is POST
// 2. path where request will be sent
// 3. request will be asynchronous
ajax.open("POST", mainURL + "/login", true);
// called everytime status of request changes
ajax.onreadystatechange = function () {
// when response is received from server
if (this.readyState == 4) {
// if the request is OK
if (this.status == 200) {
// response received from server
console.log(this.responseText);
// enable the submit button
form.submit.removeAttribute("disabled");
form.submit.value = "Login";
// convert the JSON string into Javascript object
var response = JSON.parse(this.responseText);
// if user is logged in successfully
if (response.status == "success") {
// get access token from server
var accessToken = response.accessToken;
// save in local storage
localStorage.setItem("accessToken", accessToken);
// redirect to home page
window.location.href = "/";
} else {
// display message
alert(response.message);
}
}
// if there is an internal server error
if (this.status == 500) {
console.log(this.responseText);
}
}
};
// create form data object from form
var formData = new FormData(form);
// actually sending the AJAX request
ajax.send(formData);
// prevent the form from submitting
return false;
}
</script>
This will show a form with input fields for email and password. Now we need to create a POST route in our server.js. We will chain the POST route after our “/login” GET route.
// route for login requests
app.route("/login")
// get request accessed from browser
.get(function (request, result) {
// render login.ejs file inside "views" folder
result.render("login");
})
// post request called from AJAX
.post(async function (request, result) {
// get values from login form
var email = request.fields.email;
var password = request.fields.password;
// check if email exists
var user = await database.collection("users").findOne({
"email": email
});
if (user == null) {
result.json({
"status": "error",
"message": "Email does not exists."
});
return false;
}
// check if password is correct
bcrypt.compare(password, user.password, async function (error, isVerify) {
if (isVerify) {
// generate JWT of user
var accessToken = jwt.sign({
"email": email
}, accessTokenSecret);
// update JWT of user in database
await database.collection("users").findOneAndUpdate({
"email": email
}, {
$set: {
"accessToken": accessToken
}
});
result.json({
"status": "success",
"message": "Login successfully.",
"accessToken": accessToken
});
return false;
}
result.json({
"status": "error",
"message": "Password is not correct."
});
});
});
If you run the code now, you will see a login form. You can enter your email and password and hit enter. If the credentials are correct, then it will save the access token in your local storage in your browser, and you will be redirected to the home page. You can refresh your Mongo DB Compass, and you will see that the accessToken field of the user has been updated. If you enter the wrong credentials, then it will not update the database.
Home Page
Now we need to create a route for our home page in server.js:
// route for home page
app.get("/", function (request, result) {
result.render("index");
});
Create a file named index.ejs inside views folder and write the following code in it:
<%- include ("header") %>
<%- include ("footer") %>
Now comes the footer part. Following will be the code of your footer.ejs file:
footer.ejs
<script>
// get user on page load
window.addEventListener("load", function () {
getUser();
});
function getUser() {
// check if user is logged in
if (localStorage.getItem(accessTokenKey)) {
// call AJAX to get user data
var ajax = new XMLHttpRequest();
ajax.open("POST", mainURL + "/getUser", true);
ajax.onreadystatechange = function () {
if (this.readyState == 4) {
if (this.status == 200) {
// console.log(this.responseText);
var response = JSON.parse(this.responseText);
if (response.status == "success") {
// user is logged in
window.user = response.user;
} else {
// user is logged out
localStorage.removeItem(accessTokenKey);
}
showMainMenu();
}
if (this.status == 500) {
console.log(this.responseText);
}
}
};
var formData = new FormData();
formData.append("accessToken", localStorage.getItem(accessTokenKey));
ajax.send(formData);
return false;
}
showMainMenu();
}
function doLogout() {
// send beacon to server before redirecting
var formData = new FormData();
formData.append("accessToken", localStorage.getItem(accessTokenKey));
navigator.sendBeacon(mainURL + "/logout", formData);
// remove access token from local storage
localStorage.removeItem(accessTokenKey);
return true;
}
function showMainMenu() {
var html = "";
// if user is logged in
if (localStorage.getItem(accessTokenKey)) {
html += `<li>
<a href='/login' onclick='return doLogout();'>Logout</a>
</li>`;
} else {
html += `<li>
<a href='/login'>Login</a>
</li>`;
html += `<li>
<a href='/signup'>Signup</a>
</li>`;
}
// show in main menu
document.getElementById("main-menu").innerHTML = html;
}
</script>
This will show login and signup links if the user is not logged in. If the user is logged in, then it will display a logout button. When the logout button is clicked, it will be redirected to the “/login” route. But before redirecting, it will send a beacon to the server to empty the accessToken field in the database. So we need to create POST routes for “/getUser” and for “/logout”:
// return user data using access token
app.post("/getUser", async function (request, result) {
var accessToken = request.fields.accessToken;
var user = await database.collection("users").findOne({
"accessToken": accessToken
});
if (user == null) {
result.json({
"status": "error",
"message": "User has been logged out. Please login again."
});
return false;
}
result.json({
"status": "success",
"message": "Data has been fetched.",
"user": user
});
});
// empty the access token of user (logout)
app.post("/logout", async function (request, result) {
var accessToken = request.fields.accessToken;
await database.collection("users").findOneAndUpdate({
"accessToken": accessToken
}, {
$set: {
"accessToken": ""
}
});
result.json({
"status": "success",
"message": "User has been logged out."
});
});
With that, your authentication is completed. You can log in and log out now. Once you log out and refresh the Mongo DB Compass, you will see the accessToken field becomes empty. And it will again have the value once the user logged in again.
Why we used Beacon ?
You might have noticed that we have used Beacon instead of AJAX when the logout button is clicked. The reason for this is when the browser navigates from one page to another, the AJAX calls are sometimes aborted by the browser. To prevent this, we use beacons.
Get Data using AJAX
Now that your authentication system is done, you might want to show data to logged-in users only. So for example, you have a blogging website and you want to show your blog posts to logged-in users only. So you need to do the following steps.
1) Create Variable and a Function
In the file where you want to fetch and display the data, create the following variable and a function in Javascript.
<script>
var onPostsPage = true;
function getPosts() {
// call ajax
}
</script>
If you want to learn how to call an AJAX request and show data from it, follow this tutorial.
2) Call Function if Variable is Set
In your footer.ejs, when the user is logged-in, check if this variable is set and is true. If yes, then simply call that function.
// user is logged in
window.user = response.user;
if (typeof onPostsPage !== "undefined" && onPostsPage) {
getPosts();
}
So, that’s how you can create a JWT authentication system In Node JS and Mongo DB.
In this tutorial, we will teach you how you can put a watermark on an image uploaded by a user. You might have seen websites that allow you to upload images, but when you try to download them, they put their watermark on your image and ask you to pay to remove it. So let’s learn how they do this.
First I am going to create a form where we will show the input type file to select the image. Its method will be POST and the action will be “index.php”. The encoding (enctype) type must be multipart/form-data. This is necessary for uploading files. Then we are going to show a label that says “select file” and an input type file. The name attribute on the input type file will be used on the PHP side. It will also have an attribute accept=”image/*”, which means this input field will accept only image type, for example, PNG or JPEG, etc, but no PDF or DOCX type of files will be selected in this field. Finally, the form will have a submit button. Submit button will have a name attribute so we can know when this form is submitted.
If you run the code now, you will see a form with an input type file and a submit button. Now we need to write the PHP code to handle this form.
Apply the watermark
First, we will check if the form is submitted.
<?php
if (isset($_POST["submit"]))
Then we will save the user uploaded image in a file named “image.png” because, in order to put a watermark on it, the file must be stored in our server.
Then we need to get this image as an object. So call imagecreatefromjpeg function if your image was a JPEG image, imagecreatefrompng if it is a PNG. Parameter will be the path of saved image file.
$image_obj = imagecreatefromjpeg("image.png");
Similarly, we will create an object of our watermark image, which is a PNG image.
$watermark = imagecreatefrompng("watermark.png");
Then we will define the margin from the right and from the bottom, since we do not want the watermark to stick to the corners, we want a little margin.
$margin_right = 10;
$margin_bottom = 10;
Then we are going to get the watermark image width and height in a separate variable.
Now comes the tricky part. We need to tell the X and Y coordinates of original image where we will place the watermark. The X position from left will be width of uploaded image, minus width of watermark image, minus margin from right.
Our destination image is the image uploaded by user. Source image is the watermark image. Destination x and y are just we calculated, I will explain this as well. And source x and y will be zero, because we do not want any margin in our watermark image. And finally the width and height of our source image which is watermark image.
width of user image = 2560
width of watermark image = 640
margin from right = 10
2560 - 640 - 10 = 1910 = x
height of user image = 1600
height of watermark image = 345
margin from bottom = 10
1600 - 345 - 10 = 1245 = y
First is destination_x variable. The width of image object is 2560px, minus the width of watermark is 640px, minus the margin right which is 10. That equals 1910, so our watermark will be placed at 1910 on X axis from left. Now come to destination_y variable, height of user uploaded image is 1600px, it will be different for each image because user can upload image of any resolution. I am writing these values as sample. So the 1600px is the height of user uploaded image. Minus the height of watermark, which is 345px. Minus margin from bottom that is 10. That equals 1245. So our watermark image will be placed at 1245 on Y axis from top.
1910 + 640 = 2550
1245 + 345 = 1590
Now when the watermark is placed at 1910 from left and it has a width of 640px, then that equals 2550 which is 10px less than the width of user image, which is 2560px. So there is a difference of 10px, which means there will be a margin of 10px from right. Similarly, when the watermark is placed at 1245 from the top and it has a height of 345px, then that equals 1590 which is again 10px less than the height of the user image, which is 1600px. So it will have a margin of 10px from the bottom.
Generating watermarked image
Now the watermark image object has been copied to the user uploaded image object using imagecopy function above. Now we need to generate that image.
imagepng($image_obj, "output.png");
The first parameter will be an image object, which is a user object along with a watermark object because the watermark image has been copied to it. And second, will be the path of the output file.
Finally, we are going to destroy the image objects we just created for user-uploaded images and watermark image to free up the memory.
Also, we saved the user image using the move uploaded file function because we need the image to be stored before putting a watermark on it. Now the watermark is placed, so we have no need for the original user-selected picture, so we are going to delete it using the unlink function.
unlink("image.png");
If you run the code now, select an image, and upload it, you will see that the original image was saved as image.png and the output.png is being rendered for a few seconds. When the output.png is completely rendered then the original image.png was automatically deleted because we have used the unlink function.
The final image will be of the same quality as of original image. If you face any problems in following this, kindly mention them in the comments section below.
How you can also resize the image without stretching in PHP by following this tutorial.