Uppy File Uploader in Laravel & Vue.js Apps

Handling single file uploads with Uppy in a Laravel & Vue.js app

Photo by Mathew Schwartz on Unsplash

Some technical details about my local setup — just for reference

Back story

Setting up

npm install @uppy/core @uppy/xhr-upload @uppy/dashboard @uppy/form
import Vue from 'vue';
import UppyUploader from '../components/UppyUploader';

new Vue({
el: 'uppy-uploader',
components:{
UppyUploader
}
});
<template>
<form>
<div class="image-container mb-3" v-if="previewPath">
<img :src="previewPath" alt="Uploaded Image Preview">
</div>
<div class="form-group">
<div ref="dashboardContainer"></div>
</div>
<button :disabled="disabled" @click.prevent="confirmUpload" class="btn btn-primary btn-block mb-2">Confirm upload</button>
</form>
</template>

<script>
import Uppy from '@uppy/core';
import XHRUpload from '@uppy/xhr-upload';
import Dashboard from '@uppy/dashboard';
import Form from '@uppy/form';

import notify from './mixins/noty';

import '@uppy/core/dist/style.css';
import '@uppy/dashboard/dist/style.css';

export default {
props: {
maxFileSizeInBytes: {
type: Number,
required: true
}
},
mixins: [notify],
data() {
return {
payload: null,
previewPath: null,
disabled: true
}
},
mounted() {
this.instantiateUppy()
},
methods: {
instantiateUppy() {
this.uppy = Uppy({
debug: true,
autoProceed: true,
restrictions: {
maxFileSize: this.maxFileSizeInBytes,
minNumberOfFiles: 1,
maxNumberOfFiles: 1,
allowedFileTypes: ['image/*', 'video/*', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/pdf']
}
})
.use(Dashboard, {
hideUploadButton: true,
inline: true,
height: 450,
target: this.$refs.dashboardContainer,
replaceTargetContent: true,
showProgressDetails: true,
browserBackButtonClose: true

})
.use(XHRUpload, {
limit: 10,
endpoint: '/file/upload',
formData: true,
fieldName: 'file',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content') // from <meta name="csrf-token" content="{{ csrf_token() }}">
}
});

this.uppy.on('complete', (event) => {
if(event.successful[0] !== undefined) {
this.payload = event.successful[0].response.body.path;

this.disabled = false;
}
});
},
updatePreviewPath({path}) {
this.previewPath = path;

return this;
},
resetUploader() {
this.uppy.reset();
this.disabled = true;

return this;
},
confirmUpload() {
if(this.payload) {
this.disabled = true;
axios.post('/store', { file: this.payload })
.then(({ data }) => {
this.updatePreviewPath(data)
.resetUploader()
.notify('success', 'Upload Successful!');
})
.catch(err => {
console.error(err);

this.resetUploader();
})
;
} else notify('warning', `You don't have any file in processing`);

}
}
};
</script>

<style scoped>
.image-container {
height: 150px;
width: 150px;
border-radius: 50%;
overflow: hidden;
margin-right: auto;
margin-left: auto;
}

.image-container > img {
width: inherit;
height: inherit;
}
</style>
.js('resources/js/plugins/uppy_uploader.js', 'public/js/plugins')
mix.js('resources/js/app.js', 'public/js')
.js('resources/js/plugins/user_profiles.js', 'public/js/plugins')
// .sass('resources/sass/app.scss', 'public/css');
// auto recompile when any changes are madenpm run watch
<uppy-uploader
:max-file-size-in-bytes="1000000">
</uppy-uploader>
@extends('layouts.app')

