<form method="POST" action="upload" enctype="multipart/form-data">
<input type="file" name="profile_pic" />
<button type="submit"> Submit </button>
</form>
You are viewing the legacy version of AdonisJS. Visit https://adonisjs.com for newer docs. This version will receive security patches until the end of 2021.
File uploads have been reimagined in 4.0 and give you enough ways to process files securely and without wasting the server resources.
Let’s see how to handle files uploaded by HTML form.
<form method="POST" action="upload" enctype="multipart/form-data">
<input type="file" name="profile_pic" />
<button type="submit"> Submit </button>
</form>
Registering route
const Helpers = use('Helpers')
Route.post('upload', async ({ request }) => {
const profilePic = request.file('profile_pic', {
types: ['image'],
size: '2mb'
})
await profilePic.move(Helpers.tmpPath('uploads'), {
name: 'custom-name.jpg'
})
if (!profilePic.moved()) {
return profilePic.error()
}
return 'File moved'
})
The request.file
method accepts two arguments, a field name and an object of validations to be performed, before moving the file.
Next, we call profilePic.move
method, which attempts to move the file to the defined directory. You can call it with options to save file with new name.
Finally, we check whether the move operation was successful or not using profilePic.moved()
method and return errors (if any).
Uploading multiple files at a time is as simple as uploading one file. The request.file
method returns a File jar in case of multiple files.
<form method="POST" action="upload" enctype="multipart/form-data">
<input type="file" name="profile_pics[]" multiple />
<button type="submit"> Submit </button>
</form>
const Helpers = use('Helpers')
Route.post('upload', async ({ request }) => {
const profilePics = request.file('profile_pics', {
types: ['image'],
size: '2mb'
})
await profilePics.moveAll(Helpers.tmpPath('uploads'))
if (!profilePics.movedAll()) {
return profilePics.errors()
}
})
The route handler code has been changed a bit in case of multiple file uploads.
Instead of using move
, we make use of moveAll
method. Which parallelly moves all of the uploaded files to a given directory.
Also, other methods have been changed as moved → movedAll
and error → errors
.
When moving a single file, you can pass an object with name
property on it. However, it can get tricky with multiple files.
AdonisJs allows you to pass a callback to moveAll
method, which is executed for each file inside the fileJar
.
profilePics.moveAll(Helpers.tmpPath('uploads'), (file) => {
return {
name: `${new Date().getTime()}.${file.subtype}`
}
})
In case of multiple file uploads, it’s possible that only few files are successfully moved and other are rejected due to validation failures.
In that case you can make use of movedAll()
and movedList()
methods, to optimize your workflow.
const removeFile = Helpers.promisify(fs.unlink)
if (!profilePics.movedAll()) {
const movedFiles = profilePics.movedList()
await Promise.all(movedFiles.map((file) => {
return removeFile(path.join(file._location, file._fileName))
}))
return profilePics.errors()
}
Following is the list of formats in which error(s) are returned.
When using FileJar the errors method returns an array of following errors.
|
{
fieldName: "profile_pic",
clientName: "GitHub-Mark.ai",
message: "Invalid file type postscript or application. Only image is allowed",
type: "type"
}
{
fieldName: "profile_pic",
clientName: "adonis_landing.sketch",
message: "File size should be less than 2MB",
type: "size"
}
Also, multiple file uploads errors
method returns an array of errors in the same format.
Below is the list of file properties you can access on the file instance.
Property | Unprocessed | Inside tmp | Moved |
---|---|---|---|
clientName File name on client machine |
|
|
|
fileName File name after move operation |
|
|
|
fieldName Form field name |
|
|
|
tmpPath Temporary path |
|
|
|
size File size in bytes |
|
|
|
type File primary type |
|
|
|
subtype File sub type |
|
|
|
status File status. Set to |
|
|
|
Majority of file uploading libraries/frameworks process the files for multiple times when you want to stream them to an external service like s3.
Here’s how the file uploading workflows are usually designed.
Process all the request files and save them into the tmp
directory.
Move that file from the tmp
directory to the destination directory.
Use aws SDK and then stream the file to s3.
This process wastes a bunch of server resources since a single file is getting read and written for multiple times. On the other hand, AdonisJs makes the process of streaming files smooth and efficient.
The config file config/bodyparser.js
has a section to disable auto-processing of files for some selected routes.
processManually: ['/upload']
The processManually
option takes an array of routes or route patterns, for which files should not be processed automatically.
Next thing we need to do is call the process
method inside the controller/route handler.
const Drive = use('Drive')
Route.post('upload', async ({ request }) => {
request.multipart.file('profile_pic', {}, async (file) => {
await Drive.disk('s3').put(file.fileName, file.stream)
})
await request.multipart.process()
})
Always make sure to call await request.multipart.process() to start processing the files.
|
The request.multipart.file
method lets you select a specific file, and the readable stream is accessed via file.stream
property. Now you are free to consume the stream by piping it to s3 or any other service you want.
The entire process is asynchronous and processes the file(s) only once.