Hyvor Developer

Image Upload with AJAX, PHP, and MYSQL - The Beginner's Guide

Welcome to Hyvor Developer again!

In this tutorial, we will talk about how to upload an image to the server with AJAX, validate it with PHP, save it in the server, and finally, we will save the path of the image in the database to retrieve the image later. For more security, we will be using MYSQLI prepared statements to save the path in the database.

This tutorial will have 3 main parts.

First of all, you need to understand allowing users to upload something into your server can lead to a huge security risk, if you don't do the validation correctly. So, the most important part that you should consider is validation.

Creating the HTML Form

Let's create our HTML form first.


<html>
<head>
    <title>Image Upload with AJAX, PHP and MYSQL</title>
</head>
<body>
<form onsubmit="submitForm(event);">
    <input type="file" name="image" id="image-selecter" accept="image/*">
    <input type="submit" name="submit" value="Upload Image">
</form>
<div id="uploading-text" style="display:none;">Uploading...</div>
<img id="preview">
</body>
</html>


Add submitForm(event) function as the value of the onsubmit attribute. Inside the form, we have two elements.

First one is a file select input to let the user select a file. The accept="image/*" attribute says the browser to allow the user to select only images. By using this, we can tell the browser to open the correct app for the user, mainly in mobile systems like Android. We will use the id attribute later to select this element in Javascript. But, the name attribute is optional as we do not submit the form directly.

The second one is a submit button to submit the form. The submitForm(event) function will be called when this button is clicked. Note that event is a variable set by the browser which contains details and functions about the current form submit event. (We will create this function in the next step).