@section(
'content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<uppy-uploader
:max-file-size-in-bytes="1000000">
</uppy-uploader>

</div>
</div>
</div>
</div>
</div>
@endsection

@section(
'scripts')
<script src="/js/plugins/uppy_uploader.js"></script>
@endsection
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel backend. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
window.axios = require('axios');
npm install noty
// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');
// Variables
@import 'variables';
// Bootstrap
@import '~bootstrap/scss/bootstrap';
// Noty
@import "~noty/src/noty.scss";
@import "~noty/src/themes/mint.scss";
import Noty from 'noty';export default {
methods: {
notify(type, text){
new Noty({
text,
type,
layout: 'top',
theme: 'mint',
timeout: 3000,
}).show();
}
}
};
import notify from './mixins/noty';

Part 2: Backend bits

php artisan make:model Post -mr
<?php

use
Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostsTable extends Migration
{
/**
* Run the migrations.
*
*
@return void
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('file_path');
$table->timestamps();
});
}

/**
* Reverse the migrations.
*
*
@return void
*/
public function down()
{
Schema::dropIfExists('posts');
}
}
<?php

namespace
App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
protected $fillable = ['file_path'];

public static function publishNewPost($post)
{
return static::create([
'file_path' => $post
]);
}
}
<?php

namespace
App\Http\Controllers;

use App\Post;
use App\Traits\UppyUploaderTrait;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class PostController extends Controller
{
use UppyUploaderTrait;

public function store(Request $request)
{
if ($request->file) {
$post = Post::publishNewPost($this->getMovedFilePath($request->file));

return response([
'path' => Storage::url($post->file_path)
], 201);
}

return response([], 400);
}
}
// file uploader routes
Route::view('/upload', 'upload'); // returns upload page
// auto upload/store file to temporary storage path
Route::post('/file/upload', 'PostController@upload');
// store form information and move file to permanent storage path
Route::post('/store', 'PostController@store');
<?php

namespace
App\Http\Controllers;

use App\Post;
use App\Traits\UppyUploaderTrait;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class PostController extends Controller
{
use UppyUploaderTrait; // import uploader trait

...
}
<?php

namespace
App\Traits;

use App\Services\FileUploadProcessor;
use Illuminate\Http\Request;

trait UppyUploaderTrait
{
protected $fileUploadProcessor;

public function __construct(FileUploadProcessor $fileUploadProcessor)
{
$this->fileUploadProcessor = $fileUploadProcessor;
}

public function upload(Request $request)
{
return response([
'path' => $this->fileUploadProcessor->generateTempFileStoragePath($request)
], 200);
}

public function getMovedFilePath($file)
{
return $this->fileUploadProcessor->moveFileToRealStoragePath($file);
}
}
...protected $fileUploadProcessor;

public function __construct(FileUploadProcessor $fileUploadProcessor)
{
$this->fileUploadProcessor = $fileUploadProcessor;
}
...
<?php

namespace
App\Services;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

/**
* Class FileUploadProcessor
*
@package App\Services
*/
class FileUploadProcessor
{
/**
*
@param $filePath
*
@return mixed
*/
protected function getFileExtension($filePath)
{
return pathinfo($filePath, PATHINFO_EXTENSION);
}

/**
*
@param $extension
*
@return string
*/
protected function generateFileName($extension)
{
return Str::random(40) . '.' . $extension;
}

/**
*
@param $filePath
*
@return string
*/
public function generateRealFileStoragePath($filePath)
{
$extension = $this->getFileExtension($filePath);
$newFilename = $this->generateFileName($extension);

return 'uploads/' . $newFilename;
}

/**
*
@param Request $request
*
@return false|string
*/
public function generateTempFileStoragePath(Request $request)
{
return $request->file('file')->store('/uploads/temp', 'public');
}

/**
*
@param $tempPath
*
@return string
*/
public function moveFileToRealStoragePath($tempPath)
{
$newPath = $this->generateRealFileStoragePath($tempPath);

Storage::disk('public')->move($tempPath, $newPath);

return $newPath;
}
}
composer dump-autoload

Associated Github commit:

Human & software engineer. Interested in ()=> {[Arts, Education, Music, Science, Tech, AI, co-Humans]};