Previously, I had written on uploading multiple images from a Vue Application to a Laravel Backend. In that solution, we made use of Javascript’s FormData Object. Here’s that article if you missed it:
In this solution, however, we would use Javascript’s FileReader API, which allows us to read the content of image files and handle those files in base64 format. Regardless of the backend language being used, base64 encoded images can be decoded on the server and stored locally in the filesystem (or on cloud storage).
Here’s some illustration to better understand the process of uploading a Base64 image. While interfacing with your web application, the user can select an image (using the input tag, with a type of file) from their device; the content of the selected image is read using the FileReader API, and we send the base64 string in Data URL format to a backend API.
On the backend, the base64 string is decoded from the string format to its original file object, and we can then store the decoded file and return the storage path/URL to the frontend.
Down to the Base-ics
What on earth is Base64?
Base64 or Radix 64 is a binary-to-text encoding system that is designed to allow binary data to be represented in ASCII string format. One common application of base64 encoding on the web is to encode binary data so it can be included in a data: URL.
Data: URL?
Data URLs are URLs that are prefixed with “data:”. They allow embedding of file content inline in documents (.html). Data URLs are composed of 3 major parts after the “data:” prefix:
data:[<mediatype>][;base64],<data>
The [<mediatype>]
part refers to the file format, and it could contain any one of the following values:
text/plain — for text files
image/jpeg or image/jpeg — for .png and .jpeg image files
application/pdf — for .pdf files etc.
The [<base64>]
part is optional and can be ignored if the data is non-textual. The last part is the textual (or non-textual) file content. Technically, this is the format in which we would send out images.
FileReader API
What’s this?
The FileReader
object lets web applications asynchronously read the contents of files (or raw data buffers) stored on the user’s computer, using File
or Blob
objects to specify the file or data to read. — MDN
When a user selects a file using the input tag, a “change” event is triggered; this event object contains a FileList object that lets you access the list of files selected with the <input type="file">
element.
The FileReader API has 4 methods for reading file contents:
The method we’re concerned with is the readAsDataURL method, as it allows us to read the content of a Blob file and return an object with a result
property containing a data: URL representing the file’s data.
Getting Started
First, be sure you have all the Vuejs requirements installed. We will be using the Vue CLI to serve our application; however, we won’t be scaffolding an entire Vue application with the CLI. Thanks to Vue CLI’s instant prototyping, we can serve a single Vue file.
Let’s set up our project directory.
instant prototyping](https://cli.vuejs.org/guide/prototyping.html), we can serve a single Vue file.
Let’s set up our project directory.
mkdir vue-base64 vue-base64/assets vue-base64/assets/styles
touch vue-base64/Index.vue && touch vue-base64/assets/styles/main.css
Our project directory would look something like this:
.
|--vue-base64
|-- Index.vue
|-- assets
|-- styles
|--main.css
We’ll write a basic structure for our image and input elements in our template section.
Template
<template>
<div>
<div class="container mt-10">
<div class="card bg-white">
<img :src="image" alt="card_image">
<input @change="handleImage" class="custom-input" type="file" accept="image/*">
</div>
</div>
</div>
</template>
Here, we create a card to house our image and input button. On the input tag, we’re listening for a change event and calling a “handleImage” method every time that event is fired. We’re also binding the image “src” attribute to a reactive property “image” (We’ll add this property to our data object under the script section), so the image is updated every time a new one is selected.
We’ll add a bit of styling in our CSS file (main.css).
CSS
* {
font-family: Arial, Helvetica, sans-serif;
}
body {
background: #d8dddb;
}
.container {
display: flex;
justify-content: center;
}
.mt-10 {
margin-top: 10rem;
}
.bg-white {
background: #fff;
}
.card {
height: 10rem;
width: 20rem;
border-radius: 10px;
padding: 20px;
text-align: center;
}
img {
width: 17rem;
}
Import the CSS file main.css
into Index.vue
like so;
<style>
@import './assets/style/main.css';
</style>
Script
<script>
import axios from 'axios';
export default {
name: 'home',
data() {
return {
image: '',
remoteUrl: ''
}
},
methods: {
handleImage(e) {
const selectedImage = e.target.files[0]; // get first file
this.createBase64Image(selectedImage);
},
createBase64Image(fileObject) {
const reader = new FileReader();
reader.onload = (e) => {
this.image = e.target.result;
this.uploadImage();
};
reader.readAsDataURL(fileObject);
},
uploadImage() {
const { image } = this;
axios.post('http://127.0.0.1:8081/upload', { image })
.then((response) => {
this.remoteUrl = response.data.url;
})
.catch((err) => {
return new Error(err.message);
})
}
},
}
</script>
In the script section, the first thing we need to do is declare the reactive property (image) in our data object. We’ll then declare the handleImage
method to respond to the on-change event triggered by the input tag; we grab the first Blob from the FileList
object and assign it to a variable.
If you inspect the result generated using the Vue DevTool, you’ll notice the file is now in the Data URL format.
Note: The <img> tag element’s src attribute can contain the URL-address or the data URL of an image.
Backend (Node.js)
This implementation isn’t limited to just Node.js or Javascript. Using the right library or built-in APIs, you can convert the base64 string to an image file and store it in your application’s filesystem with basically any backend language of choice.
Since Vue.js uses the Nodejs runtime in the background, we can have our server-side in the same project directory so both applications can share libraries. Let’s make a bit of adjustment to the project folder. From the root directory, we’ll do a basic setup for a Node.js Express server.
npm init -y && mkdir server server/public && touch server/app.js
Our project directory should now look something like this:
|-- Index.vue
|-- assets
|-- styles/main.css
|-- server
|-- public
|-- app.js
`-- package.json
```
We’ll install all the necessary packages. Here’s what we need:
- [Express](https://expressjs.com/) Nodejs web framework
- [Body-parser](https://www.npmjs.com/package/body-parser): to parse incoming request bodies.
- [Axios](https://github.com/axios/axios): to send HTTP requests from our Vue application
- [Cors](https://www.npmjs.com/package/cors): so we can send requests between the same origin
- [Base64-img](https://www.npmjs.com/package/base64-img): to Convert images to base64, or convert base64 to images
```bash
npm i base64-img axios cors express body-parser
```
Once installed, open up app.js, set up the express server, and create the route to handle image upload, decoding, and storage.
```javascript
const express = require('express');
const app = express();
const base64Img = require('base64-img');
const bodyParser = require('body-parser');
const cors = require('cors')
const port = 8081;
app.use(cors())
app.use(express.static('./server/public'))
app.use(bodyParser.json({ limit: '50mb' }));
app.post('/upload', (req, res) => {
const { image } = req.body;
base64Img.img(image, './server/public', Date.now(), function(err, filepath) {
const pathArr = filepath.split('/')
const fileName = pathArr[pathArr.length - 1];
res.status(200).json({
success: true,
url: `http://127.0.0.1:${port}/${fileName}`
})
});
});
app.listen(port, () => {
console.info(`listening on port ${port}`);
})
```
Here, we’re telling our express server to use cors, JSON body-parser (it’s important to set the limit, as base64 images can be really large). We’re also setting the `./server/public` folder as our static folder (we’ll store our decoded images here).
Next, we’re setting up the server to respond to a POST request on the “/upload” endpoint by getting the image string from the request body, passing the image string into the [base64Img.img(…)](https://www.npmjs.com/package/base64-img) function (this function takes 4 arguments; base64 string, the path to store the image, filename, and a callback function that returns the file path. Finally, we grab the file name from the path and return the URL to the user.
> Note: we’re using the Date.now() function as the file name just to maintain a level of uniqueness. Since the function returns the number of milliseconds elapsed since January 1, 1970, it’s almost impossible to get the same value everytime the function is called.
---
Let’s make some changes to our Vue application.
```javascript
...
data() {
return {
image: '',
remoteUrl: ''
}
},
...
```
Add a new property, `remoteUrl`, to the data object, this will hold the value of the URL returned by our server. Next, we need to import Axios and create a method to handle our post request to the server.
```javascript
uploadImage() {
const { image } = this;
axios.post('http://127.0.0.1:8081/upload', { image })
.then((response) => {
this.remoteUrl = response.data.url;
})
.catch((err) => {
return new Error(err.message);
})
}
We’ll call this method within our `reader.onload(…)` event.
...
reader.onload = (e) => {
this.image = e.target.result;
this.uploadImage();
};
...
```
Finally, Let’s render the image URL returned by the server just to be sure we got the right file from the server. Add this below the .container div in your template section:
```xml
<div class="mt-10" style="text-align: center">
<h3>SERVER IMAGE</h3>
<img :src="remoteUrl" alt="">
</div>
```
---
Your `Index.vue` file should look something like this:
```html
<template>
<div>
<div class="container mt-10">
<div class="card bg-white">
<img style="" :src="image" alt="">
<input @change="handleImage" class="custom-input" type="file" accept="image/*">
</div>
</div>
<div class="mt-10" style="text-align: center">
<h3>SERVER IMAGE</h3>
<img :src="remoteUrl" alt="">
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'home',
data() {
return {
image: '',
remoteUrl: ''
}
},
methods: {
handleImage(e) {
const selectedImage = e.target.files[0]; // get first file
this.createBase64Image(selectedImage);
},
createBase64Image(fileObject) {
const reader = new FileReader();
reader.onload = (e) => {
this.image = e.target.result;
this.uploadImage();
};
reader.readAsDataURL(fileObject);
},
uploadImage() {
const { image } = this;
axios.post('http://127.0.0.1:8081/upload', { image })
.then((response) => {
this.remoteUrl = response.data.url;
})
.catch((err) => {
return new Error(err.message);
})
}
},
}
</script>
<style>
@import './assets/style/main.css';
</style>
```
---
## Piecing it all together
In our `package.json`, we’ll add two scripts, one to start our Vue application and the other to start our express server.
```json
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"ui": "vue serve Index.vue",
"server": "node ./server/app.js"
},
}
```
From here, you can open up two tabs on your terminal and run both scripts.
```bash
npm run ui
npm run server
```
You should have your server running on port 8081 and your Vue application on port 8080. Navigate to your Vue app and test.
![Test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pwe4gdkhzutbz62ryihq.png)
As mentioned previously, you can implement base64 image decode in any preferred language. Feel free to experiment with the source code here:
{% embed https://github.com/MartinsOnuoha/vue-base64 %}
Cheers ☕️
---
Top comments (0)