Aviary
Packages

@dudousxd/nestjs-media

The umbrella package — MediaModule, MediaService, the tus controller, and the /storage subpath.

@dudousxd/nestjs-media is the package you import in a Nest app. It wires core into NestJS DI and exposes everything through one service.

MediaModule

forRoot / forRootAsync register a @Global() module. It always provides the storage layer; pass store for the media-library, uploadSessions for resumable uploads, tus to mount the proxy HTTP controller, and direct to mount the direct presigned-multipart controller. See Reference → Configuration for every option.

MediaModule.forRoot({
  default: 'local',
  disks: { local },
  store,                  // → media.library
  uploadSessions,         // → media.uploads
  tus: { disk: 'local' }, // → mounts MediaUploadController
  direct: { disk: 's3' }, // → mounts MediaDirectUploadController (S3)
});

MediaService

The single injectable surface:

class MediaService {
  disk(name?: string): StorageDriver;        // layer 1
  get library(): MediaLibrary;               // layer 2, table model (throws if no store)
  get attachments(): AttachmentManager;      // layer 2, column model (always available)
  get uploads(): ResumableUploadManager;     // resumable proxy (throws if no uploadSessions)
  get directUploads(): DirectUploadManager;  // direct presigned multipart (throws if no direct)
}

media.attachments is the attachment column model and is always provided — it only needs storage (and an image processor for variants), no store or table. media.library is the media table and throws until you pass a store.

@Injectable()
export class Files {
  constructor(private readonly media: MediaService) {}
  save = (p: string, b: Buffer) => this.media.disk('s3').put(p, b);
}

tus controller

When tus is configured, MediaUploadController is mounted at media/uploads, implementing the tus protocol over the resumable engine. Remember the raw-body parser:

app.use('/media/uploads', express.raw({ type: 'application/offset+octet-stream' }));

The upload controllers are unguarded

MediaUploadController (tus) and MediaDirectUploadController (mounted when direct is configured) carry no authentication or authorization guard. Anyone able to reach media/uploads / media/uploads/direct can drive uploads. Add your own guard (a global APP_GUARD or a guard/middleware scoped to media/uploads) to protect them.

Direct upload controller

When direct is configured (with a presign/multipart-capable disk like S3), MediaDirectUploadController is mounted at media/uploads/direct, orchestrating browser-to-S3 presigned multipart uploads over DirectUploadManager (media.directUploads). It exposes initiate / per-part presign / complete / abort — no raw-body parser is needed, since the bytes go straight to S3.

direct: { disk: 's3', partSize: 8 * 1024 * 1024 } // partSize optional, default 8 MiB

The /storage subpath

For consumers that want only the filesystem facade, without the media-library layer:

import { StorageManager, type StorageDriver } from '@dudousxd/nestjs-media/storage';

Injection tokens

import {
  MEDIA_STORAGE, MEDIA_LIBRARY, MEDIA_ATTACHMENTS, MEDIA_UPLOADS, MEDIA_TUS, MEDIA_DIRECT,
} from '@dudousxd/nestjs-media';

Inject MediaService for normal use; reach for the tokens only when you need the raw StorageManager/MediaLibrary/ResumableUploadManager/DirectUploadManager (e.g. wiring a custom controller).

Peer dependencies

@nestjs/common and @nestjs/core (v10 or v11), reflect-metadata, and @dudousxd/nestjs-media-core. Disks, stores, and the image engine are separate installs.

On this page