File upload from a Nuxt.js client to an AdonisJS RESTful API server using axios


AdonisJs documentation shows how to upload files to the server using the HTML5 <form> element. But there are cases where axios comes more handy. So let us see how to upload files from a Nuxt.js client application to an AdonisJS RESTful API server with axios. I shared this project on my Github profile.

Project setup

In my demo, the client and server code are set apart. My client application is handled by Nuxt.js:

yarn create nuxt-app client

and the server by AdonisJs:

adonis new server --api-only

On my Github project, I use Vuetify.js and Eslint.

Nuxt.js client code

Here is where the main code is located:

├── FileUpload.vue
└── MainPage.vue
└── index.vue
└── default.vue

And this is the simple user interface I want to create: file upload

Basically I like to have MainPage.vue component to wrap all my other components; in this case I need only the FileUpload.vue component:

  <file-upload />

import FileUpload from '@/components/FileUpload.vue'

export default {
  name: 'MainPage',
  components: {

The essential part of the code is what the FileUpload.vue component contains. So I show here the full code and then will explain the main points:

 1 <template>
 2   <v-container
 3     grid-list-md
 4     text-xs-center
 5     fill-height>
 6     <v-layout
 7       row
 8       wrap
 9       align-center>
10       <v-flex
11         xs6
12         offset-xs3>
13         <v-text-field
14           v-model="photoName"
15           name="photo"
16           outline
17           background-color="blue"
18           color="blue"
19           label="Select image"
20           append-icon="attach_file"
21           @click="selectImage"/>
22         <input
23           ref="image"
24           class="hide-input"
25           type="file"
26           accept="image/*"
27           @change="imageSelected">
28         <v-btn
29           class="upload-button"
30           color="indigo"
31           @click="upload_photo">
32           Upload
33           <v-icon
34             right
35             color="white">
36             cloud_upload
37           </v-icon>
38         </v-btn>
39       </v-flex>
40     </v-layout>
41   </v-container>
42 </template>
44 <script>
45 export default {
46   name: 'FileUpload',
47   data: () =>({
48     photo: '',
49     photoName: ''
50   }),
51   methods: {
52     selectImage() {
53 = this.$
54     },
55     imageSelected(e) {
56       this.$emit('input',[0])
57 = this.$refs.image.files[0]
58       this.photoName =
59     },
60     async upload_photo() {
61       let formData = new FormData()
62       formData.append('file',
63       let url = ''
64       let config = {
65 	headers: {
66           'content-type': 'multipart/form-data'
67 	}
68       }
69       await this.$axios({
70       	method: 'post',
71       	url: url,
72       	data:  formData,
73       	config: config
74       })
76     }
77   }
78 }
79 </script>
81 <style scoped>
82 .hide-input {
83     display: none;
84 }
85 *{
86     text-transform: none !important;
87 }
88 .upload-button {
89     border-radius: 50px;
90     color: white;
91 }
92 </style>
As Vuetify.js which I am using here (listed in the dependencies of the project) does not have a component which behaves as the HTML5 <input> element, I need to hide this later one when displaying the <v-text-field /> component. This is the traditional simple but efficient trick usually used in this case; then we trigger a click on this hidden file input as follows:

imageSelected(e) { = this.$refs.image.files[0]

My input file element is referenced with image, that is why we need to look to the references available in this DOM template with it:


In my particular case, I am interested in uploading images only, that is why I set accept="image/*". The main thing not to forget in the AJAX request is to declare the content-type header. I think the rest of the code is self explanatory:

async upload_photo() {
  let formData = new FormData()
  let url = '' // This is the endpoint of my REST API on the server 
  // below is equivalent to the enctype="multipart/form-data" we use in the <form> element
  let config = {
    headers: {
      'content-type': 'multipart/form-data'
  await this.$axios({
    method: 'post',
    url: url,
    data:  formData,
    config: config

If you are a one-liner, the asynchronous code above (the await part) can be written concisely as follows:

await this.$axios.$post(url, formData, config)

AdonisJs REST API server code

On the server, I first set the endpoint in start/routes.js:'/upload', 'PhotoController.upload')

Then I created the corresponding controller:

adonis make:controller PhotoController

Inside this controller (app/Controllers/Http/PhotoController.js), I received the object File() sent by the axios POST request in my client code by inspecting the request object of AdonisJs: const photo = request.file('file'). Note that I use file to reference its key in the FormData() object which contains it. Below I rename the file with the current time of the server’s machine:

'use strict'

const Helpers = use('Helpers')

class PhotoController {
  async upload( {request, response} ) {
    const photo = request.file('file')
    await photo.move(Helpers.tmpPath('photos'), {
      name: new Date().getTime() +'.'+avatar.subtype,
      overwrite: true

module.exports = PhotoController

If you upload a photo, you will find it on the server, precisely in the folder /tmp/photos. Note that you have to enable CORS for the requests to be acceped. You can do that by setting cors: true in config/cors.js file. For larger images, set the limit value of maxSize to whatever you want in /config/bodyParser.js which is 20mb by default.