Skip to the content
OrielDocs
0.1.0-alpha.10GitHub

Digging deeper

File storage

Oriel's Storage reads and writes files on disks: folders on your server, or an Amazon S3 bucket (or any S3-compatible service), through one API.

Configuration#

Your disks are in config/filesystems.ts. Each disk names its driver, local or s3, and its settings; default names the disk Storage uses when you don't name one (FILESYSTEM_DISK, else local). A new app has three disks: local, public and s3.

The local driver#

A local disk's paths are relative to its root, an absolute folder. The local disk's root is storage/app/private:

import { Storage } from '@orieljs/framework'await Storage.disk('local').put('example.txt', 'Contents')

A path that climbs out of the root with ../ throws.

The public disk#

The public disk is for files anyone may download: its root is storage/app/public, its files are written public, and its url is APP_URL with /storage. To serve those files, link public/storage to storage/app/public:

Terminal
node oriel storage:link

It always makes that one link. --relative makes it relative, and --force makes it again. The links entry of a new app's config/filesystems.ts isn't read yet.

The S3 driver#

The s3 disk reads AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, AWS_BUCKET, AWS_URL, AWS_ENDPOINT and AWS_USE_PATH_STYLE_ENDPOINT from your .env. For an S3-compatible service, such as MinIO or Cloudflare R2, set endpoint, and usePathStyleEndpoint if it wants the bucket in the path.

Obtaining disk instances#

Storage works on the default disk; Storage.disk(name) on another:

import { Storage } from '@orieljs/framework'await Storage.put('avatars/1.txt', 'Contents')await Storage.disk('s3').put('avatars/1.txt', 'Contents')

Retrieving files#

Every method is async. get() gives a file's contents as a string, getBytes() as a Uint8Array, and readStream() as a web ReadableStream. A missing file throws.

import { Storage } from '@orieljs/framework'const contents = await Storage.get('file.txt')const bytes = await Storage.getBytes('photo.jpg')const stream = await Storage.readStream('video.mp4')if (await Storage.exists('file.txt')) {  // ...}

missing() is exists()'s opposite.

File URLs#

url() gives a file's public URL: the disk's url with the path. A local disk with no url, such as local, throws.

import { Storage } from '@orieljs/framework'const url = Storage.disk('public').url('avatars/1.jpg')

On an S3 disk, url() encodes the key, segment by segment, and joins it to the configured url (a CDN's address, for example). Without one, it uses the endpoint, else https://s3.<region>.amazonaws.com (us-east-1 when no region is set), with the bucket as a subdomain, or in the path when usePathStyleEndpoint is on.

Temporary URLs#

On an S3 disk, temporaryUrl() gives a signed URL that works until the date you give:

import { Storage } from '@orieljs/framework'const url = await Storage.disk('s3').temporaryUrl('report.pdf', new Date(Date.now() + 5 * 60_000))

A local disk can't sign URLs, and throws.

File metadata#

size() gives a file's size in bytes, lastModified() a Date, and mimeType() its type, or undefined. On a local disk, path() gives the file's absolute path.

Storing files#

put() writes a string, a Uint8Array or a web ReadableStream, creating the folders it needs:

import { Storage } from '@orieljs/framework'await Storage.put('file.txt', 'Contents')await Storage.put('file.txt', 'Contents', { visibility: 'public' })

copy(from, to) and move(from, to) copy and move a file on the same disk.

File uploads#

An uploaded file's store() saves it in a folder under a random name, with the extension its contents show, and gives back the path. storeAs() takes the name. Both take a disk's name last:

routes/web.ts
import { request, Route } from '@orieljs/framework'Route.post('/avatar', async () => {  const file = request().file('avatar')  const path = await file!.store('avatars')  await file!.storeAs('avatars', 'user-1.jpg', 's3')  return { path }})

Storage.putFile(directory, file) and Storage.putFileAs(directory, file, name) do the same from the disk's side.

File visibility#

A file is public or private. On a local disk that is the file's mode: 0644 or 0600 for files, and 0755 or 0700 for the folders makeDirectory() makes; a folder put() makes has the default mode. A disk's visibility setting is its default, and put()'s visibility option sets it for one file.

Deleting files#

delete() takes a path or an array of paths:

import { Storage } from '@orieljs/framework'await Storage.delete('file.txt')await Storage.delete(['file.jpg', 'file2.jpg'])

Directories#

files(directory) lists a folder's files, and files(directory, true) its subfolders' too; directories() does the same for folders. makeDirectory() creates a folder and deleteDirectory() deletes one with everything in it.

Live uploads#

A Live component's upload is first stored as a temporary file, under live-tmp on your default disk, with a random name. Your component's store() moves it to its place. When an upload finishes, temporary files older than a day are deleted, except on an S3 disk, where listing the folder on every upload would be slow: give the bucket a lifecycle rule for live-tmp instead. live.temporary_file_upload.disk and directory choose another place.

Testing#

Storage.fake() swaps a disk for one in memory, so a test writes no files:

import { Storage } from '@orieljs/framework'const disk = Storage.fake('avatars')// Upload the avatar...await disk.assertExists('avatar.jpg')

Fakes covers fakes in full.

Custom filesystems#

Storage.extend(driver, factory) adds a driver, in a provider's boot() method: the factory takes a disk's settings and returns an object with the Filesystem contract's methods.

Privacy policy ·

esc