npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@onivoro/server-aws-s3

v24.33.9

Published

AWS S3 integration for NestJS applications with file upload/download capabilities.

Readme

@onivoro/server-aws-s3

AWS S3 integration for NestJS applications with file upload/download capabilities.

Installation

npm install @onivoro/server-aws-s3

Overview

This library provides AWS S3 integration for NestJS applications, offering file upload, download, deletion, and pre-signed URL generation.

Module Setup

import { Module } from '@nestjs/common';
import { ServerAwsS3Module } from '@onivoro/server-aws-s3';

@Module({
  imports: [
    ServerAwsS3Module.configure()
  ]
})
export class AppModule {}

Configuration

The module uses environment-based configuration:

export class ServerAwsS3Config {
  AWS_REGION: string;
  AWS_PROFILE?: string;  // Optional AWS profile
  AWS_S3_BUCKET: string;  // Default bucket name
  AWS_S3_PREFIX?: string; // Optional prefix for all keys
}

Service

S3Service

The service provides file operations:

import { Injectable } from '@nestjs/common';
import { S3Service } from '@onivoro/server-aws-s3';

@Injectable()
export class FileStorageService {
  constructor(private readonly s3Service: S3Service) {}

  // Upload a file
  async uploadFile(key: string, body: Buffer | string, contentType: string) {
    const result = await this.s3Service.upload(key, body, contentType);
    return result;
  }

  // Upload a public file
  async uploadPublicFile(key: string, body: Buffer | string, contentType: string) {
    const result = await this.s3Service.uploadPublic(key, body, contentType);
    return result;
  }

  // Download a file
  async downloadFile(key: string) {
    const fileContent = await this.s3Service.download(key);
    return fileContent;
  }

  // Get a pre-signed download URL
  async getDownloadUrl(key: string, expiresIn: number = 3600) {
    const url = await this.s3Service.getPresignedDownloadUrl(key, expiresIn);
    return url;
  }

  // Delete a single file
  async deleteFile(key: string) {
    await this.s3Service.delete(key);
  }

  // Delete files by prefix
  async deleteFilesByPrefix(prefix: string) {
    await this.s3Service.deleteByPrefix(prefix);
  }
}

Available Methods

Upload Operations

  • upload(key: string, body: Buffer | string, contentType: string, bucket?: string) - Upload a private file
  • uploadPublic(key: string, body: Buffer | string, contentType: string, bucket?: string) - Upload a publicly accessible file

Download Operations

  • download(key: string, bucket?: string) - Download file content
  • getPresignedDownloadUrl(key: string, expiresInSeconds?: number, bucket?: string) - Generate pre-signed download URL

Delete Operations

  • delete(key: string, bucket?: string) - Delete a single object
  • deleteByPrefix(prefix: string, bucket?: string) - Delete all objects with a specific prefix

Direct Client Access

The service exposes the underlying S3 client for advanced operations:

import { 
  ListObjectsV2Command,
  CopyObjectCommand,
  HeadObjectCommand,
  GetObjectTaggingCommand
} from '@aws-sdk/client-s3';

@Injectable()
export class AdvancedS3Service {
  constructor(private readonly s3Service: S3Service) {}

  // List objects in bucket
  async listObjects(prefix?: string) {
    const command = new ListObjectsV2Command({
      Bucket: process.env.AWS_S3_BUCKET,
      Prefix: prefix
    });
    
    return await this.s3Service.s3Client.send(command);
  }

  // Copy object
  async copyObject(sourceKey: string, destinationKey: string) {
    const command = new CopyObjectCommand({
      Bucket: process.env.AWS_S3_BUCKET,
      CopySource: `${process.env.AWS_S3_BUCKET}/${sourceKey}`,
      Key: destinationKey
    });
    
    return await this.s3Service.s3Client.send(command);
  }

  // Get object metadata
  async getObjectMetadata(key: string) {
    const command = new HeadObjectCommand({
      Bucket: process.env.AWS_S3_BUCKET,
      Key: key
    });
    
    return await this.s3Service.s3Client.send(command);
  }
}

Complete Example

import { Module, Injectable, Controller, Post, Get, Delete, Param, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ServerAwsS3Module, S3Service } from '@onivoro/server-aws-s3';

@Module({
  imports: [ServerAwsS3Module.configure()],
  controllers: [DocumentController],
  providers: [DocumentService]
})
export class DocumentModule {}

@Injectable()
export class DocumentService {
  constructor(private readonly s3Service: S3Service) {}

  async uploadDocument(userId: string, file: Express.Multer.File) {
    const key = `documents/${userId}/${Date.now()}-${file.originalname}`;
    
    try {
      // Upload to S3
      const result = await this.s3Service.upload(
        key,
        file.buffer,
        file.mimetype
      );

      return {
        key,
        location: result.Location,
        etag: result.ETag
      };
    } catch (error) {
      console.error('Upload failed:', error);
      throw error;
    }
  }

  async getDocumentUrl(key: string) {
    // Generate URL valid for 1 hour
    const url = await this.s3Service.getPresignedDownloadUrl(key, 3600);
    return { url };
  }

  async deleteUserDocuments(userId: string) {
    const prefix = `documents/${userId}/`;
    await this.s3Service.deleteByPrefix(prefix);
  }

  async uploadPublicAvatar(userId: string, imageBuffer: Buffer) {
    const key = `avatars/${userId}.jpg`;
    
    const result = await this.s3Service.uploadPublic(
      key,
      imageBuffer,
      'image/jpeg'
    );

    return {
      key,
      publicUrl: result.Location
    };
  }
}

@Controller('documents')
export class DocumentController {
  constructor(private readonly documentService: DocumentService) {}

  @Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  async uploadDocument(
    @UploadedFile() file: Express.Multer.File
  ) {
    const userId = 'user123'; // Get from auth context
    return await this.documentService.uploadDocument(userId, file);
  }

  @Get(':key/url')
  async getDocumentUrl(@Param('key') key: string) {
    return await this.documentService.getDocumentUrl(key);
  }

  @Delete('user/:userId')
  async deleteUserDocuments(@Param('userId') userId: string) {
    await this.documentService.deleteUserDocuments(userId);
    return { message: 'Documents deleted successfully' };
  }
}

Environment Variables

# Required
AWS_REGION=us-east-1
AWS_S3_BUCKET=my-bucket-name

# Optional
AWS_PROFILE=my-profile
AWS_S3_PREFIX=my-app/  # Prefix for all keys

Error Handling

try {
  await s3Service.download('non-existent-key');
} catch (error) {
  if (error.name === 'NoSuchKey') {
    console.error('File not found');
  } else if (error.name === 'AccessDenied') {
    console.error('Permission denied');
  }
}

Limitations

  • No multipart upload support (large files must be handled manually)
  • No built-in bucket management operations
  • No object tagging or versioning support
  • Limited to basic CRUD operations
  • For advanced features, use the exposed s3Client directly

Best Practices

  1. Key Naming: Use a consistent key naming strategy (e.g., type/userId/timestamp-filename)
  2. Content Types: Always specify correct content types for proper browser handling
  3. Security: Use pre-signed URLs for temporary access instead of public uploads when possible
  4. Cleanup: Implement lifecycle policies for automatic object expiration
  5. Error Handling: Always handle S3 errors appropriately

License

MIT