Skip to content

file-storage

The file-storage package provides a key/value storage interface for File objects. It ships with two backends: a filesystem backend for writing files to disk and an in-memory backend for testing and development.

Installation

The file storage package is included with Remix. No additional installation is needed.

Import

ts
import { FileStorage } from 'remix/file-storage'

Backend-specific imports:

ts
import { createFsFileStorage } from 'remix/file-storage/fs'
import { createMemoryFileStorage } from 'remix/file-storage/memory'

API

FileStorage Interface

All file storage backends implement this interface:

ts
interface FileStorage {
  set(key: string, file: File): Promise<void>
  get(key: string): Promise<File | null>
  has(key: string): Promise<boolean>
  remove(key: string): Promise<void>
}

set(key, file)

Stores a file under the given key. If a file already exists at that key, it is overwritten.

ts
await storage.set('avatars/user-123.jpg', file)

get(key)

Retrieves a file by key. Returns null if no file exists at that key.

ts
let file = await storage.get('avatars/user-123.jpg')

if (file) {
  console.log(file.name) // 'user-123.jpg'
  console.log(file.type) // 'image/jpeg'
  console.log(file.size) // 1048576
}

has(key)

Returns true if a file exists at the given key.

ts
if (await storage.has('avatars/user-123.jpg')) {
  // File exists
}

remove(key)

Deletes the file at the given key. No error is thrown if the key does not exist.

ts
await storage.remove('avatars/user-123.jpg')

Backends

createFsFileStorage(options)

Creates a file storage backend that reads and writes files to the local filesystem.

ts
import { createFsFileStorage } from 'remix/file-storage/fs'

let storage = createFsFileStorage({
  directory: './uploads',
})
OptionTypeDescription
directorystringThe root directory for stored files. Created automatically if it does not exist.

The key maps directly to the file path within the directory. For example, storage.set('photos/cat.jpg', file) writes to ./uploads/photos/cat.jpg.

createMemoryFileStorage()

Creates an in-memory file storage backend. Useful for testing and development.

ts
import { createMemoryFileStorage } from 'remix/file-storage/memory'

let storage = createMemoryFileStorage()

All files are stored in memory and lost when the process exits.

Examples

Upload Handler with Filesystem Storage

ts
import { parseFormData } from 'remix/form-data-parser'
import { createFsFileStorage } from 'remix/file-storage/fs'

let storage = createFsFileStorage({ directory: './uploads' })

router.map(uploadRoute, async ({ request }) => {
  let formData = await parseFormData(request, {}, async (fileUpload) => {
    let key = `${Date.now()}-${fileUpload.name}`
    await storage.set(key, fileUpload)
    return key
  })

  let fileKey = formData.get('file') as string
  return new Response(`Stored as ${fileKey}`)
})

Serving Stored Files

ts
import { createFsFileStorage } from 'remix/file-storage/fs'
import { createFileResponse } from 'remix/response/file'

let storage = createFsFileStorage({ directory: './uploads' })

router.map(fileRoute, async ({ request, params }) => {
  let file = await storage.get(params.key)

  if (!file) {
    return new Response('Not Found', { status: 404 })
  }

  return createFileResponse(file, request)
})

Testing with In-Memory Storage

ts
import { describe, it } from 'remix/test'
import { createMemoryFileStorage } from 'remix/file-storage/memory'

describe('file upload', () => {
  it('stores a file', async (t) => {
    let storage = createMemoryFileStorage()
    let file = new File(['hello'], 'test.txt', { type: 'text/plain' })

    await storage.set('test.txt', file)

    t.assert.ok(await storage.has('test.txt'))

    let retrieved = await storage.get('test.txt')
    t.assert.equal(await retrieved!.text(), 'hello')
  })
})

Released under the MIT License.