v0.21

storage

introduction

dframework provides a file system abstraction through the Storage facade. it allows you to easily read, write, and manage files across multiple configured "disks". the storage service automatically manages path traversal protection, symlinking public assets, concurrent operation queuing, and even disk space verification.

configuration

the storage configuration is defined within the storage.disks configuration namespace in config/storage.js. a typical configuration might include a local disk for private files, a public disk for assets meant to be publicly accessible, and a secure disk for encrypted files.

1// config/storage.js
2import { Env } from 'dframework';
3
4export default {
5 disks: {
6 local: {
7 root: './storage/local', // private files
8 },
9 public: {
10 root: './storage/public', // web accessible files
11 },
12 secure: {
13 root: './storage/secure',
14 encryptionKey: Env.value('APP_KEY'), // enables automatic aes-256-gcm encryption
15 },
16 },
17};

dframework automatically creates a symbolic link from public/storage to storage/public upon boot, allowing files stored on the public disk to be served directly to the browser.

obtaining a disk

the preferred canonical pattern is calling Storage.disk(name) to obtain a disk instance. omitting the disk name resolves the default disk configured in config/storage.js (falling back to public).

for convenience, the Storage facade also proxies operations directly (Storage.put(), Storage.get(), Storage.delete(), etc.) to the default disk.

1import { Storage } from 'dframework';
2
3// preferred canonical pattern on default disk
4await Storage.disk().put('notes.txt', 'hello');
5
6// direct proxy shorthand to default disk
7await Storage.put('notes.txt', 'hello');
8
9// target a specific configured disk
10const disk = Storage.disk('local');

storing files

to write a file to a disk, use the put method. it accepts the relative file path as the first argument, and the file contents (as a string or buffer) as the second argument. the method will automatically create any necessary directories.

1await Storage.disk().put('avatars/1.jpg', imageBuffer);
2await Storage.disk('local').put('exports/report.csv', 'id,name\n1,tarou');

file uploads

the put method is fully aware of multipart file objects generated during http uploads. you can pass the uploaded file object directly to the put method, and the framework will stream it to its destination without requiring manual buffer extraction.

1// assuming `req.file('avatar')` is a multiparty file upload
2const path = await Storage.disk().put('avatars/user.jpg', req.file('avatar'));

retrieving files

use the get method to read a file's contents. by default, it returns a raw Buffer. if you prefer a string, you can pass the desired encoding (like utf8) as the second argument.

1const raw = await Storage.disk('local').get('documents/contract.pdf');
2const text = await Storage.disk('local').get('reports/summary.txt', 'utf8');

you can verify if a file exists on the disk using the exists method.

1if (await Storage.disk().exists('avatars/1.jpg')) {
2 // file exists
3}

if you need the absolute server path to a file on the disk, use the path method.

1const absolutePath = Storage.disk('local').path('exports/report.csv');

deleting files

to remove a file, pass the path to the delete method. it returns true if the file was deleted, and false if the file did not exist.

1await Storage.disk().delete('avatars/1.jpg');

file management

the framework provides convenience methods for moving and copying files within the same disk.

1// rename or move a file
2await Storage.disk().move('temp/draft.txt', 'published/final.txt');
3
4// duplicate a file
5await Storage.disk().copy('templates/invoice.pdf', 'invoices/123.pdf');

encryption

disks configured with an encryptionKey automatically encrypt on write and decrypt on read. the algorithm is aes-256-gcm. the key is derived from the provided string via sha256. the encryption key is never stored in the file. it lives only in the environment configuration (APP_KEY). the api is identical to unencrypted disks.

the on disk format is: 12 byte iv + 16 byte auth tag + ciphertext. the auth tag provides tamper detection. reads will fail if the file has been modified.

1// write, encrypted transparently
2await Storage.disk('secure').put('keys/api-key.txt', 'my-secret-value');
3
4// read, decrypted transparently
5const value = await Storage.disk('secure').get('keys/api-key.txt', 'utf8');

disk space protection

to prevent the application from crashing due to disk exhaustion, the storage service performs active health checks before writing data.

NOTE

before writing data with `put()`, the storage service verifies that the target disk has sufficient capacity for the payload plus a 100 megabyte safety buffer. if space is insufficient, the operation is safely aborted.