@dudousxd/nestjs-media
Version:
Filesystem + media-library for NestJS — one package
203 lines (165 loc) • 7.05 kB
Markdown
---
name: media-module-setup
description: >-
Register /nestjs-media's MediaModule in a NestJS app via
MediaModule.forRoot or MediaModule.forRootAsync. Wire the storage layer
(default + disks with LocalDriver / S3Driver), optionally the media-library
layer (store: TypeOrmMediaStore / MikroOrmMediaStore / DrizzleMediaStore /
PrismaMediaStore), an imageProcessor (SharpImageProcessor), collections with
conversions, and uploads (uploadSessions + tus, direct). MediaModule is
; inject MediaService anywhere. Covers MediaModuleOptions, the
forRoot-vs-forRootAsync (DI) choice, injection tokens MEDIA_STORAGE /
MEDIA_LIBRARY / MEDIA_ATTACHMENTS / MEDIA_UPLOADS / MEDIA_TUS / MEDIA_DIRECT,
and the UnknownDiskError when the default disk is missing.
license: MIT
metadata:
type: core
library: "@dudousxd/nestjs-media"
library_version: 0.5.0
framework: nestjs
---
# Registering MediaModule
`MediaModule` is the single NestJS entry point for `/nestjs-media`. It
has two layers, enabled independently:
- **Layer 1 — storage** (`default` + `disks`): always on.
- **Layer 2 — media-library** (`store`, `collections`, `imageProcessor`):
on only when you pass a `store`.
Plus optional resumable (`uploadSessions` + `tus`) and direct (`direct`) uploads.
## Setup
Install the umbrella package plus the à-la-carte disk / store / image packages you
use:
```bash
pnpm add /nestjs-media @dudousxd/nestjs-media-disk-local \
/nestjs-media-disk-s3 @dudousxd/nestjs-media-database-typeorm \
/nestjs-media-image-sharp
```
`MediaModule` is `()` — import it **once** (in `AppModule`) and inject
`MediaService` anywhere.
```ts
// app.module.ts
import { Module } from '@nestjs/common';
import { MediaModule } from '@dudousxd/nestjs-media';
import { LocalDriver } from '@dudousxd/nestjs-media-disk-local';
import { S3Driver } from '@dudousxd/nestjs-media-disk-s3';
import { TypeOrmMediaStore } from '@dudousxd/nestjs-media-database-typeorm';
import { SharpImageProcessor } from '@dudousxd/nestjs-media-image-sharp';
import { S3Client } from '@aws-sdk/client-s3';
import { getDataSourceToken } from '@nestjs/typeorm';
({
imports: [
MediaModule.forRootAsync({
inject: [getDataSourceToken(), S3Client],
useFactory: (ds, s3: S3Client) => ({
default: 's3',
disks: {
s3: new S3Driver({ client: s3, bucket: 'app-uploads' }),
local: new LocalDriver({ root: './storage', baseUrl: 'http://localhost:3000/files' }),
},
store: new TypeOrmMediaStore(ds),
imageProcessor: new SharpImageProcessor(),
collections: [
{ name: 'avatar', single: true, acceptsMimeTypes: ['image/png', 'image/jpeg'] },
{
name: 'gallery',
conversions: [
{ name: 'thumb', width: 200 },
{ name: 'og', width: 1200, height: 630, eager: true },
],
},
],
}),
}),
],
})
export class AppModule {}
```
## Core patterns
### 1. `forRoot` (static) vs `forRootAsync` (DI)
`forRoot` takes a literal `MediaModuleOptions`. Use it when your disks/stores need
no injected providers:
```ts
import { LocalDriver } from '/nestjs-media-disk-local';
MediaModule.forRoot({
default: 'local',
disks: { local: new LocalDriver({ root: './storage', baseUrl: 'http://localhost:3000/files' }) },
});
```
Reach for `forRootAsync` when a disk or store needs an injected dependency (an
`S3Client`, a TypeORM `DataSource`, a MikroORM `EntityManager`, a config service).
`inject` lists the providers; `useFactory` receives them and returns the same
`MediaModuleOptions` shape (sync or `Promise`).
### 2. Pick a store for layer 2
Each ORM store is a plain object you construct in the factory with its connection
(no ``, no internal token):
```ts
import { MikroOrmMediaStore } from '/nestjs-media-database-mikro-orm'; // new MikroOrmMediaStore(em)
import { DrizzleMediaStore } from '/nestjs-media-database-drizzle'; // new DrizzleMediaStore(db)
import { PrismaMediaStore } from '/nestjs-media-database-prisma'; // new PrismaMediaStore(prisma)
```
TypeORM and MikroORM stores create the `media` table on first use
(non-destructive). Drizzle is migration-first; Prisma's schema is
consumer-managed. Omit `store` entirely for a raw-storage-only app — then
`media.disk(...)` works but `media.library` throws.
### 3. Inject `MediaService`, or the tokens directly
```ts
import { Injectable } from '/common';
import { MediaService } from '/nestjs-media';
()
export class PhotosService {
constructor(private readonly media: MediaService) {}
}
```
For advanced wiring the module exports the underlying providers as tokens:
`MEDIA_STORAGE` (`StorageManager`), `MEDIA_LIBRARY` (`MediaLibrary | null`),
`MEDIA_ATTACHMENTS` (`AttachmentManager`), `MEDIA_UPLOADS`
(`ResumableUploadManager | null`), `MEDIA_TUS` (`TusUploadHandler | null`),
`MEDIA_DIRECT` (`DirectUploadManager | null`).
## Common mistakes
### Mistake 1 — `default` names a disk that isn't in `disks`
```ts
// Wrong — StorageManager throws UnknownDiskError at construction.
MediaModule.forRoot({
default: 's3',
disks: { local: new LocalDriver({ root: './storage' }) }, // no 's3' key
});
// Correct — the default key must exist in disks.
MediaModule.forRoot({
default: 'local',
disks: { local: new LocalDriver({ root: './storage' }) },
});
```
`StorageManager`'s constructor validates `default` against `disks` and throws
`UnknownDiskError` immediately if missing. Source:
`packages/core/src/storage-manager.ts`.
### Mistake 2 — using `forRoot` when a disk needs an injected client
```ts
// Wrong — S3Client isn't resolvable here; you end up newing it by hand,
// duplicating config and losing Nest's lifecycle.
MediaModule.forRoot({
default: 's3',
disks: { s3: new S3Driver({ client: new S3Client({}), bucket: 'app-uploads' }) },
});
// Correct — inject the provider through forRootAsync.
MediaModule.forRootAsync({
inject: [S3Client],
useFactory: (s3: S3Client) => ({
default: 's3',
disks: { s3: new S3Driver({ client: s3, bucket: 'app-uploads' }) },
}),
});
```
`forRootAsync.useFactory` receives `inject`ed providers; `forRoot` cannot resolve
DI. Source: `packages/nestjs/src/media.module.ts`.
### Mistake 3 — importing `MediaModule` again in a feature module
```ts
// Wrong — MediaModule is @Global(); re-registering it per feature module
// rebuilds StorageManager/MediaLibrary with (possibly) different config.
({ imports: [MediaModule.forRoot({ /* ... */ })] })
export class PhotosModule {}
// Correct — register once in AppModule; just inject MediaService downstream.
({ providers: [PhotosService] })
export class PhotosModule {}
```
`MediaModule` is decorated `()`, so a single root registration exports
`MediaService` and every token app-wide. Source:
`packages/nestjs/src/media.module.ts`.