@nestbolt/medialibrary
Adding Media
All methods for uploading files -- from paths, buffers, streams, URLs, and base64 -- plus the complete FileAdder chain API.
MediaService provides five factory methods for uploading files from different sources. Each method returns a FileAdder instance that implements a fluent builder pattern. You chain configuration calls and finalize the upload by calling .toMediaCollection().
Upload Methods
From a File Path
Use addMedia() to upload a file from the local filesystem:
const media = await mediaService
.addMedia("/path/to/photo.jpg")
.forModel("Post", postId)
.toMediaCollection("images");The file is read from disk, and its name is inferred from the path. If the file does not exist, a FileDoesNotExistException is thrown.
From a Buffer
Use addMediaFromBuffer() when you already have the file contents in memory, such as from a Multer upload or an API response:
const media = await mediaService
.addMediaFromBuffer(file.buffer, file.originalname)
.forModel("Post", postId)
.toMediaCollection("images");The second argument is the file name, which is used for MIME type detection and as the default storage name.
From a Readable Stream
Use addMediaFromStream() for large files or when processing data incrementally:
import { createReadStream } from "fs";
const stream = createReadStream("/path/to/large-video.mp4");
const media = await mediaService
.addMediaFromStream(stream, "video.mp4")
.forModel("Post", postId)
.toMediaCollection("videos");The stream is consumed and buffered internally before storage. The second argument provides the file name.
From a URL
Use addMediaFromUrl() to download a file from a remote URL and attach it. Note that this method is async and returns a Promise<FileAdder>:
const adder = await mediaService.addMediaFromUrl("https://example.com/photo.jpg");
const media = await adder
.forModel("Post", postId)
.toMediaCollection("images");Or using await inline:
const media = await (
await mediaService.addMediaFromUrl("https://example.com/photo.jpg")
)
.forModel("Post", postId)
.withCustomProperties({ source: "https://example.com/photo.jpg" })
.toMediaCollection("images");The file name is extracted from the URL path. HTTP and HTTPS URLs are supported, and redirects are followed automatically (up to 5 redirects).
From a Base64 String
Use addMediaFromBase64() for files encoded as base64, such as from a JSON API payload or data URI:
const media = await mediaService
.addMediaFromBase64(base64String, "avatar.png")
.forModel("User", userId)
.toMediaCollection("avatar");Data URI prefixes (e.g., data:image/png;base64,) are automatically stripped before decoding.
FileAdder Chain Methods
Every upload method returns a FileAdder instance. The following methods can be chained before calling .toMediaCollection() to configure the upload.
forModel(modelType, modelId)
Required. Associates the media with an entity. The modelType is typically the entity class name, and modelId is its primary key:
.forModel("Post", post.id)
.forModel("User", "550e8400-e29b-41d4-a716-446655440000")Both arguments are strings. If your entity uses numeric IDs, convert them: .forModel("Post", String(post.id)).
usingName(name)
Sets a human-readable display name for the media item. By default, the name is derived from the file name (without extension):
.usingName("Company Logo")This value is stored in the name column of the MediaEntity and can be used for display purposes in your UI.
usingFileName(fileName)
Overrides the file name used for storage. By default, the original file name (after sanitization) is used:
.usingFileName("logo-2024.png")The file name is still passed through the sanitizer unless you also override it with sanitizingFileName().
toDisk(diskName)
Overrides the storage disk for this specific upload. By default, the collection's disk is used, or the global defaultDisk:
.toDisk("s3")storingConversionsOnDisk(diskName)
Stores image conversions on a different disk than the original file. Useful for storing originals locally but serving conversions from a CDN-backed S3 bucket:
.toDisk("local")
.storingConversionsOnDisk("s3")withCustomProperties(properties)
Attaches arbitrary JSON metadata to the media record. Properties are merged with any existing custom properties:
.withCustomProperties({ alt: "Sunset over the ocean", credit: "John Doe" })You can call this method multiple times to merge additional properties:
.withCustomProperties({ alt: "Sunset" })
.withCustomProperties({ credit: "John Doe" })
// Result: { alt: "Sunset", credit: "John Doe" }Custom properties can be read, updated, and deleted later on the MediaEntity instance. See Querying Media for details.
withManipulations(manipulations)
Attaches manipulation metadata to the media record. This is stored as JSON but does not trigger any automatic processing:
.withManipulations({ crop: { x: 10, y: 20, width: 200, height: 200 } })setOrder(order)
Sets the sort order for the media item within its collection. Media items are returned sorted by orderColumn ascending, then by createdAt ascending:
.setOrder(0) // first
.setOrder(5) // sixthYou can also reorder media after upload using mediaService.setOrder().
preservingOriginal(preserve?)
When uploading from a file path, this option controls whether the source file is preserved after upload. By default, the source file is not removed. Call with no arguments or true to explicitly preserve:
.preservingOriginal()
.preservingOriginal(true)
// To allow removal:
.preservingOriginal(false)sanitizingFileName(sanitizer)
Provides a custom function to sanitize the file name before storage. The default sanitizer removes special characters and replaces spaces with hyphens:
.sanitizingFileName((name) => name.toLowerCase().replace(/\s+/g, "-"))The sanitizer receives the base name (without extension) and should return the sanitized base name. The extension is preserved automatically.
toMediaCollection(collectionName?, diskName?)
Finalizer. Saves the file to the specified collection and returns the created MediaEntity. This is an async operation:
const media = await fileAdder.toMediaCollection("images");If no collection name is provided, the file is saved to the "default" collection:
const media = await fileAdder.toMediaCollection();An optional second argument overrides the disk:
const media = await fileAdder.toMediaCollection("images", "s3");Complete Example
Here is an example using all FileAdder options together:
const media = await mediaService
.addMediaFromBuffer(photoBuffer, "vacation-photo.jpg")
.forModel("Post", post.id)
.usingName("Vacation Photo 2024")
.usingFileName("vacation-2024.jpg")
.toDisk("s3")
.storingConversionsOnDisk("s3")
.withCustomProperties({
alt: "Beach sunset",
credit: "Jane Doe",
location: { lat: 25.7617, lng: -80.1918 },
})
.setOrder(0)
.preservingOriginal()
.sanitizingFileName((name) => name.toLowerCase().replace(/\s+/g, "-"))
.toMediaCollection("images");
console.log(media.id); // "a1b2c3d4-..."
console.log(media.name); // "Vacation Photo 2024"
console.log(media.fileName); // "vacation-2024.jpg"
console.log(media.disk); // "s3"
console.log(media.collectionName); // "images"
console.log(media.size); // 2457600
console.log(media.humanReadableSize); // "2.3 MB"
console.log(media.mimeType); // "image/jpeg"
console.log(media.getCustomProperty("alt")); // "Beach sunset"Upload Validation
During the upload process, the following validations are performed:
- File existence -- When uploading from a file path, the file must exist on disk.
- Global max file size -- The file size is checked against the
maxFileSizeoption (default: 10 MB). ThrowsFileIsTooBigException. - Collection MIME type -- If the target collection has
acceptsMimeTypes, the file's MIME type must match. ThrowsFileUnacceptableException. - Collection max file size -- If the target collection has a
maxFileSize, it takes precedence over the global setting. ThrowsFileIsTooBigException. - Custom file validator -- If the target collection has an
acceptsFilepredicate, it is called with the file info. ThrowsFileUnacceptableExceptionon rejection. - Single file enforcement -- If the collection is configured with
singleFile(), existing files in the collection are deleted before the new file is saved. - Collection size limit -- If the collection uses
onlyKeepLatest(n), the oldest files are removed to stay within the limit.
Error Handling
All upload errors are thrown as typed exceptions that extend standard NestJS HttpException:
import {
FileDoesNotExistException,
FileIsTooBigException,
FileUnacceptableException,
} from "@nestbolt/medialibrary";
try {
await mediaService
.addMediaFromBuffer(buffer, "file.exe")
.forModel("Post", postId)
.toMediaCollection("images");
} catch (error) {
if (error instanceof FileUnacceptableException) {
// MIME type not accepted by the "images" collection
console.log(error.message);
}
if (error instanceof FileIsTooBigException) {
// File exceeds max size
console.log(error.message);
}
}