@zola_do/seaweed
v0.2.9
Published
AWS S3-compatible storage for NestJS
Maintainers
Readme
@zola_do/seaweed
AWS S3-compatible object storage for NestJS (SeaweedFS, MinIO, or AWS S3).
Overview
@zola_do/seaweed provides a unified interface for S3-compatible storage:
- Upload — Files, buffers, and streams
- Download — Streaming and buffered downloads
- Presigned URLs — Secure upload/download links
- Abstract S3 Class — Implement custom providers
- TypeScript Types — Full type safety
Installation
# Install individually
npm install @zola_do/seaweed
# Or via meta package
npm install @zola_do/nestjs-sharedDependencies
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presignerQuick Start
1. Configure Environment
# .env
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_ENDPOINT=https://your-s3-endpoint.com
AWS_REGION=us-east-12. Register Module
import { Module } from '@nestjs/common';
import { StorageModule } from '@zola_do/seaweed';
@Module({
imports: [StorageModule],
})
export class AppModule {}3. Use Storage Service
import { Injectable } from '@nestjs/common';
import { StorageService } from '@zola_do/seaweed';
@Injectable()
export class FileService {
constructor(private readonly storageService: StorageService) {}
async uploadFile(file: Express.Multer.File, bucketName: string) {
return await this.storageService.upload(file, bucketName);
}
async downloadFile(fileInfo: FileInfo, response: Response) {
await this.storageService.download(fileInfo, response);
}
}Storage Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Storage Flow │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ Client │ │
│ └────┬─────┘ │
│ │ │
│ │ 1. Request presigned URL │
│ ├─────────────────────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Controller │ │ Storage │ │
│ │ GET /upload │ │ Service │ │
│ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │
│ │ 2. Generate presigned URL │ │
│ ├─────────────────────────────────────┤ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Response │ │ AWS SDK │ │
│ │ { url } │ │ S3Client │ │
│ └───────────────┘ └───────┬───────┘ │
│ │ │
│ │ │
│ ┌─────────────────────────────────────┤ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Direct PUT │───────────────────► │ S3 Bucket │ │
│ │ to S3 │ 3. Upload file │ (any S3) │ │
│ └───────────────┘ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘Upload Operations
File Upload
async uploadFile(file: Express.Multer.File, bucketName: string) {
const result = await this.storageService.upload(file, bucketName);
return result;
// Returns: { filepath, bucketName, contentType, originalname }
}Buffer Upload
async uploadBuffer(
buffer: Buffer,
originalname: string,
mimetype: string,
bucketName: string,
) {
const result = await this.storageService.uploadBuffer(
buffer,
originalname,
mimetype,
bucketName,
);
return result;
}Stream Upload
async uploadStream(
stream: ReadableStream,
filename: string,
mimetype: string,
bucketName: string,
) {
return await this.storageService.uploadStream(
stream,
filename,
mimetype,
bucketName,
);
}Download Operations
Download to Response
async downloadToResponse(fileInfo: FileInfo, response: Response) {
response.setHeader('Content-Type', fileInfo.contentType);
response.setHeader('Content-Disposition', `attachment; filename="${fileInfo.originalname}"`);
await this.storageService.download(fileInfo, response);
}Download to Buffer
async downloadToBuffer(fileInfo: FileInfo): Promise<Buffer> {
return await this.storageService.downloadBuffer(fileInfo);
}Presigned URLs
Generate Upload URL
async getUploadUrl(fileInfo: FileInfo): Promise<string> {
const presignedUrl = await this.storageService.generatePresignedUploadUrl(
fileInfo,
3600, // URL expires in 1 hour
);
return presignedUrl;
}Generate Download URL
async getDownloadUrl(fileInfo: FileInfo): Promise<string> {
const downloadUrl = await this.storageService.generatePresignedDownloadUrl(
fileInfo,
3600, // URL expires in 1 hour
);
return downloadUrl;
}Usage with Client
// Server generates URL
const { presignedUrl, file } =
await this.storageService.generatePresignedUploadUrl({
originalname: 'document.pdf',
contentType: 'application/pdf',
});
// Client uploads directly
fetch(presignedUrl, {
method: 'PUT',
body: fileBuffer,
headers: { 'Content-Type': 'application/pdf' },
});FileInfo Type
interface FileInfo {
filepath: string; // Key/path in bucket
bucketName: string; // Target bucket
contentType: string; // MIME type
originalname: string; // Original filename
}Custom S3 Provider
Implement a custom storage provider:
import { S3, FileInfo } from '@zola_do/seaweed';
class CustomS3Provider extends S3 {
constructor() {
super({
region: 'custom-region',
credentials: {
accessKeyId: process.env.CUSTOM_KEY!,
secretAccessKey: process.env.CUSTOM_SECRET!,
},
endpoint: process.env.CUSTOM_ENDPOINT,
});
}
async upload(
file: Express.Multer.File,
bucketName: string,
): Promise<FileInfo> {
// Custom upload logic
const filepath = `${Date.now()}-${file.originalname}`;
await this.client.putObject({
Bucket: bucketName,
Key: filepath,
Body: file.buffer,
ContentType: file.mimetype,
});
return {
filepath,
bucketName,
contentType: file.mimetype,
originalname: file.originalname,
};
}
}Environment Variables
| Variable | Description | Required |
| -------------------------- | ------------------------------- | ------------------------- |
| AWS_ACCESS_KEY_ID | S3/MinIO access key | Yes |
| AWS_SECRET_ACCESS_KEY | S3/MinIO secret key | Yes |
| AWS_ENDPOINT | S3-compatible endpoint URL | Yes |
| AWS_REGION | AWS region | No (default: us-east-1) |
| PRESIGNED_URL_EXPIRATION | Presigned URL expiry in seconds | No (default: 3600) |
Supported Providers
| Provider | Endpoint Example |
| ------------------- | ------------------------------------- |
| AWS S3 | https://s3.amazonaws.com |
| MinIO | http://localhost:9000 |
| SeaweedFS | http://localhost:8888 |
| DigitalOcean Spaces | https://nyc3.digitaloceanspaces.com |
| Wasabi | https://s3.wasabisys.com |
API Reference
Module
StorageModule.forRoot(options?: StorageModuleOptions)
StorageModule.forRootAsync(options?: StorageModuleAsyncOptions)Service
class StorageService {
upload(file: Express.Multer.File, bucketName: string): Promise<FileInfo>;
uploadBuffer(
buffer: Buffer,
filename: string,
mimetype: string,
bucketName: string,
): Promise<FileInfo>;
uploadStream(
stream: Readable,
filename: string,
mimetype: string,
bucketName: string,
): Promise<FileInfo>;
download(fileInfo: FileInfo, response: Response): Promise<void>;
downloadBuffer(fileInfo: FileInfo): Promise<Buffer>;
generatePresignedUploadUrl(
fileInfo: Partial<FileInfo>,
expiresIn?: number,
): Promise<string>;
generatePresignedDownloadUrl(
fileInfo: FileInfo,
expiresIn?: number,
): Promise<string>;
}Types
interface FileInfo {
filepath: string;
bucketName: string;
contentType: string;
originalname: string;
}
class S3 {
constructor(config: S3ClientConfig);
// Override methods for custom implementation
}Exceptions
class FileNotFoundException extends NotFoundException {
constructor(message?: string);
}Recommended Imports
Subpath imports are recommended for tree-shaking:
import { StorageModule } from '@zola_do/seaweed/module';
import { StorageService, AwsS3 } from '@zola_do/seaweed/services';
import type { FileInfo } from '@zola_do/seaweed/types';
import { FileNotFoundException } from '@zola_do/seaweed/exceptions';Root import is supported for backward compatibility:
import { StorageModule, StorageService, FileInfo } from '@zola_do/seaweed';Troubleshooting
Q: Upload fails with CORS error?
Configure CORS on your S3 bucket:
{
"CORSRules": [
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
"AllowedOrigins": ["*"]
}
]
}Q: Presigned URL returns 403?
Check:
- IAM permissions for the bucket
- Correct access key and secret
- URL hasn't expired
Q: Connection timeout?
Verify AWS_ENDPOINT is correct and accessible from your server.
Related Packages
- @zola_do/minio — Alternative MinIO-specific module
License
ISC
