I have a simple NestJS application running on AWS Lambda. I am using the AWS SSM Parameter Store to keep database connection information and credentials. When I import TypeORM I use the parameters already retrieved from the store.
Right now I am just retrieving the params right in my AppModule where TypeORM is imported. I'm sure there is a better way to do this, but I'm not sure what it would be. A custom Provider? Some kind of settings Service? I don't think my current solution is very robust and there isn't great error handling.
The only requirement is that I am retrieving the SSM parameters at runtime and not at build or deploy time. Any advice
This is what I am currently doing:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ThingyModule } from 'thingy/thingy.module';
import * as awsParamStore from 'aws-param-store';
const ssmParams = awsParamStore.getParametersByPathSync('/myapp/prod', {region: (process.env['AWS_DEFAULT_REGION'] ? process.env['AWS_DEFAULT_REGION'] : 'us-east-2')});
const ssmMap = ssmParams.reduce(function(map, obj) {
map[obj.Name] = obj.Value;
return map;
}, {});
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: ssmMap['/myapp/prod/db/host'],
port: 5432,
username: ssmMap['/myapp/prod/db/username'],
password: ssmMap['/myapp/prod/db/password'],
database: 'postgres',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
ThingyModule
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
The parameters have been created previously with the AWS CLI:
// aws ssm put-parameter --type String --name /myapp/prod/db/username --value postgres --region us-east-2
// aws ssm put-parameter --type String --name /myapp/prod/db/password --value supRCkrit --region us-east-2
// aws ssm put-parameter --type String --name /myapp/prod/db/host --value localhost --region us-east-2
We can do this using async providers.
Sample from a working project:
Create async provider:
import * as AWS from 'aws-sdk';
import { Parameter } from 'aws-sdk/clients/ssm';
export const ssmProvider = {
provide: 'AWS_SSM',
useFactory: async (): Promise<Parameter[]> => {
const ssmClient = new AWS.SSM({
endpoint: 'endpoint',
region: 'us-west-2',
});
const result = await ssmClient
.getParametersByPath({
Path: '/ssm/path',
Recursive: true,
})
.promise();
return result?.Parameters;
},
};
We can then use this provider for injecting values.
For example injecting AWS SSM Parameters in config service:
export class ConfigModule {
static register(options: ConfigModuleOptions): DynamicModule {
return {
global: true,
module: ConfigModule,
providers: [
ssmProvider,
{
provide: CONFIG_OPTIONS,
useValue: options,
},
ConfigService,
],
exports: [ConfigService],
};
}
}
And in config service constructor:
constructor(
@Inject(CONFIG_OPTIONS) options: ConfigOptions,
@Inject('AWS_SSM') awsParameters: Parameter[],
) {}
Edit: NPM Package which provide a service which loads aws param store parameters at the application start up: