NestboltNestbolt

@nestbolt/medialibrary

Querying Media

Retrieve, manage, reorder, and delete media using MediaService methods and the entity mixin.

Once media is uploaded, MediaService provides methods to query, retrieve URLs, reorder, and delete media. The HasMediaMixin also adds convenience methods directly to entity instances.

Retrieving Media

getMedia(modelType, modelId, collectionName?)

Returns all media for an entity, optionally filtered by collection. Results are ordered by orderColumn ascending, then createdAt ascending:

// All media for a post
const allMedia = await mediaService.getMedia("Post", postId);

// Only images collection
const images = await mediaService.getMedia("Post", postId, "images");

// Only documents collection
const docs = await mediaService.getMedia("Post", postId, "documents");

Returns an empty array if no media exists.

getFirstMedia(modelType, modelId, collectionName?)

Returns the first media item (by order and creation date), or null if none exists:

const avatar = await mediaService.getFirstMedia("User", userId, "avatar");
if (avatar) {
  console.log(avatar.fileName);  // "profile.jpg"
  console.log(avatar.mimeType);  // "image/jpeg"
  console.log(avatar.size);      // 245760
}

getLastMedia(modelType, modelId, collectionName?)

Returns the last media item (by order and creation date), or null if none exists:

const latestUpload = await mediaService.getLastMedia("Post", postId, "images");

hasMedia(modelType, modelId, collectionName?)

Returns true if at least one media item exists:

const hasAvatar = await mediaService.hasMedia("User", userId, "avatar");
if (!hasAvatar) {
  // Show default avatar
}

URL Generation

getUrl(media, conversionName?)

Returns the URL for a media item. Pass a conversion name to get the URL for a specific conversion:

const media = await mediaService.getFirstMedia("Post", postId, "images");

// Original file URL
const originalUrl = mediaService.getUrl(media);
// e.g., "/media/a1b2c3d4-.../photo.jpg"

// Thumbnail conversion URL
const thumbUrl = mediaService.getUrl(media, "thumbnail");
// e.g., "/media/a1b2c3d4-.../conversions/thumbnail-photo.webp"

The URL format depends on the configured disk and module options:

  • Local disk with urlBase: /<urlBase>/<path>
  • S3 disk: https://<bucket>.s3.<region>.amazonaws.com/<key>
  • With baseUrl configured: <baseUrl>/<path> (regardless of disk)

getPath(media, conversionName?)

Returns the storage path (not a URL) for a media item:

const path = mediaService.getPath(media);
// e.g., "media/a1b2c3d4-.../photo.jpg"

const thumbPath = mediaService.getPath(media, "thumbnail");
// e.g., "media/a1b2c3d4-.../conversions/thumbnail-photo.webp"

getTemporaryUrl(media, expiration, conversionName?, options?)

Generates a presigned temporary URL for S3-stored media. The URL expires at the given Date:

// URL valid for 1 hour
const tempUrl = await mediaService.getTemporaryUrl(
  media,
  new Date(Date.now() + 60 * 60 * 1000),
);

// Temporary URL for a conversion, valid for 30 minutes
const tempThumbUrl = await mediaService.getTemporaryUrl(
  media,
  new Date(Date.now() + 30 * 60 * 1000),
  "thumbnail",
);

This requires @aws-sdk/s3-request-presigner to be installed. Calling this on a local disk throws an error, as temporary URLs are not supported by the local driver.

MediaEntity Properties

Each media record is a MediaEntity instance with the following properties and methods:

Properties

PropertyTypeDescription
idstringUUID primary key
modelTypestringAssociated entity type (e.g., "Post")
modelIdstringAssociated entity ID
uuidstringUnique identifier
collectionNamestringCollection name (e.g., "images")
namestringDisplay name
fileNamestringStorage file name
mimeTypestringMIME type (e.g., "image/jpeg")
diskstringStorage disk name
conversionsDiskstring | nullDisk for conversions
sizenumberFile size in bytes
orderColumnnumber | nullSort order
createdAtDateCreation timestamp
updatedAtDateLast update timestamp

Computed Properties

PropertyTypeDescription
humanReadableSizestringFormatted size (e.g., "2.3 MB")
extensionstringFile extension (e.g., "jpg")
typestringFile type category (e.g., "image")
console.log(media.humanReadableSize); // "2.3 MB"
console.log(media.extension);         // "jpg"
console.log(media.type);              // "image"

Custom Properties

Custom properties are stored as JSON in the customProperties column. The MediaEntity provides dot-notation access:

