Drive
AdonisJS Drive (@adonisjs/drive
) is a lightweight wrapper on top of flydrive.dev. FlyDrive is a file storage library for Node.js. It provides a unified API to interact with the local file system and cloud storage solutions like S3, R2, and GCS.
Using FlyDrive, you can manage user-uploaded files on various cloud storage services (including the local filesystem) without changing a single line of code.
Installation
Install and configure the @adonisjs/drive
package using the following command:
node ace add @adonisjs/drive
-
Installs the
@adonisjs/drive
package using the detected package manager. -
Registers the following service provider inside the
adonisrc.ts
file.{providers: [// ...other providers() => import('@adonisjs/drive/drive_provider'),]} -
Creates the
config/drive.ts
file. -
Defines the environment variables for the selected storage services.
-
Install the required peer dependencies for the selected storage services.
Configuration
The @adonisjs/drive
package configuration is stored inside the config/drive.ts
file. You can define config for multiple services within a single config file.
See also: Config stub
import env from '#start/env'
import app from '@adonisjs/core/services/app'
import { defineConfig, services } from '@adonisjs/drive'
const driveConfig = defineConfig({
default: env.get('DRIVE_DISK'),
services: {
/**
* Persist files on the local filesystem
*/
fs: services.fs({
location: app.makePath('storage'),
serveFiles: true,
routeBasePath: '/uploads',
visibility: 'public',
}),
/**
* Persist files on Digital Ocean spaces
*/
spaces: services.s3({
credentials: {
accessKeyId: env.get('SPACES_KEY'),
secretAccessKey: env.get('SPACES_SECRET'),
},
region: env.get('SPACES_REGION'),
bucket: env.get('SPACES_BUCKET'),
endpoint: env.get('SPACES_ENDPOINT'),
visibility: 'public',
}),
},
})
export default driveConfig
Environment variables
The credentials/settings for the storage services are stored as environment variables within the .env
file. Make sure to update the values before you can use Drive.
Also, the DRIVE_DISK
environment variable defines the default disk/service for managing files. For example, you may want to use the fs
disk in development and the spaces
disk in production.
Usage
Once you have configured Drive, you can import the drive
service to interact with its APIs. In the following example, we handle a file upload operation using Drive.
Since AdonisJS integration is a thin wrapper on top of FlyDrive. To better understand its APIs, you should read FlyDrive docs.
import { cuid } from '@adonisjs/core/helpers'
import drive from '@adonisjs/drive/services/main'
import router from '@adonisjs/core/services/router'
router.put('/me', async ({ request, response }) => {
/**
* Step 1: Grab the image from the request and perform basic
* validations
*/
const image = request.file('avatar', {
size: '2mb',
extnames: ['jpeg', 'jpg', 'png'],
})
if (!image) {
return response.badRequest({ error: 'Image missing' })
}
/**
* Step 2: Move the image with a unique name using Drive
*/
const key = `uploads/${cuid()}.${image.extname}`
await image.moveToDisk(key)
/**
* Respond with the file's public URL
*/
return {
message: 'Image uploaded',
url: await drive.use().getUrl(key),
}
})
-
The Drive package adds the
moveToDisk
method to the MultipartFile. This method copies the file from itstmpPath
to the configured storage provider. -
The
drive.use().getUrl()
method returns the public URL for the file. For private files, you must use thegetSignedUrl
method.
Drive service
Drive service exported by the @adonisjs/drive/services/main
path is a singleton instance of the DriveManager class created using the config exported from the config/drive.ts
file.
You can import this service to interact with the DriveManager and the configured file storage services. For example:
import drive from '@adonisjs/drive/services/main'
drive instanceof DriveManager // true
/**
* Returns instance of the default disk
*/
const disk = drive.use()
/**
* Returns instance of a disk named r2
*/
const disk = drive.use('r2')
/**
* Returns instance of a disk named spaces
*/
const disk = drive.use('spaces')
Once you have access to an instance of a Disk, you can use it to manage files.
See also: Disk API
await disk.put(key, value)
await disk.putStream(key, readableStream)
await disk.get(key)
await disk.getStream(key)
await disk.getArrayBuffer(key)
await disk.delete(key)
await disk.deleteAll(prefix)
await disk.copy(source, destination)
await disk.move(source, destination)
await disk.copyFromFs(source, destination)
await disk.moveFromFs(source, destination)
Local filesystem driver
AdonisJS integration enhances the FlyDrive's local filesystem driver and adds support for URL generation and ability to serve files using the AdonisJS HTTP server.
Following is the list of options you may use configure the filesystem driver.
{
services: {
fs: services.fs({
location: app.makePath('storage'),
visibility: 'public',
appUrl: env.get('APP_URL'),
serveFiles: true,
routeBasePath: '/uploads',
}),
}
}
-
location
-
The
location
property defines the stores inside which the files should be stored. This directory should be added to.gitignore
so that you do not push files uploaded during development to the production server. -
visibility
-
The
visibility
property is used to mark files public or private. Private files can only be accessed using signed URLs. Learn more -
serveFiles
-
The
serveFiles
option auto registers a route to serve the files from the local filesystem. You can view this route using the list:routes ace command. -
routeBasePath
-
The
routeBasePath
option defines the base prefix for the route to serve files. Make sure the base prefix is unique. -
appUrl
-
You may optionally define the
appUrl
property to create URLs with the complete domain name of your application. Otherwise relative URLs will be created.
Edge helpers
Within the Edge templates, you may use one the following helper methods to generate URLs. Both the methods are async, so make sure to await
them.
<img src="{{ await driveUrl(user.avatar) }}" />
<!-- Generate URL for a named disk -->
<img src="{{ await driveUrl(user.avatar, 's3') }}" />
<img src="{{ await driveUrl(user.avatar, 'r2') }}" />
<a href="{{ await driveSignedUrl(invoice.key) }}">
Download Invoice
</a>
<!-- Generate URL for a named disk -->
<a href="{{ await driveSignedUrl(invoice.key, 's3') }}">
Download Invoice
</a>
<!-- Generate URL with signed options -->
<a href="{{ await driveSignedUrl(invoice.key, {
expiresIn: '30 mins',
}) }}">
Download Invoice
</a>
MultipartFile helper
Drive extends the Bodyparser MultipartFile class and adds the moveToDisk
method. This method copies the file from its tmpPath
to the configured storage provider.
const image = request.file('image')!
const key = 'user-1-avatar.png'
/**
* Move file to the default disk
*/
await image.moveToDisk(key)
/**
* Move file to a named disk
*/
await image.moveToDisk(key, 's3')
/**
* Define additional properties during the
* move operation
*/
await image.moveToDisk(key, 's3', {
contentType: 'image/png',
})
Faking Disks during tests
The fakes API of Drive can be used during testing to prevent interacting with a remote storage. In the fakes mode, the drive.use()
method will return a fake disk (backed by local filesystem) and all files will be written inside the ./tmp/drive-fakes
directory of your application root.
These files are deleted automatically after you restore a fake using the drive.restore
method.
See also: FlyDrive fakes documentation
import { test } from '@japa/runner'
import drive from '@adonisjs/drive/services/main'
import fileGenerator from '@poppinss/file-generator'
test.group('Users | update', () => {
test('should be able to update my avatar', async ({ client, cleanup }) => {
/**
* Fake the "spaces" disk and restore the fake
* after the test finishes
*/
const fakeDisk = drive.fake('spaces')
cleanup(() => drive.restore('spaces'))
/**
* Create user to perform the login and update
*/
const user = await UserFactory.create()
/**
* Generate a fake in-memory png file with size of
* 1mb
*/
const { contents, mime, name } = await fileGenerator.generatePng('1mb')
/**
* Make put request and send the file
*/
await client
.put('me')
.file('avatar', contents, {
filename: name,
contentType: mime,
})
.loginAs(user)
/**
* Assert the file exists
*/
fakeDisk.assertExists(user.avatar)
})
})