Dees Oomens
Dees Oomens

Reputation: 5082

Upload file from VueJS app to API in Laravel

I'm trying to upload a file (Excel sheet) from a front-end app build with VueJS to an API build with Laravel 5.5. I've some form request validation which says to me that The file field is required. So the file doesn't get uploaded to my API at all.

VueJS file upload:

onFileChange(e) {
  let files = e.target.files || e.dataTransfer.files;

  if (files.length <= 0) {
    return;
  }

  this.upload(files[0]);
},
upload(file) {
  this.form.file = file;

  this.form.put('courses/import')
    .then((response) => {
      alert('Your upload has been started. This can take some time.');
    })
    .catch((response) => {
      alert('Something went wrong with your upload.');
    });
}

this.form is a Form class copied from this project but the data() method returns a FormData object instead of a object.

data() method:

data() {
    let data = new FormData();

    for (let property in this.originalData) {
        data.append(property, this[property]);
    }

    return data;
}

The route:

enter image description here

FormRequest rules:

public function rules()
{
    return [
        'file' => 'required',
    ];
}

If I look to the Network tab in Chrome DevTools it does seem that the request is send correctly: (image after click).

I've tried a lot of things, like sending the excel as base64 to the api. But then I couldn't decode it correctly. So now I'm trying this, but I can't solve the problem.

Edit (controller function)

public function update(ImportRequest $request, CoursesImport $file)
{
    $courses = $file->handleImport();

    dispatch(new StartCourseUploading($courses, currentUser()));

    $file->delete();

    return ok();
}

Upvotes: 2

Views: 3532

Answers (2)

Roozbeh
Roozbeh

Reputation: 471

If you store your data in an associative array like data:

var formData = new FormData();
Object.keys(data).forEach(function(key, index){
    if(Array.isArray(data[key])){
        formData.append(key + '[]', data[key]);
    } else {
        formData.append(key, data[key]);
    }
});
formData.append('file', document.getElementById('file_id').files[0]);
axios.post('path', formData).then( res => {
    console.log(res);
}).catch(res => {
    console.log(res);
});

Upvotes: 1

Suresh Velusamy
Suresh Velusamy

Reputation: 2398

You are getting 422 as status, I hope you aware of this meaning as validation failed according to your Responses class's rule.

In laravel, PUT method won't accept file uploads, so you need to change from PUT to POST.

this.form.post('courses/import')
    .then((response) => {
      alert('Your upload has been started. This can take some time.');
    })
    .catch((response) => {
      alert('Something went wrong with your upload.');
    });

Don't forget to update your routes of laravel.

Other considerations:

  1. Make sure you added property kind of following code

    data: {
        form: new Form({
            file:null
        })
    },
    
  2. Check browser request weather sending form data properly or not , I added sample screen shot Request

My code samples

class Errors {
  /**
   * Create a new Errors instance.
   */
  constructor() {
    this.errors = {};
  }


  /**
   * Determine if an errors exists for the given field.
   *
   * @param {string} field
   */
  has(field) {
    return this.errors.hasOwnProperty(field);
  }


  /**
   * Determine if we have any errors.
   */
  any() {
    return Object.keys(this.errors).length > 0;
  }


  /**
   * Retrieve the error message for a field.
   *
   * @param {string} field
   */
  get(field) {
    if (this.errors[field]) {
      return this.errors[field][0];
    }
  }


  /**
   * Record the new errors.
   *
   * @param {object} errors
   */
  record(errors) {
    this.errors = errors;
  }


  /**
   * Clear one or all error fields.
   *
   * @param {string|null} field
   */
  clear(field) {
    if (field) {
      delete this.errors[field];

      return;
    }

    this.errors = {};
  }
}


class Form {
  /**
   * Create a new Form instance.
   *
   * @param {object} data
   */
  constructor(data) {
    this.originalData = data;

    for (let field in data) {
      this[field] = data[field];
    }

    this.errors = new Errors();
  }


  /**
   * Fetch all relevant data for the form.
   */
  data() {
    let data = new FormData();

    for (let property in this.originalData) {
      data.append(property, this[property]);
    }

    return data;
  }


  /**
   * Reset the form fields.
   */
  reset() {
    for (let field in this.originalData) {
      this[field] = '';
    }

    this.errors.clear();
  }


  /**
   * Send a POST request to the given URL.
   * .
   * @param {string} url
   */
  post(url) {
    return this.submit('post', url);
  }


  /**
   * Send a PUT request to the given URL.
   * .
   * @param {string} url
   */
  put(url) {
    return this.submit('put', url);
  }


  /**
   * Send a PATCH request to the given URL.
   * .
   * @param {string} url
   */
  patch(url) {
    return this.submit('patch', url);
  }


  /**
   * Send a DELETE request to the given URL.
   * .
   * @param {string} url
   */
  delete(url) {
    return this.submit('delete', url);
  }


  /**
   * Submit the form.
   *
   * @param {string} requestType
   * @param {string} url
   */
  submit(requestType, url) {
    return new Promise((resolve, reject) => {
      axios[requestType](url, this.data())
        .then(response => {
          this.onSuccess(response.data);

          resolve(response.data);
        })
        .catch(error => {
          this.onFail(error.response.data);

          reject(error.response.data);
        });
    });
  }


  /**
   * Handle a successful form submission.
   *
   * @param {object} data
   */
  onSuccess(data) {
    alert(data.message); // temporary

    this.reset();
  }


  /**
   * Handle a failed form submission.
   *
   * @param {object} errors
   */
  onFail(errors) {
    this.errors.record(errors);
  }
}


var app = new Vue({
  el: '#app',

  data: {
    form: new Form({
      file: ''
    })
  },
  methods: {
    onSubmit() {
      this.form.post('/projects')
        .then(response => alert('Wahoo!'));
    },
    onFileChange(e) {
      let files = e.target.files || e.dataTransfer.files;

      if (files.length <= 0) {
        return;
      }

      this.upload(files[0]);
    },
    upload(file) {
      this.form.file = file;

      this.form.post('courses/import')
        .then((response) => {
          alert('Your upload has been started. This can take some time.');
        })
        .catch((response) => {
          alert('Something went wrong with your upload.');
        });
    }
  }
});
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.2.3/css/bulma.css">
  <style>
    body {
      padding-top: 40px;
    }
  </style>
</head>

<body>
  <div id="app" class="container">


    <form method="POST" action="/projects" @submit.prevent="onSubmit" @keydown="form.errors.clear($event.target.name)">

      <input type="file" name="image" @change="onFileChange">
        
      <div class="control">
        <button class="button is-primary" :disabled="form.errors.any()">Create</button>
      </div>
    </form>
  </div>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.js"></script>
  <script src="https://unpkg.com/[email protected]/dist/vue.js"></script>

</body>

</html>

Related Links

https://github.com/laravel/framework/issues/17560

Upvotes: 2

Related Questions