// Read
media.getCustomProperty("alt");                    // "Sunset photo"
media.getCustomProperty("missing", "default");     // "default"
media.hasCustomProperty("alt");                    // true

// Write
media.setCustomProperty("alt", "Updated alt text");
media.setCustomProperty("meta.author", "Jane");    // nested key

// Read nested
media.getCustomProperty("meta.author");            // "Jane"

// Delete
media.forgetCustomProperty("alt");
media.forgetCustomProperty("meta.author");

After modifying custom properties, save the entity to persist changes:

media.setCustomProperty("alt", "New description");
await mediaRepo.save(media);

Conversion Status

// Check if a conversion was generated
media.hasGeneratedConversion("thumbnail"); // true

// View all generated conversions
console.log(media.generatedConversions);
// { thumbnail: true, preview: true }

// Mark conversions (used internally)
media.markConversionAsGenerated("custom");
media.markConversionAsNotGenerated("thumbnail");

Entity Mixin Methods

If your entity extends HasMediaMixin, you can query and manage media directly on entity instances without injecting MediaService:

const post = await postRepo.findOneBy({ id: postId });

// Get all media for a collection
const images = await post.getMedia("images");

// Get first media
const firstImage = await post.getFirstMedia("images");

// Get URL of first media with a conversion
const thumbUrl = await post.getFirstMediaUrl("images", "thumbnail");
// Returns "" if no media exists

// Check existence
const hasImages = await post.hasMedia("images");

Adding Media via the Mixin

The mixin also provides an addMedia() method for uploading:

// From a file path
await post.addMedia("/path/to/photo.jpg").toMediaCollection("images");

// From a buffer
await post.addMedia(imageBuffer, "photo.jpg").toMediaCollection("images");

The mixin's addMedia() automatically calls .forModel() with the entity's type and ID.

Managing Media

Deleting a Single Media Item

await mediaService.deleteMedia(media.id);

This removes the database record and deletes the file (and its conversions) from storage. A media.deleted event is emitted.

Deleting All Media for an Entity

await mediaService.deleteAllMedia("Post", postId);

Removes all media records and files for the given entity, across all collections.

Clearing a Collection

await mediaService.clearMediaCollection("Post", postId, "images");

Removes all media in the specified collection for the entity. A media.collection-cleared event is emitted.

Via the entity mixin:

await post.clearMediaCollection("images");
await post.deleteAllMedia();

Automatic Cleanup on Entity Deletion

When an entity decorated with @HasMedia() is removed via TypeORM, the MediaSubscriber automatically deletes all associated media files. This works with both repository.remove(entity) and repository.delete(id) (when the entity is loaded):

// All media for this post is automatically deleted
await postRepo.remove(post);

Reordering Media

Set a custom display order for media items within a collection:

// Get current media
const images = await mediaService.getMedia("Post", postId, "images");

// Reorder: put the third image first
await mediaService.setOrder([
  images[2].id,
  images[0].id,
  images[1].id,
]);

The setOrder() method takes an array of media IDs in the desired order. Each media item's orderColumn is updated to match its position in the array (0-indexed). The operation runs in a database transaction.

After reordering, subsequent calls to getMedia() return items in the new order.

Regenerating Conversions

Regenerate image conversions for existing media. This is useful when you add new conversions to an entity class or want to reprocess images:

// Regenerate all conversions
await mediaService.regenerateConversions(media);

// Regenerate specific conversions
await mediaService.regenerateConversions(media, ["thumbnail", "preview"]);

The original file is read from storage, all applicable conversions are re-applied, and the conversion files are overwritten.

Query Patterns

Get all media URLs for an entity

const images = await mediaService.getMedia("Post", postId, "images");
const urls = images.map((m) => ({
  id: m.id,
  original: mediaService.getUrl(m),
  thumbnail: mediaService.getUrl(m, "thumbnail"),
  alt: m.getCustomProperty("alt", ""),
}));

Get avatar with fallback

const avatar = await mediaService.getFirstMedia("User", userId, "avatar");
const avatarUrl = avatar
  ? mediaService.getUrl(avatar)
  : "/defaults/avatar.png";

Paginate media manually

const allImages = await mediaService.getMedia("Post", postId, "gallery");
const page = 1;
const perPage = 12;
const paginated = allImages.slice((page - 1) * perPage, page * perPage);

Find media by custom property

const allMedia = await mediaService.getMedia("Product", productId, "images");
const featured = allMedia.filter(
  (m) => m.getCustomProperty("featured") === true,
);