@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 MiBThe /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.