<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.
AdonisJs processes file uploads securely without wasting server resources.
Let’s see how to handle files uploaded via HTML form:
<form method="POST" action="upload" enctype="multipart/form-data">
<input type="file" name="profile_pic" />
<button type="submit"> Submit </button>
</form>
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',
overwrite: true
})
if (!profilePic.moved()) {
return profilePic.error()
}
return 'File moved'
})
The request.file
method accepts two arguments (a field name and a validation object to apply to the uploaded file), and returns a File instance.
Next, we call the profilePic.move
method which attempts to move the file to the defined directory (in this case, called with options to save the file with a new name and to overwrite the file if needed).
Finally, we check whether the move operation was successful by calling the profilePic.moved()
method (returning errors if any were found).
AdonisJs makes uploading multiple files as simple as uploading a single file.
When multiple files are uploaded together, request.file
returns a FileJar instance instead of a File instance:
<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()
}
})
In the example above, compared to the way we handle a single file:
Instead of move
, we use the moveAll
method (which moves all uploaded files in parallel to a given directory).
Single file methods have been changed to multiple file methods (such as moved → movedAll
and error → errors
).
To move and rename a single file upload, pass an options object to the move
method defining the file’s new name
:
await profilePic.move(Helpers.tmpPath('uploads'), {
name: 'my-new-name.jpg'
})
To move and rename multiple file uploads, pass a callback to the moveAll
method to create a custom options object for each file inside your FileJar instance:
profilePics.moveAll(Helpers.tmpPath('uploads'), (file) => {
return {
name: `${new Date().getTime()}.${file.subtype}`
}
})
When moving multiple file uploads, it’s possible some files move successfully while others reject due to validation failures.
In that case, you can use the 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()
}
The following validation options can be passed to validate a file before completing a move operation:
Key | Value | Description |
---|---|---|
|
|
An array of types to be allowed. The value will be checked against the file media type. |
|
|
The maximum size allowed for the file. The value is parsed using the bytes.parse method. |
|
|
To have to more granular control over the file type, you can define the allowed extensions over defining the type. |
An example of how to apply validation rules is as follows:
const validationOptions = {
types: ['image'],
size: '2mb',
extnames: ['png', 'gif']
}
const avatar = request.file('avatar', validationOptions)
// this is when validation occurs
await avatar.move()
When upload validation fails, the File error
method returns an object containing the failed fieldName
, original clientName
, an error message
, and the rule type
that triggered the error.
The FileJar errors method returns an array of errors.
|
A few example error objects are list below.
{
fieldName: "field_name",
clientName: "invalid-file-type.ai",
message: "Invalid file type postscript or application. Only image is allowed",
type: "type"
}
{
fieldName: "field_name",
clientName: "invalid-file-size.png",
message: "File size should be less than 2MB",
type: "size"
}
The following file properties can be accessed 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 |
|
|
|
extname File extension |
|
|
|
Route validators validate uploaded files before passing them to the controller.
In the example route validator below:
'use strict'
class StoreUser {
get rules () {
return {
avatar: 'file|file_ext:png,jpg|file_size:2mb|file_types:image'
}
}
}
module.exports = StoreUser
The file
rule ensures the avatar
field is a valid File.
The file_ext
rule defines the extnames
allowed for the file.
The file_size
rule defines the maximum size
for the file.
The file_types
rule defines the types
allowed for the file.
The majority of upload libraries/frameworks process files multiple times when streaming to an external service such as Amazon S3. Their upload workflows are usually designed like so:
Process request files then save them to the tmp
directory.
Move each file from the tmp
directory to the destination directory.
Use the external service’s SDK to finally stream the file to the external service.
This process wastes server resources reading/writing single files multiple times.
AdonisJs makes the process of streaming uploaded files far more efficient.
First, disable file auto-processing for your upload routes via the config/bodyparser.js
file:
processManually: ['/upload']
The processManually
option takes an array of routes or route patterns for which files should not be processed automatically.
Finally, call the request.multipart.process
method inside the file upload 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.clientName, file.stream)
})
await request.multipart.process()
})
You must call await request.multipart.process() to start processing uploaded files.
|
The request.multipart.file
method lets you select a specific file and access its readable stream via the file.stream
property so you can pipe the stream to Amazon S3 or any other external service you want.
The entire process is asynchronous and processes the file(s) only once.