@nestbolt/medialibrary
Quick Start
Get up and running with @nestbolt/medialibrary in five minutes -- module setup, file upload, entity association, and URL retrieval.
This guide walks through a complete working example: configuring the module, uploading a file, associating it with an entity, and retrieving its URL.
1. Register the Module
Import MediaModule in your root AppModule and call forRoot() with your disk configuration. The module registers globally, so you only need to import it once.
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { MediaModule } from "@nestbolt/medialibrary";
import { Post } from "./posts/post.entity";
@Module({
imports: [
TypeOrmModule.forRoot({
type: "postgres",
host: "localhost",
port: 5432,
database: "myapp",
username: "postgres",
password: "postgres",
entities: [Post],
synchronize: true, // development only
}),
MediaModule.forRoot({
defaultDisk: "local",
disks: {
local: {
driver: "local",
root: "./uploads",
urlBase: "/media",
},
},
}),
],
})
export class AppModule {}2. Create Your Entity
Decorate your entity with @HasMedia() and extend HasMediaMixin to get built-in media methods. Define a collection and an image conversion:
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from "typeorm";
import {
HasMedia,
HasMediaMixin,
RegisterMediaCollections,
RegisterMediaConversions,
} from "@nestbolt/medialibrary";
@Entity("posts")
@HasMedia()
@RegisterMediaCollections((addCollection) => {
addCollection("images")
.acceptsMimeTypes(["image/jpeg", "image/png", "image/webp"])
.onlyKeepLatest(10);
})
@RegisterMediaConversions((addConversion) => {
addConversion("thumbnail")
.resize(300, 300, { fit: "cover" })
.format("webp")
.quality(80)
.performOnCollections("images");
})
export class Post extends HasMediaMixin(BaseEntity) {
@PrimaryGeneratedColumn("uuid")
id!: string;
@Column()
title!: string;
}3. Build a Service That Uploads Files
Inject MediaService and use it to upload files and query media:
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { MediaService, MediaEntity } from "@nestbolt/medialibrary";
import { Post } from "./post.entity";
@Injectable()
export class PostService {
constructor(
@InjectRepository(Post)
private readonly postRepo: Repository<Post>,
private readonly mediaService: MediaService,
) {}
async createPost(title: string, imageBuffer: Buffer, fileName: string): Promise<Post> {
const post = this.postRepo.create({ title });
await this.postRepo.save(post);
// Upload the image and associate it with the post
await this.mediaService
.addMediaFromBuffer(imageBuffer, fileName)
.forModel("Post", post.id)
.withCustomProperties({ alt: title })
.toMediaCollection("images");
return post;
}
async getPostImages(postId: string): Promise<MediaEntity[]> {
return this.mediaService.getMedia("Post", postId, "images");
}
async getPostThumbnailUrl(postId: string): Promise<string> {
const media = await this.mediaService.getFirstMedia("Post", postId, "images");
if (!media) return "";
return this.mediaService.getUrl(media, "thumbnail");
}
}4. Create a Controller
Wire up an HTTP endpoint that accepts file uploads:
import {
Controller,
Post as HttpPost,
UploadedFile,
UseInterceptors,
Param,
Get,
Body,
} from "@nestjs/common";
import { FileInterceptor } from "@nestjs/platform-express";
import { PostService } from "./post.service";
@Controller("posts")
export class PostController {
constructor(private readonly postService: PostService) {}
@HttpPost()
@UseInterceptors(FileInterceptor("image"))
async create(
@Body("title") title: string,
@UploadedFile() file: Express.Multer.File,
) {
const post = await this.postService.createPost(
title,
file.buffer,
file.originalname,
);
return { id: post.id, title: post.title };
}
@Get(":id/images")
async getImages(@Param("id") id: string) {
const images = await this.postService.getPostImages(id);
return images.map((media) => ({
id: media.id,
fileName: media.fileName,
size: media.humanReadableSize,
url: this.postService.mediaService.getUrl(media),
thumbnailUrl: this.postService.mediaService.getUrl(media, "thumbnail"),
}));
}
}5. Using the Entity Mixin Directly
Because Post extends HasMediaMixin, you can also work with media directly on entity instances without injecting MediaService:
const post = await postRepo.findOneBy({ id: postId });
// Upload via the entity
await post.addMedia(imageBuffer, "photo.jpg").toMediaCollection("images");
// Query via the entity
const images = await post.getMedia("images");
const firstImage = await post.getFirstMedia("images");
const thumbnailUrl = await post.getFirstMediaUrl("images", "thumbnail");
const hasImages = await post.hasMedia("images");
// Clean up
await post.clearMediaCollection("images");6. Test It
Upload a file using curl:
curl -X POST http://localhost:3000/posts \
-F "title=My First Post" \
-F "image=@/path/to/photo.jpg"Then retrieve the images:
curl http://localhost:3000/posts/<post-id>/imagesThe response includes URLs for both the original image and the auto-generated thumbnail conversion:
[
{
"id": "a1b2c3d4-...",
"fileName": "photo.jpg",
"size": "1.2 MB",
"url": "/media/a1b2c3d4-.../photo.jpg",
"thumbnailUrl": "/media/a1b2c3d4-.../conversions/thumbnail-photo.webp"
}
]What Happened Behind the Scenes
addMediaFromBuffer()created aFileAdderinstance with the buffer and file name..forModel("Post", post.id)associated the upload with yourPostentity..toMediaCollection("images")triggered the upload pipeline:- The file was validated against the
"images"collection rules (MIME type check, size limit enforcement of 10 latest items). - A
MediaEntityrecord was saved to the database. - The file was written to the configured disk (
./uploads/<media-id>/photo.jpg). - The
"thumbnail"conversion was detected (it targets the"images"collection), so Sharp resized the image to 300x300, converted it to WebP at quality 80, and saved it as./uploads/<media-id>/conversions/thumbnail-photo.webp. - A
media.addedevent was emitted (if@nestjs/event-emitteris installed).
- The file was validated against the
Next Steps
- Configuration -- All module options in detail.
- Adding Media -- Every upload method and FileAdder chain option.
- Collections -- Define collection constraints for your entities.
- Image Conversions -- Auto-generate thumbnails and image variants.