@vsaas/loopback-component-storage
v11.0.2
Published
Fork of LoopBack component for file storage and management using AWS S3 or compatible services
Maintainers
Readme
LoopBack Component Storage
Modernized fork of loopback-component-storage for LoopBack 3 projects that
only need:
filesystems3/aws/amazon
This fork removes pkgcloud, formidable, strong-globalize, and the legacy
provider matrix. The package is now implemented in TypeScript and keeps the
LoopBack 3 connector surface.
Stack
- TypeScript
6.0.2 @fastify/busboyfor streaming multipart parsing- AWS SDK for JavaScript v3 for S3
tsdownfor buildsvitestfor testsoxlintfor linting
Behavior
- Uploads are streamed end to end.
- When
maxFileSizeis exceeded, the request is aborted immediately. - Filesystem uploads do not buffer the full body in memory.
- S3 uploads use AWS SDK v3 multipart upload support.
- Signed URLs validate object existence before signing by default. Set
signedUrl.validateBeforeSigntofalseto skip the extraHeadObjectrequest. - Filesystem signed URLs are local application URLs signed with HMAC. They
require
signedUrl.secret.
Supported upload options
getFilenamenameConflict: "makeUnique"allowedContentTypesmaxFileSizemaxFieldsSizeaclStorageClassCacheControlServerSideEncryptionSSEKMSKeyIdSSECustomerAlgorithmSSECustomerKeySSECustomerKeyMD5
S3 client tuning
For S3 datasources you can also tune the AWS SDK v3 client:
maxAttemptsretryModeconnectionTimeoutrequestTimeoutsocketTimeoutthrowOnRequestTimeoutmaxSockets
Example:
{
"name": "storage",
"connector": "@vsaas/loopback-component-storage",
"provider": "s3",
"region": "us-east-1",
"maxAttempts": 3,
"retryMode": "standard",
"connectionTimeout": 2000,
"requestTimeout": 30000,
"socketTimeout": 30000,
"throwOnRequestTimeout": true,
"maxSockets": 50
}Datasource examples
Filesystem:
{
"name": "storage",
"connector": "@vsaas/loopback-component-storage",
"provider": "filesystem",
"root": "./storage",
"maxFileSize": 10485760,
"maxFieldsSize": 1048576,
"signedUrl": {
"enabled": true,
"expiresIn": 900,
"secret": "replace-this-with-a-long-random-secret",
"baseUrl": "https://api.example.com"
}
}AWS S3:
{
"name": "storage",
"connector": "@vsaas/loopback-component-storage",
"provider": "s3",
"region": "us-east-1",
"accessKeyId": "YOUR_ACCESS_KEY",
"secretAccessKey": "YOUR_SECRET_KEY",
"CacheControl": "max-age=300",
"signedUrl": {
"enabled": true,
"expiresIn": 900,
"strategy": "provider",
"validateBeforeSign": true
},
"maxAttempts": 3,
"retryMode": "standard",
"connectionTimeout": 2000,
"requestTimeout": 30000,
"socketTimeout": 30000,
"throwOnRequestTimeout": true,
"maxSockets": 50
}MinIO or local S3-compatible storage:
{
"name": "storage",
"connector": "@vsaas/loopback-component-storage",
"provider": "amazon",
"region": "us-east-1",
"endpoint": "http://localhost:9000",
"forcePathStyle": true,
"keyId": "MINIO_ACCESS_KEY",
"key": "MINIO_SECRET_KEY",
"CacheControl": "max-age=300",
"signedUrl": {
"enabled": true,
"expiresIn": 1800,
"strategy": "provider",
"validateBeforeSign": false
},
"requestTimeout": 20000
}Dedicated datasource with signed URLs enabled:
{
"AssetStorage": {
"name": "AssetStorage",
"connector": "@vsaas/loopback-component-storage",
"provider": "amazon",
"region": "us-west-1",
"endpoint": "http://localhost:9000",
"forcePathStyle": true,
"keyId": "${S3_ACCESS_KEY_ID_WEST_1}",
"key": "${S3_SECRET_ACCESS_KEY_WEST_1}",
"CacheControl": "max-age=300",
"signedUrl": {
"enabled": true,
"expiresIn": 1800,
"strategy": "provider",
"validateBeforeSign": true
},
"maxAttempts": 2,
"requestTimeout": 20000
}
}signedUrl is only used when the download request includes ?signedUrl=true
or ?signed-url=true.
For S3-compatible providers:
signedUrl.strategy: "provider"keeps the current behavior and returns a presigned URL from S3 or the compatible backendsignedUrl.strategy: "local"returns a signed URL from your own app instead of exposing the backend URL; that URL hits the component and streams the file through your server- when
signedUrl.strategyis"local",signedUrl.secretis required because the component signs and validates the local URL itself - you can override the datasource strategy per request via
signedUrlStrategy=local|providerorsigned-url-strategy=local|provider - when calling
getSignedUrl()ordownload()from code, you can pass an options object like{ strategy: "local" }; that explicit option has higher priority than both the query string and the datasource configuration
For filesystem providers:
signedUrl.secretis required to generate and validate local signed URLssignedUrl.baseUrlis optional and lets you force the public origin used in generated URLs when the app is behind a proxy- signed filesystem URLs now prefer
/:container/signed-download?file=...withexpiresandsignaturequery params /:container/download/:file(*)and/:container/signed-download/:file(*)are still supported for compatibility- the current request query string is preserved except for signing-related and
sensitive params such as
signedUrl,signed-url,signature,expires, and anything that looks like tokens, secrets, passwords, authorization, API keys, or AWS presign parameters
This allows you to keep /:container/download protected and expose only
/:container/signed-download as a public route that validates the signature
before streaming the file. The target file can be provided as a query string
like ?file=projects/project-1/event-summaries/2026-03-29.json.gz.
Example S3 datasource that always returns app-local signed URLs:
{
"name": "storage",
"connector": "@vsaas/loopback-component-storage",
"provider": "amazon",
"region": "us-east-1",
"endpoint": "http://localhost:9000",
"forcePathStyle": true,
"keyId": "MINIO_ACCESS_KEY",
"key": "MINIO_SECRET_KEY",
"signedUrl": {
"enabled": true,
"expiresIn": 1800,
"strategy": "local",
"secret": "replace-this-with-a-long-random-secret",
"baseUrl": "https://api.example.com",
"validateBeforeSign": true
}
}Migration notes from legacy pkgcloud-style configs
forcePathBucket->forcePathStylemaxRetries->maxAttemptsdefaultUploadParams.CacheControl->CacheControlsignatureVersionis no longer usedprotocolis no longer used; include the protocol directly inendpointhttpOptions.timeout->requestTimeouthttpOptions.agent.keepAliveis not needed because AWS SDK v3 already reuses connections by default
When using custom S3-compatible endpoints, endpoint should usually point to
the service root, for example http://localhost:9000, not to a bucket URL such
as http://localhost:9000/my-bucket.
Development
npm run lint
npm run typecheck
npm run build
npm test