Introduction

OstroJS provides a robust filesystem abstraction. Simple drivers for interacting with local filesystems, SFTP, and Amazon S3 are provided via the OstroJS Flysystem integration. Even better, because the API for each system is the same, switching between different storage solutions between your local development workstation and production server is really straightforward.

Configuration

config/filesystems.js is the location of OstroJS's filesystem configuration file. You may configure all of your filesystem "discs" in this file. Each disc corresponds to a certain storage driver and location. The configuration file includes example setups for each available driver, which you may alter to suit your storage choices and credentials.

The local driver interacts with files on the server hosting the OstroJS application, whereas the s3 driver writes to Amazon's S3 cloud storage service.

The Local Driver

All file actions while using the local driver are relative to the root directory indicated in your filesystems configuration file. This value is assigned to the storage/app directory by default. As a result, the technique below will write to storage/app/example.txt:

Storage.disk('local').put('example.txt', 'Contents');

The Public Disk

The public disc specified in your application's filesystems configuration file is for files that will be exposed to the public. The public disc defaults to using the local driver and storing its files at storage/app/public.

Create a symbolic link from public/storage to storage/app/public to make these files available from the web. When utilising zero-downtime deployment solutions like Envoyer, this folder convention will keep your publicly available files in one directory that can be readily shared across deployments.

You may use the storage:link Artisan command to create the symbolic link:

node assistant storage:link

After a file has been saved and a symbolic link has been made, you can use the asset helper to build a URL to the files:

asset('storage/file.txt');

Additional symbolic links can be configured in the filesystems configuration file. When you execute the storage, each of the configured connections will be created: command to link:

'links' : {
    [public_path('storage')]: storage_path('app/public'),
    [public_path('images')] : storage_path('app/images'),
}

Driver Prerequisites

S3 Driver Configuration

The configuration information for the S3 driver may be found in the config/filesystems.Js configuration file. This file offers an example S3 driver configuration array. You are free to customise this array using your own S3 credentials and setup. These environment variables follow the AWS CLI's naming pattern for simplicity.

Amazon S3 Compatible Filesystems

A disc configuration for the s3 disc is included by default in your application's filesystems configuration file. This disc may be used to interface with any S3 compliant file storage provider, such as MinIO or DigitalOcean Spaces, in addition to Amazon S3.

Typically, you only need to adjust the value of the url configuration option after modifying the disk's credentials to match the credentials of the service you want to use. The AWS ENDPOINT environment variable is generally used to determine the value of this option:

'endpoint' : env('AWS_ENDPOINT', 'https://minio:9000')

 

Obtaining Disk Instances

Any of your specified discs may be accessed using the Storage fa├žade. You may use the put method on the facade, for example, to save an avatar on the default disc. The method will be sent to the default disc if you use methods on the Storage facade without first using the disc method:

await Storage.put('avatars/1', $content);

If your application interacts with several drives, you may operate with files on a specific disc using the disc method on the Storage facade:

await Storage.disk('s3').put('avatars/1', $content);

Retrieving Files

The get method can be used to get a file's contents. The function will return the file's raw string contents as a string. Remember to specify all file paths relative to the disk's "root" location:

let contents = await Storage.get('file.jpg');

To discover if a file exists on the disc, use the exists method:

if (await Storage.disk('s3').exists('file.jpg')) {
    // ...
}

 

Storing Files

To save file contents on a disc, use the put technique. You may also use the put method to supply a NodeJS resource, which will be used by Filesystem's underlying stream support. Remember that all file paths should be supplied relative to the disk's "root" location:

await Storage.put('file.jpg', $contents);

await Storage.put('file.jpg', $resource);

File Uploads

One of the most prevalent use-cases for storing files in web applications is storing user-uploaded items such as images and documents. The store function on an uploaded file instance in OstroJS makes it extremely simple to store uploaded files. Call the store method with the directory where you want the uploaded file to be saved:

const Controller = require('~/app/http/controllers/controller')

class UserAvatarController extends Controller {
    /**
     * Update the avatar for the user.
     *
     */
    async update({request, response}) {
        let $path = await request.file('avatar').store('avatars');

        response.send($path);
    }
}

module.exports = UserAvatarController

Specifying A File Name

You may use the storeAs method, which takes the path, the filename, and the (optional) disc as parameters, if you don't want a filename to be automatically provided to your saved file:

let $path = await request.file('avatar').storeAs(
    'avatars', (await request.user()).id
);

Specifying A Disk

The default storage mechanism for this uploaded file is your usual disc. Pass the disc name as the second argument to the save function if you want to specify another disc:

let $path = await request.file('avatar').store(
    'avatars/'+(await request.user()).id, 's3'
);

Other Uploaded File Information

The getClientOriginalName and getClientOriginalExtension methods can be used to retrieve the original name and extension of an uploaded file:

let $file = request.file('avatar');

let $name = $file.getName();
let $extension = $file.getHashname();

Keep in mind that the getClientOriginalName and getClientOriginalExtension functions are deemed risky because a malicious user might change the file name or extension. As a result, the hashName and extension methods should be used to acquire a name and an extension for a particular file upload:

let $file = request.file('avatar');

let $name = $file.hashName(); // Generate a unique, random name...
let $extension = $file.extension(); // Determine the file's extension based on the file's MIME type...

Deleting Files

The delete method may be used to remove a single file or an array of files:

await Storage.delete('file.jpg');

You can specify the disc from which the file should be erased if necessary:

await Storage.disk('s3').delete('path/file.jpg');

Custom Filesystems

Filesystem's integration with OstroJS  comes with support for a number of "drivers" out of the box; however, Flysystem is not restricted to these and offers adapters for a variety of additional storage systems. If you wish to use one of these extra adapters in your OstroJS application, you may construct a custom driver.

In order to define a custom filesystem you will need a Flysystem adapter. 

The driver may then be registered in one of your application's service providers' boot procedure. You should utilise the extend method of the Storage facade to do this:

const ServiceProvider = require('@ostro/support/serviceProvider')
const CustomClient = require('...')
const CustomAdapter = require('...')
const Filesystem = require('@ostro/filesystem/filesystem')
const Storage = require('@ostro/support/facades/storage')
class AppServiceProvider extends ServiceProvider {
    /**
     * Register any application services.
     *
     * @return void
     */
    register() {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    boot() {
        Storage.extend('custom', function ($app, $config) {
            let $client = new CustomClient(
                $config['authorization_token']
            );

            return new Filesystem(new CustomAdapter($client));
        });
    }
}

module.exports = require('AppServiceProvider')