There are two additional elements to show uploading text and preview the image after it is uploaded. (I'll call them UI handling elements in this tutorial)

Uploading the Image with AJAX

In this step, we will be writing the Javascript code to handle the UI and upload the image. First of all add a <script> tag before the </body> tag.


<script type="text/javascript">
	// your code will go here
</script>
</body>
</html>


Why before the </body> tag? It is a great place to add your Javascript code as this code will be executed after the HTML elements are loaded. So, functions like getElementById, getElementsByClassName will return the correct results.

If you take a look at our form again, you will understand that a function called submitForm(event) will be called on form submit, because we defined it in the form's onsubmit attribute. Let's create that function first.


function submitForm(event) {
	// prevent default form submission
	event.preventDefault();
	uploadImage();
}


event.preventDefault() prevents the default form submission. Next, let's jump to the uploadImage() function. This function can be a little bit complex, but I'll go through each line carefully.


function uploadImage() {
	var imageSelecter = document.getElementById("image-selecter"),
		file = imageSelecter.files[0];
	if (!file) 
		return alert("Please select a file");
	// clear the previous image
	previewImage.removeAttribute("src");
	// show uploading text
	uploadingText.style.display = "block";
	// create form data and append the file
	var formData = new FormData();
	formData.append("image", file);
	// do the ajax part
	var ajax = new XMLHttpRequest();
	ajax.onreadystatechange = function() {
		if (this.readyState === 4 && this.status === 200) {
			var json = JSON.parse(this.responseText);
			if (!json || json.status !== true) 
				return uploadError(json.error);

			showImage(json.url);
		}
	}
	ajax.open("POST", "upload.php", true);
	ajax.send(formData); // send the form data
}


Wait! where the previewImage and uploadingText variables came from? They are global variables which our UI handling elements are stored in. Declare them in global scope as we need them in several functions.


var previewImage = document.getElementById("preview"),	
	uploadingText = document.getElementById("uploading-text");


Now, I'll go through each line of the uploadImage() function.


var imageSelecter = document.getElementById("image-selecter"),
	file = imageSelecter.files[0];

	if (!file) 
		return alert("Please select a file");


imageSelecter saves the file select input element. This DOM object has the property files, which is an array of selected files. As we only select one file in this example, our file is at the index 0. Then, we store that in the variable file. The file variable is always an object that contains some information about the selected file. So, if it evaluates to false, it means that the user hasn't selected any file. In this case, we return from the function alerting a message.


// clear the previous image
previewImage.removeAttribute("src");
// show uploading text
uploadingText.style.display = "block";


In this part, we clear the image and show the uploading message in the UI.


// create form data and append the file
var formData = new FormData();
formData.append("image", file);


To upload an image with AJAX, we need a FormData object and append the image into it. FormData is a built-in object in Javascript. The first line creates an instance of the object. Then, we append our selected file to that object. In the append method, the first argument is the name of the parameter. The second one is the file. If you need to send more parameters like client ID with the AJAX request, you can use the same append method for it. Parameters should be added as key/value pairs.

Example:


formData.append("clientID", 2993); // can be used this as $_POST['clientID'] in PHP
 

Creating the AJAX request:


var ajax = new XMLHttpRequest();
ajax.onreadystatechange = function() {
    if (this.readyState === 4 && this.status === 200) {
        var json = JSON.parse(this.responseText);
        if (!json || json.status !== true) 
            return uploadError(json.error);

        showImage(json.url);
    }
}
ajax.open("POST", "upload.php", true);
ajax.send(formData); // send the form data


  • In the first line, we create an instance of XMLHttpRequest object and save it in ajax variable.
  • Next, we declare the onreadystatechange() function for our ajax request. This function will be executed each time the ready state of the AJAX request changes. In each time it checks whether the this.readyState is 4 and this.responseText is 200. (This is the recommended way to check whether an AJAX request is successfully completed)
  • On success, we will work with some JSON data. So, we need to return JSON data with PHP as the response. (To understand what's happening on success it's better to explain it after creating the PHP part.)
  • Next, we open an asynchronous POST request to upload.php.
  • Finally, we send the formData variable we created earlier.

Did we miss something? If you have used AJAX for POST request earlier, you should have seen this.


ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");


It is important to notice that this code is not needed when using the FormData object to send data.

Noticed there are uploadError() and showImage() functions inside the AJAX on success part? Here's the code for it.


function uploadError(error) {
    // called on error
    alert(error || 'Something went wrong');
}
function showImage(url) {
    previewImage.src = url;
    uploadingText.style.display = "none";
}


The uploadError() function alerts its first parameter (error) or the 'Something went wrong' message if the error variable does not evaluate to true. Using || operator is a common way of declaring default values. (See more here.)

The showImage() function shows the image and hides the "Uploading..." message.

Yay! we just finished the AJAX part! Let's create the back-end with PHP.

Creating the AJAX Request Handler with PHP

Which method should we use?

First of all, if you need to learn the best way to create an AJAX request handler I strongly recommend this tutorial. Simply, the method we are going to use is writing handler code inside a try-catch block and throw exceptions on errors.

Here's the basic structure of this method.


<?php
try {
    // the code goes here
    if ($anythingFails)
        throw new Exception('My error message');
    // if everything is fine
    exit(json_encode(
        array (
            'status' => true,
            'additionalData1' => '...',
            'additionalData2' => '...'
        )
    ));
} catch (Exception $e) {
    exit(json_encode(
        array (
            'status' => false,
            'error' => $e -> getMessage()
        )
    ));
}


Let's assume that we found that the uploaded image is not a valid one. Then we throw an exception and exit the script outputting a JSON response which has the status key to save the status of the request (false in this case) and error key to store the error message. $e -> getMessage() function returns the string we sent as the first parameter of the throw new Exception() statement.

If everything went fine, we exit the script outputting a JSON response with status set to true and some additional data which are needed to be sent to the client-side. (URL of the saved image)

Code the upload handler

The final code will look like this. If you see it hard, just skip the code and jump to the explanation.


<?php
$host = 'localhost';
$user = 'user';
$password = 'password';
$database = 'database';
$mysqli = new mysqli($host, $user, $password, $database);
try {
    if (empty($_FILES['image'])) {
        throw new Exception('Image file is missing');
    }
    $image = $_FILES['image'];
    // check INI error
    if ($image['error'] !== 0) {
        if ($image['error'] === 1) 
            throw new Exception('Max upload size exceeded');
            
        throw new Exception('Image uploading error: INI Error');
    }
    // check if the file exists
    if (!file_exists($image['tmp_name']))
        throw new Exception('Image file is missing in the server');
    $maxFileSize = 2 * 10e6; // in bytes
    if ($image['size'] > $maxFileSize)
        throw new Exception('Max size limit exceeded'); 
    // check if uploaded file is an image
    $imageData = getimagesize($image['tmp_name']);
    if (!$imageData) 
        throw new Exception('Invalid image');
    $mimeType = $imageData['mime'];
    // validate mime type
    $allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif'];
    if (!in_array($mimeType, $allowedMimeTypes)) 
        throw new Exception('Only JPEG, PNG and GIFs are allowed');
    
    // nice! it's a valid image
    // get file extension (ex: jpg, png) not (.jpg)
    $fileExtention = strtolower(pathinfo($image['name'] ,PATHINFO_EXTENSION));
    // create random name for your image
    $fileName = round(microtime(true)) . mt_rand() . '.' . $fileExtention; // anyfilename.jpg
    // Create the path starting from DOCUMENT ROOT of your website
    $path = '/examples/image-upload/images/' . $fileName;
    // file path in the computer - where to save it 
    $destination = $_SERVER['DOCUMENT_ROOT'] . $path;

    if (!move_uploaded_file($image['tmp_name'], $destination))
        throw new Exception('Error in moving the uploaded file');

    // create the url
    $protocol = stripos($_SERVER['SERVER_PROTOCOL'],'https') === true ? 'https://' : 'http://';
    $domain = $protocol . $_SERVER['SERVER_NAME'];
    $url = $domain . $path;
    $stmt = $mysqli -> prepare('INSERT INTO image_uploads (url) VALUES (?)');
    if (
        $stmt &&
        $stmt -> bind_param('s', $url) &&
        $stmt -> execute()
    ) {
        exit(
            json_encode(
                array(
                    'status' => true,
                    'url' => $url
                )
            )
        );
    } else 
        throw new Exception('Error in saving into the database');

} catch (Exception $e) {
    exit(json_encode(
        array (
            'status' => false,
            'error' => $e -> getMessage()
        )
    ));
}

Let's dive into our uploading process.

Connecting to the database

If you read the beginning of the article, I said that we will be saving the URL in a MYSQL database. To do that, we need a connection to the database. Keeping those kinds of connections and declaration at the top of a PHP script is really useful in many cases.

Mind to replace the database credentials with your ones.


$host = 'localhost';
$user = 'user';
$password = 'password';
$database = 'database';
$mysqli = new mysqli($host, $user, $password, $database);


Hereafter, all the upload validation should be done inside a try block.

Check the image upload


if (empty($_FILES['image']))
    throw new Exception('Image file is missing');


When we upload files to the server, PHP keeps data about those uploaded files in the $_FILES superglobal array. Remember that we set the name of the uploaded file as 'image' in our FormData object. Here we use that name to find our file inside the $_FILES array. Then, we will save it in a variable to access it easier in the later code.


$image = $_FILES['image'];


Understanding the $image variable

Now, $image is an array which contains some elements of data about the uploaded image. It's really important to understand these elements.

  • $image['tmp_name'] - The absolute path of the temporary file created in the server.
  • $image['name'] - The name of the original file (This sent by the browser. The most of browsers set this to the name of the file when it's in the computer of the user.)
  • $image['error'] - An integer representing any error occurred in uploading
  • $image['size'] - The size of the file

Check for upload time errors

If there's no upload time error, $image['error'] will have the value 0. But, if there's any we can't do furthermore validations, because the image uploading hasn't been successful. A common error that we get when uploading is exceeding the upload_max_filesize defined in the php.ini file. In this error, $image['error'] will have the value 1. By checking it, we can send the client side something like 'Max upload size exceeded'. If $image['error'] is not 0 or 1, it will be any other error. (Refer the manual to learn more about the other errors.)


if ($image['error'] !== 0) {
    if ($image['error'] === 1) 
        throw new Exception('Max upload size exceeded');
        
    throw new Exception('Image uploading error: INI Error');
}


Check the uploaded file

Here we will check whether the uploaded file exists in the server. The file_exists() function can be used.


if (!file_exists($image['tmp_name']))
    throw new Exception('Image file is missing in the server');


Note that here we are using tmp_name, not name, because the file path of the file which is saved temporarily in the server is stored in tmp_name. The name element only holds a string of the original name of the file which the user had in his computer.

Check the file size

Never let users upload unlimited size files to the server. Hackers/Attackers can overload your server by uploading huge-sized files.


$maxFileSize = 2 * 10e6; // = 2 000 000 bytes = 2MB
if ($image['size'] > $maxFileSize)
    throw new Exception('Max size limit exceeded'); 


Reminder: $image['size'] stores the size of the file.

Validating the image

The file is uploaded fine and the size of the file is perfect. Now, we need to check whether the uploaded file is a real image. The getimagesize returns an array of data of the image. If it returns false, it means that the file is not a valid image.


$imageData = getimagesize($image['tmp_name']);
if (!$imageData) 
    throw new Exception('Invalid image');


Here also we use $image['tmp_name'] to refer the file in the server.

The getimagesize() function returns the mime type of the image. ($imageData['mime']) We will use that in the next step.

Validating the Mime Type

Even it is an image, it can be any type among various image types. Restricting the users only to upload certain types of images is a good idea to prevent exploits. Here we only allow the user to upload jpg, png and gif images. You can change it according to your needs. (always refer a mime types table.)


$mimeType = $imageData['mime'];
$allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($mimeType, $allowedMimeTypes)) 
    throw new Exception('Only JPEG, PNG and GIFs are allowed');


First, we save the mime type of the image which we got from getimagesize() function in $mimeType variable. Then, we add allowed mime types into an array. Finally, we check whether the mime type of the uploaded image is in this array using the in_array function. Otherwise, we throw an exception and exit the script.

Finding the extension of the file


$fileExtention = strtolower(pathinfo($image['name'] ,PATHINFO_EXTENSION));


The pathinfo() function with the PATHINFO_EXTENSION flag will return the extension of the file. Why did we use $image['name'] here? The reason is that the original file name is stored in $image['name']. So, it has the correct extension. $image['tmp_name'] has the extension of the temporary file. In most cases, it is .tmp. Therefore $image['tmp_name'] cannot be used to find the extension.

Generating a random and unique file name

Generating a random file name is not that hard, but making it unique needs some thinking. Using the current timestamp is a common and useful way to generate a unique file name. The microtime() function returns the current UNIX timestamp in microseconds. To get the same file name to two uploaded files while using microtime() function, both uploads should be done at the same time without a difference of a microsecond. Adding a unique number (generated with mt_rand() function) can ensure the uniqueness more.


$fileName = round(microtime(true)) . mt_rand() . '.' . $fileExtention; // 3240930420523.jpg


In the end, we add the extension to the generated file name.

Defining the place to save the image in the server

Let's declare the $path variable to save the place where you gonna save the image in the server starting from the document root of your website. Adding $_SERVER['DOCUMENT_ROOT'] in the beginning will give the absolute path of the destination where you need to save the file.


$path = '/uploads/' . $fileName;
$destination = $_SERVER['DOCUMENT_ROOT'] . $path;


Saving the image

move_uploaded_file() function can be used to move the temporary file into the destination.


if (!move_uploaded_file($image['tmp_name'], $destination))
    throw new Exception('Error in moving the uploaded file');


When we upload a file to the server, it will be saved in the temporary folder of your server (/tmp folder). But, files in this folder are temporary as they mean. So, to make it permanent we have to move the uploaded file into another folder. As we are using this image file later using a URL, it's better to save it in a folder on our website. Hence, we declared the $destination to be a file path which is inside our website.

Creating the URL

Finding the protocol and domain name and adding it in front of the $path variable will return the URL we need.


$protocol = stripos($_SERVER['SERVER_PROTOCOL'],'https') === true ? 'https://' : 'http://';
$domain = $protocol . $_SERVER['SERVER_NAME'];
$url = $domain . $path;


Adding the URL to the database

Why do we add the URL to the database? In this tutorial, it is just to explain to you that it is possible. But, let's assume that you have a user management system. When you add a feature to users to upload their profile pictures, saving the profile picture's URL in the database is needed. So, to do that you can simply do something like following.


$stmt = $mysqli -> prepare('INSERT INTO image_uploads (url) VALUES (?)');
if (
    $stmt &&
    $stmt -> bind_param('s', $url) &&
    $stmt -> execute()
) {
    exit(
        json_encode(
            array(
                'status' => true,
                'url' => $url
            )
        )
    );
} else 
    throw new Exception('Error in saving into the database');


Also, there are some ways to save images directly in databases as blobs, but I personally recommend saving URLs while the image is saved in the server as many developers tend to use this one.

Finally, we send a JSON response to the client-side including the URL of the image. Great! You just finished the AJAX request handler part of this tutorial.

Let's go back to the step 2. Remember the onreadystatechange function in our AJAX request?


ajax.onreadystatechange = function() {
    if (this.readyState === 4 && this.status === 200) {
        var json = JSON.parse(this.responseText);
        if (!json || json.status !== true) 
            return uploadError(json.error);

        showImage(json.url);
    }
}


In this part, we use JSON.parse() method to parse the JSON string into a Javascript object. If json.status isn't true, we show an error message to the user and return from the function. If everything went fine, whoah! we are successful! Let's display the image in our UI element.

Finally,

I have a little gift for you. The Source Code for free download!

Conclusion

In this beginner's guide, we learned how to upload an image to the server securely using AJAX, PHP, and MYSQL. First, we allowed the user to select an image file. Then, we uploaded it with AJAX to the server. Then, we checked and validated the image and saved it in the server with PHP. Also, I gave an example about how to save URLs in MYSQL database to fetch it later.

I hope you enjoyed the tutorial. Thank you very much for reading! Never forget to share the tutorial. If you have any question to ask, leave a comment below.

Author SupunKavinda