-
Notifications
You must be signed in to change notification settings - Fork 0
6. Advanced Usage
This section covers more advanced ways to use NestJS Secrets
, including creating custom secret providers, understanding all module registration options, strategies for testing your application, and manually controlling the configuration loading process.
While NestJS Secrets
provides built-in support for major cloud providers, you can extend its capabilities by creating your own custom secret providers. This is useful if you need to integrate with an unsupported secret manager (like HashiCorp Vault, a local development vault, or a custom internal solution) or if you need specialized logic for resolving certain types of secret references.
To create a custom provider, you need to implement the SecretProvider
interface:
// custom-secret.provider.ts
import {Injectable} from '@nestjs/common';
import {SecretProvider} from '@floracodex/nestjs-secrets'; // Assuming SecretProvider is exported
@Injectable()
export class MyCustomSecretProvider implements SecretProvider {
/**
* Checks if a given configuration value string is a reference
* that this provider should handle.
* @param value The configuration value to check.
* @returns True if it's a reference for this provider, false otherwise.
*/
isSecretReference(value: string): boolean {
return typeof value === 'string' && value.startsWith('customvault://');
}
/**
* Resolves the secret reference to its actual value.
* @param secretRef The secret reference string (e.g., "customvault://my-secret-path").
* @returns A Promise that resolves to the secret value (string or array of strings).
*/
async resolveSecret(secretRef: string): Promise<string | string[]> {
const secretPath = secretRef.replace('customvault://', '');
// Implement your custom logic to fetch the secret here
// For example, make an API call to your custom vault:
// const response = await fetch(`https://my-custom-vault.com/api/secrets/${secretPath}`);
// const secret = await response.text();
// return secret;
// Placeholder for demonstration:
return `resolved-value-for-${secretPath}-from-custom-provider`;
}
}
Once your custom provider is created, you register an instance of it with SecretsModule
:
// app.module.ts
import {Module} from '@nestjs/common';
import {SecretsModule} from '@floracodex/nestjs-secrets';
import {MyCustomSecretProvider} from './custom-secret.provider'; // Import your custom provider
@Module({
imports: [
SecretsModule.forRoot({
files: ['settings.yaml'],
isGlobal: true,
provider: new MyCustomSecretProvider() // Pass an instance of your custom provider
// When providing a custom provider instance, the `client` option is typically not used here,
// as the custom provider itself should handle its own client or connection logic.
})
]
})
export class AppModule {
}
In your settings.yaml
, you could then have:
my_api:
key: 'customvault://path/to/my/apikey'
NestJS Secrets
will use your MyCustomSecretProvider to resolve this reference.
Pro Tip: This is our preferred way of loading
NestJS Secret
. If all things were right in the world, all dependencies would be injectable and injected where they're needed.
For ultimate control over the configuration loading process or to integrate NestJS Secrets
into complex existing module factory setups, you can use the SecretsLoaderService
directly. This approach requires you to set up @nestjs/config's ConfigModule.forRoot()
yourself and then provide the ConfigService
using a factory.
This pattern is useful if:
- You need to dynamically configure the
SSMClient
(or any other SDK clients) using NestJS's dependency injection. - You have other complex asynchronous operations required before configuration is finalized.
- You want to separate the concerns of SDK client provisioning from the secret loading logic.
Here’s an example using AWS Parameter Store, where the SSMClient
is provided via DI:
// app.module.ts
import {Module} from '@nestjs/common';
import {ConfigModule, ConfigService} from '@nestjs/config';
import {SSMClient} from '@aws-sdk/client-ssm'; // AWS SDK Client
import {SecretsLoaderService, SecretsModuleOptions} from '@floracodex/nestjs-secrets'; // Import necessary types
@Module({
imports: [
// Configure the base ConfigModule from NestJS.
// SecretsModule.forRoot() is NOT used in this pattern.
ConfigModule.forRoot({
isGlobal: true,
cache: true,
ignoreEnvFile: true
})
],
providers: [
// Provide the SSMClient (or any other SDK client)
{
provide: SSMClient,
useFactory: () => {
return new SSMClient({region: 'us-east-1'}); // Configure your client
}
},
// Override the standard ConfigService with one that uses SecretsLoaderService
{
provide: ConfigService,
useFactory: async (ssmClient: SSMClient) => {
const secretsLoader = new SecretsLoaderService();
// The load method should return the fully resolved configuration object
return secretsLoader.load({
provider: 'AwsParameterStoreProvider', // Or let it be auto-detected from client
client: ssmClient,
files: ['settings.yaml', 'settings.local.yaml'],
root: './config'
// Note that these options _do not_ include the ones available to ConfigModule.
});
},
inject: [SSMClient]
}
]
})
export class AppModule {
}