@chadborghini/adonisjs-mediable
v2.0.1
Published
A lightweight, flexible media attachment system for AdonisJS 7+. Inspired by Laravel Media Library, rewritten for AdonisJS with TypeScript.
Downloads
35
Readme
@chadborghini/adonisjs-mediable
A lightweight, flexible media attachment system for AdonisJS 7+.
Inspired by Laravel Media Library, rewritten for AdonisJS with TypeScript.
hasMedia()model mixin- Media uploading & storage
- Automatic file naming
- Media tagging
- Query helpers (
media(),getMedia(),firstMedia(), etc.) - Full TypeScript support
- Works with any Drive disk (local, s3, etc.)
Installation
1. Install
npm install @chadborghini/adonisjs-mediable2. Configure
node ace configure @chadborghini/adonisjs-mediableThis registers the service provider and publishes the migration files.
3. Run migrations
node ace migration:runSetup
Prepare your model
Decorate your model with @MediableMorphMap and mix in hasMedia():
import { compose } from '@adonisjs/core/helpers'
import { BaseModel } from '@adonisjs/lucid/orm'
import { hasMedia, MediableMorphMap } from '@chadborghini/adonisjs-mediable'
import type { MediableModelInterface } from '@chadborghini/adonisjs-mediable/types'
@MediableMorphMap('products')
export default class Product extends compose(BaseModel, hasMedia()) implements MediableModelInterface {
getModelId() {
return this.id
}
}The string passed to
@MediableMorphMapis used as the morph type identifier. It should be unique per model and stable — the table name is a good choice.
Uploading Media
Use MediaUploader to upload a file and get back a Media record to attach to a model.
From a multipart file (form upload)
import { MediaUploader } from '@chadborghini/adonisjs-mediable'
const file = request.file('image')
const media = await MediaUploader
.fromSource(file)
.toDestination('fs', 'products')
.upload()
await product.attachMedia(media)From a Buffer or URL string
const media = await MediaUploader
.fromSource(buffer)
.toDestination('s3', 'avatars')
.upload()Custom filename
const media = await MediaUploader
.fromSource(file)
.toDestination('fs', 'products')
.useFilename('hero-image')
.upload()Separate disk and directory
const media = await MediaUploader
.fromSource(file)
.toDisk('s3')
.toDirectory('products/images')
.upload()From an existing Media record
const uploader = await MediaUploader.fromExistingMedia(existingMedia)
const copy = await uploader
.toDestination('fs', 'archive')
.upload()Attaching & Detaching
Attach media
// Without a tag (uses "default")
await product.attachMedia(media)
// With a tag
await product.attachMedia(media, 'thumbnail')
// Multiple at once
await product.attachMedia([media1, media2], 'gallery')
// By media ID
await product.attachMedia(42, 'thumbnail')Detach media
// Detach a specific record
await product.detachMedia(media)
// Detach by ID
await product.detachMedia(42)
// Detach all media from the model
await product.detachMedia()Sync media
Replaces all attached media for a tag with the new set — useful for replacing a single image or reordering a gallery:
await product.syncMedia(media, 'thumbnail')
await product.syncMedia([media1, media2, media3], 'gallery')Clone media to another model
Copies a media file to a new directory and returns a new Media record:
const cloned = await sourceProduct.cloneMedia(media, {
directory: 'products/456',
tag: 'thumbnail', // optional, defaults to "default"
disk: 's3', // optional, defaults to the original media's disk
})Retrieving Media
Query builder
// All tags
const query = product.media()
// Specific tag
const query = product.media('gallery')Since media() returns a Lucid query builder you can chain onto it:
const recent = await product.media('gallery')
.orderBy('created_at', 'desc')
.limit(5)Get all media as an array
const all = await product.getMedia()
const thumbnails = await product.getMedia('thumbnail')Get the first media record
const thumbnail = await product.firstMedia('thumbnail')
// returns Media | nullCheck if media is attached
const isAttached = await product.hasMedia(media)
const isAttached = await product.hasMedia(42)
// returns booleanThe Media Model
Each uploaded file creates a Media row with the following fields:
| Field | Type | Description |
|-----------------|----------|---------------------------------------|
| id | number | Primary key |
| disk | string | Drive disk name (e.g. fs, s3) |
| directory | string | Storage directory |
| filename | string | Stored filename (auto-generated) |
| extension | string | File extension |
| mimeType | string | MIME type |
| aggregateType | string | Broad type (image, video, etc.) |
| size | number | File size in bytes |
| createdAt | DateTime | |
| updatedAt | DateTime | |
TypeScript
Types are available from the /types subpath:
import type { MediableModelInterface, MediaInterface, MediableInterface } from '@chadborghini/adonisjs-mediable/types'Your model must implement MediableModelInterface, which requires one method:
interface MediableModelInterface {
getModelId(): string | number
}License
MIT
