├── .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 |
4 |
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 |
--------------------------------------------------------------------------------