├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── index.d.ts ├── index.js ├── index.ts ├── package-lock.json ├── package.json ├── src ├── index.ts ├── s3.constants.ts ├── s3.core-module.ts ├── s3.decorators.ts ├── s3.interfaces.ts ├── s3.module.ts └── s3.utils.ts ├── test ├── index.spec.ts └── jest.config.json ├── tsconfig.build.json └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | index.js 2 | index.ts 3 | index.d.ts -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:prettier/recommended', 11 | ], 12 | ignorePatterns: ['.eslintrc.js'], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-function-return-type': 'off', 21 | '@typescript-eslint/no-explicit-any': 'off', 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NestJS S3 2 | 3 | NPM Version 4 | Package License 5 | 6 | ## Table of Contents 7 | 8 | - [Description](#description) 9 | - [Installation](#installation) 10 | - [Examples](#examples) 11 | - [License](#license) 12 | 13 | ## Description 14 | Integrates S3 with Nest 15 | 16 | ## Installation 17 | 18 | ```bash 19 | npm install nestjs-s3 @aws-sdk/client-s3 20 | ``` 21 | 22 | You can also use the interactive CLI 23 | 24 | ```sh 25 | npx nestjs-modules 26 | ``` 27 | 28 | ## Examples 29 | 30 | ```bash 31 | docker run \ 32 | -p 9000:9000 \ 33 | -e MINIO_ACCESS_KEY=minio \ 34 | -e MINIO_SECRET_KEY=password \ 35 | minio/minio server /data 36 | ``` 37 | 38 | ### S3Module.forRoot(options, connection?) 39 | 40 | ```ts 41 | import { Module } from '@nestjs/common'; 42 | import { S3Module } from 'nestjs-s3'; 43 | import { AppController } from './app.controller'; 44 | 45 | @Module({ 46 | imports: [ 47 | S3Module.forRoot({ 48 | config: { 49 | credentials: { 50 | accessKeyId: 'minio', 51 | secretAccessKey: 'password', 52 | }, 53 | // region: 'us-east-1', 54 | endpoint: 'http://127.0.0.1:9000', 55 | forcePathStyle: true, 56 | signatureVersion: 'v4', 57 | }, 58 | }), 59 | ], 60 | controllers: [AppController], 61 | }) 62 | export class AppModule {} 63 | ``` 64 | 65 | ### S3Module.forRootAsync(options, connection?) 66 | 67 | ```ts 68 | import { Module } from '@nestjs/common'; 69 | import { S3Module } from 'nestjs-s3'; 70 | import { AppController } from './app.controller'; 71 | 72 | @Module({ 73 | imports: [ 74 | S3Module.forRootAsync({ 75 | useFactory: () => ({ 76 | config: { 77 | credentials: { 78 | accessKeyId: 'minio', 79 | secretAccessKey: 'password', 80 | }, 81 | // region: 'us-east-1', 82 | endpoint: 'http://localhost:9000', 83 | forcePathStyle: true, 84 | signatureVersion: 'v4', 85 | }, 86 | }), 87 | }), 88 | ], 89 | controllers: [AppController], 90 | }) 91 | export class AppModule {} 92 | ``` 93 | 94 | ### InjectS3(connection?) 95 | 96 | ```ts 97 | import { Controller, Get, } from '@nestjs/common'; 98 | import { InjectS3, S3 } from 'nestjs-s3'; 99 | 100 | @Controller() 101 | export class AppController { 102 | constructor( 103 | @InjectS3() private readonly s3: S3, 104 | ) {} 105 | 106 | @Get() 107 | async listBuckets() { 108 | try { 109 | await this.s3.createBucket({ Bucket: 'bucket' }); 110 | } catch (e) {} 111 | 112 | try { 113 | // this.s3.send(new ListBucketsCommand({})) 114 | const list = await this.s3.listBuckets({}); 115 | return list.Buckets; 116 | } catch (e) { 117 | console.log(e); 118 | } 119 | } 120 | } 121 | ``` 122 | 123 | ## License 124 | 125 | MIT 126 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist'; 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function __export(m) { 3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 4 | } 5 | exports.__esModule = true; 6 | __export(require("./dist")); 7 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './dist'; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-s3", 3 | "version": "2.0.1", 4 | "description": "Nest - modern, fast, powerful node.js web framework (@s3)", 5 | "author": "Sviatoslav H", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/svtslv/nestjs-s3" 10 | }, 11 | "keywords": [ 12 | "nest", 13 | "nestjs", 14 | "s3", 15 | "aws", 16 | "storage" 17 | ], 18 | "files": [ 19 | "dist", 20 | "index.js", 21 | "index.d.ts" 22 | ], 23 | "main": "dist/index.js", 24 | "scripts": { 25 | "fix": "rm -rf node_modules && rm package-lock.json && npm install", 26 | "build": "rm -rf dist && tsc -p tsconfig.build.json", 27 | "format": "prettier --write \"**/*.ts\"", 28 | "lint": "eslint 'lib/**/*.ts' --fix", 29 | "test": "jest --config ./test/jest.config.json --runInBand", 30 | "prepublish:npm": "npm run build", 31 | "publish:npm": "npm publish --access public", 32 | "prepublish:next": "npm run build", 33 | "publish:next": "npm publish --access public --tag next", 34 | "prerelease": "npm run build", 35 | "release": "release-it" 36 | }, 37 | "peerDependencies": { 38 | "@aws-sdk/client-s3": ">=3.328.0", 39 | "@nestjs/common": ">=6.7.0", 40 | "@nestjs/core": ">=6.7.0" 41 | }, 42 | "devDependencies": { 43 | "@aws-sdk/client-s3": "^3.328.0", 44 | "@nestjs/common": "^9.0.0", 45 | "@nestjs/core": "^9.0.0", 46 | "@nestjs/testing": "^9.0.0", 47 | "@types/jest": "^29.5.1", 48 | "@types/node": "^16.0.0", 49 | "@types/supertest": "^2.0.8", 50 | "@typescript-eslint/eslint-plugin": "^5.59.2", 51 | "@typescript-eslint/parser": "^5.59.2", 52 | "eslint": "^8.40.0", 53 | "eslint-config-prettier": "^8.8.0", 54 | "eslint-plugin-prettier": "^4.2.1", 55 | "jest": "^29.5.0", 56 | "minio": "^7.0.15", 57 | "prettier": "^2.8.8", 58 | "reflect-metadata": "^0.1.13", 59 | "supertest": "^6.3.3", 60 | "ts-jest": "^29.1.0", 61 | "ts-loader": "^9.4.2", 62 | "ts-node": "^10.9.1", 63 | "tsconfig-paths": "^4.2.0", 64 | "typescript": "^4.9.5" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './s3.module'; 2 | export * from './s3.decorators'; 3 | export * from './s3.interfaces'; 4 | export * from './s3.utils'; 5 | -------------------------------------------------------------------------------- /src/s3.constants.ts: -------------------------------------------------------------------------------- 1 | export const S3_MODULE_CONNECTION = 'default'; 2 | export const S3_MODULE_CONNECTION_TOKEN = 'S3ModuleConnectionToken'; 3 | export const S3_MODULE_OPTIONS_TOKEN = 'S3ModuleOptionsToken'; 4 | -------------------------------------------------------------------------------- /src/s3.core-module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module, DynamicModule, Provider } from '@nestjs/common'; 2 | import { 3 | S3ModuleAsyncOptions, 4 | S3ModuleOptions, 5 | S3ModuleOptionsFactory, 6 | } from './s3.interfaces'; 7 | import { 8 | createS3Connection, 9 | getS3OptionsToken, 10 | getS3ConnectionToken, 11 | } from './s3.utils'; 12 | 13 | @Global() 14 | @Module({}) 15 | export class S3CoreModule { 16 | /* forRoot */ 17 | static forRoot(options: S3ModuleOptions, connection?: string): DynamicModule { 18 | const s3OptionsProvider: Provider = { 19 | provide: getS3OptionsToken(connection), 20 | useValue: options, 21 | }; 22 | 23 | const s3ConnectionProvider: Provider = { 24 | provide: getS3ConnectionToken(connection), 25 | useValue: createS3Connection(options), 26 | }; 27 | 28 | return { 29 | module: S3CoreModule, 30 | providers: [s3OptionsProvider, s3ConnectionProvider], 31 | exports: [s3OptionsProvider, s3ConnectionProvider], 32 | }; 33 | } 34 | 35 | /* forRootAsync */ 36 | public static forRootAsync( 37 | options: S3ModuleAsyncOptions, 38 | connection: string, 39 | ): DynamicModule { 40 | const s3ConnectionProvider: Provider = { 41 | provide: getS3ConnectionToken(connection), 42 | useFactory(options: S3ModuleOptions) { 43 | return createS3Connection(options); 44 | }, 45 | inject: [getS3OptionsToken(connection)], 46 | }; 47 | 48 | return { 49 | module: S3CoreModule, 50 | imports: options.imports, 51 | providers: [ 52 | ...this.createAsyncProviders(options, connection), 53 | s3ConnectionProvider, 54 | ], 55 | exports: [s3ConnectionProvider], 56 | }; 57 | } 58 | 59 | /* createAsyncProviders */ 60 | public static createAsyncProviders( 61 | options: S3ModuleAsyncOptions, 62 | connection?: string, 63 | ): Provider[] { 64 | if (!(options.useExisting || options.useFactory || options.useClass)) { 65 | throw new Error( 66 | 'Invalid configuration. Must provide useFactory, useClass or useExisting', 67 | ); 68 | } 69 | 70 | if (options.useExisting || options.useFactory) { 71 | return [this.createAsyncOptionsProvider(options, connection)]; 72 | } 73 | 74 | return [ 75 | this.createAsyncOptionsProvider(options, connection), 76 | { provide: options.useClass, useClass: options.useClass }, 77 | ]; 78 | } 79 | 80 | /* createAsyncOptionsProvider */ 81 | public static createAsyncOptionsProvider( 82 | options: S3ModuleAsyncOptions, 83 | connection?: string, 84 | ): Provider { 85 | if (!(options.useExisting || options.useFactory || options.useClass)) { 86 | throw new Error( 87 | 'Invalid configuration. Must provide useFactory, useClass or useExisting', 88 | ); 89 | } 90 | 91 | if (options.useFactory) { 92 | return { 93 | provide: getS3OptionsToken(connection), 94 | useFactory: options.useFactory, 95 | inject: options.inject || [], 96 | }; 97 | } 98 | 99 | return { 100 | provide: getS3OptionsToken(connection), 101 | async useFactory( 102 | optionsFactory: S3ModuleOptionsFactory, 103 | ): Promise { 104 | return optionsFactory.createS3ModuleOptions(); 105 | }, 106 | inject: [options.useClass || options.useExisting], 107 | }; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/s3.decorators.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from '@nestjs/common'; 2 | import { getS3ConnectionToken } from './s3.utils'; 3 | 4 | export const InjectS3 = (connection?) => { 5 | return Inject(getS3ConnectionToken(connection)); 6 | }; 7 | -------------------------------------------------------------------------------- /src/s3.interfaces.ts: -------------------------------------------------------------------------------- 1 | import * as ClientS3 from '@aws-sdk/client-s3'; 2 | import { ModuleMetadata, Type } from '@nestjs/common/interfaces'; 3 | 4 | export type S3 = ClientS3.S3; 5 | 6 | export interface S3ModuleOptions { 7 | config: ClientS3.S3ClientConfig; 8 | } 9 | 10 | export interface S3ModuleOptionsFactory { 11 | createS3ModuleOptions(): Promise | S3ModuleOptions; 12 | } 13 | 14 | export interface S3ModuleAsyncOptions extends Pick { 15 | inject?: any[]; 16 | useClass?: Type; 17 | useExisting?: Type; 18 | useFactory?: (...args: any[]) => Promise | S3ModuleOptions; 19 | } 20 | -------------------------------------------------------------------------------- /src/s3.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module } from '@nestjs/common'; 2 | import { S3CoreModule } from './s3.core-module'; 3 | import { S3ModuleAsyncOptions, S3ModuleOptions } from './s3.interfaces'; 4 | 5 | @Module({}) 6 | export class S3Module { 7 | public static forRoot( 8 | options: S3ModuleOptions, 9 | connection?: string, 10 | ): DynamicModule { 11 | return { 12 | module: S3Module, 13 | imports: [S3CoreModule.forRoot(options, connection)], 14 | exports: [S3CoreModule], 15 | }; 16 | } 17 | 18 | public static forRootAsync( 19 | options: S3ModuleAsyncOptions, 20 | connection?: string, 21 | ): DynamicModule { 22 | return { 23 | module: S3Module, 24 | imports: [S3CoreModule.forRootAsync(options, connection)], 25 | exports: [S3CoreModule], 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/s3.utils.ts: -------------------------------------------------------------------------------- 1 | import { S3 } from '@aws-sdk/client-s3'; 2 | import { S3ModuleOptions } from './s3.interfaces'; 3 | import { 4 | S3_MODULE_CONNECTION, 5 | S3_MODULE_CONNECTION_TOKEN, 6 | S3_MODULE_OPTIONS_TOKEN, 7 | } from './s3.constants'; 8 | 9 | export function getS3OptionsToken(connection: string): string { 10 | return `${connection || S3_MODULE_CONNECTION}_${S3_MODULE_OPTIONS_TOKEN}`; 11 | } 12 | 13 | export function getS3ConnectionToken(connection: string): string { 14 | return `${connection || S3_MODULE_CONNECTION}_${S3_MODULE_CONNECTION_TOKEN}`; 15 | } 16 | 17 | export function createS3Connection(options: S3ModuleOptions): S3 { 18 | const { config } = options; 19 | return new S3(config); 20 | } 21 | -------------------------------------------------------------------------------- /test/index.spec.ts: -------------------------------------------------------------------------------- 1 | import * as index from '../src/index'; 2 | 3 | describe('Index', () => { 4 | test('should return 5 exports', () => { 5 | expect(Object.keys(index)).toHaveLength(5); 6 | }); 7 | }); -------------------------------------------------------------------------------- /test/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es2017", 9 | "sourceMap": false, 10 | "outDir": "./dist", 11 | "baseUrl": "./", 12 | "skipLibCheck": true 13 | }, 14 | "include": ["src/**/*", "test/**/*"], 15 | "exclude": ["node_modules", "dist"] 16 | } 17 | --------------------------------------------------------------------------------