Before top-level await becomes a thing, loading secrets asynchronously from AWS Secrets Manager upon startup is a bit of a pain. I'm wondering if anyone has a better solution than what I currently have.
Upon starting up my Node.JS server I'm loading all secrets from AWS Secrets manager and setting them in config files where I have a mix of hardcoded variables and secrets. Here's an example:
In aws.js
import AWS from 'aws-sdk';
const region = "eu-north-1";
AWS.config.setPromisesDependency();
const client = new AWS.SecretsManager({
region
});
export const getSecret = async(secretName) => {
const data = await client.getSecretValue({SecretId: secretName}).promise();
return data.SecretString;
}
Then in sendgridConfig.js
import { getSecret } from "./aws";
export default async() => {
const secret = JSON.parse(await getSecret("sendgridSecret"));
return {
APIKey: secret.sendgridKey,
fromEmail: "some#email.com",
toEmail: "some#email.com"
}
}
Then in some file where the config is used:
import { sendgridConfig } from "./sendgridConfig";
const myFunc = () => {
const sendgridConf = await sendgridConfig();
... do stuff with config ...
}
This works okay in async functions, but what if I'd like to use the same setup in non-async functions where I use my hardcoded variables? Then the secrets haven't been fetched yet, and I can't use them. Also I have to always await the secrets. IMO a good solution in the future could be top level await, where upon booting the server, the server will await the secrets from AWS before proceeding. I guess I could find a way to block the main thread and set the secrets, but that feels kind of hacky.
Does anyone have a better solution?
So I ended up doing the following. First I'm setting the non-async config variables in an exported object literal. Then I'm assigning values to the object literal in the sendgridConfigAsync IIFE (doesn't have to be an IFEE). That way I don't have to await the config promise. As long as the app awaits the IIFE on startup, the keys will be assigned before being accessed.
In sendgridConfig.js
import { getSecret } from "./aws";
export const sendgridConfig = {
emailFrom: process.env.sendgridFromEmail,
emailTo: process.env.sendgridToEmail
}
export const sendgridConfigAsync = (async() => {
const secret = JSON.parse(await getSecret("Sendgrid-dev"));
sendgridConfig.sendgridKey = secret.key;
})()
Then in the main config file _index.js where I import all the config files.
import { sendgridConfigAsync } from "./sendgrid";
import { twilioConfigAsync } from "./twilio";
import { appConfigAsync } from "./app";
export const setAsyncConfig = async() => {
await Promise.all([
appConfigAsync,
sendgridConfigAsync,
twilioConfigAsync
]);
}
Then in the main index.js file I'm awaiting the setAsyncConfig function first. I did also rebuild the app somewhat in order to control all function invocations and promise resolving in the desired order.
import { servicesConnect } from "../src/service/_index.js";
import { setAsyncConfig } from '$config';
import { asyncHandler } from "./middleware/async";
import { middleware } from "./middleware/_index";
import { initJobs } from "./jobs/_index"
import http from 'http';
async function startServer() {
await setAsyncConfig();
await servicesConnect();
await initJobs();
app.use(middleware);
app.server = http.createServer(app);
app.server.listen(appConfig.port);
console.log(`Started on port ${app.server.address().port}`);
}
asyncHandler(startServer());
Yup I have the same problem. Once you start with a promise, all dependencies down the line require await.
One solution is to do your awaits and only after that have all your downstream code run. Requires a slightly different software architecture.
E.g.
export const getSecretAndThenDoStuff = async(secretName) => {
const data = await client.getSecretValue({SecretId: secretName}).promise();
// instead of return data.SecretString;
runYourCodeThatNeedsSecret(data);
}
A more generic solution to the top-level await that I tend to use:
async function main() {
// Do whatever you want with await here
}
main();
Clean and simple.
Related
I am having the following problem setting up my redux store in a react app with preloadedState: I am relying on data from an async function. I tried to solve it with top level await, but I couldn't enable it via the webpack.config.js file (how to enable top level await in reactjs).
my first question is if react (^18.2.0) supports top level await?
my second question is if there is a workaround to my problem?
async function preloadState() {
const selectionState = await window.browser.storage.local.get("selectionState");
const externalTimings = await window.browser.storage.local.get("externalTimings");
return {
'selectionState': selectionState,
'externalTimings': externalTimings
}
}
export const store = configureStore({
preloadedState: preloadState(),
reducer: {
selectionState: selectionStateSliceReducer,
externalTimings: externalTimingsReducer,
},
})
I am using NestJS with a serverless app (deployed to AWS Lambda). I now have a need to use middleware, or Interceptors as they are called in nest, but I'm struggling to get them to work. I have changed from using NestFactory.createApplicationContext to NestFactory.create, as per the docs, that's what wraps Controller methods with enhancers, e.g. Interceptors
I am registering the Interceptor in a module, so it should be globally available
const loggingInterceptorProvider = {
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
};
My bootstrap looks like so
export async function bootstrap(Module: any) {
if (app) return app;
app = await NestFactory.createApplicationContext(Module);
return await app.init();
}
Now the non-standard bit, because I am using a generic "builder" (library code), the builder is passed the controller name as a string, and it is then invoked, as such
// the Module is accessible in the bootstrap via a closure, not shown in this code
const app = await bootstrap();
const appController = app.get(Controller);
// functionName is a string
const controllerFunction = appController[functionName];
const boundControllerFunction = controllerFunction.bind(
appController,
);
const result = await boundControllerFunction(body);
I am not seeing any of my Interceptor logging output. Am I doing something wrong? Or is it the way I am invoking the Controller that is not working with Interceptors?
EDIT:
For completeness, this is the correct bootstrap function I use
let cachedApp: INestApplication;
export async function bootstrap(Module: any) {
if (cachedApp) return cachedApp;
cachedApp = await NestFactory.create(Module, {
bufferLogs: true,
logger: ['error', 'warn'],
});
await cachedApp.init();
return cachedApp;
}
It happens because you've called the controller method directly, bypassing the nestjs lifecycle. When nest js server handles the request it applies its internal mechanisms for running interceptors, validation pipes, and exception filters. If you call class method directly it will not be used.
In your case you can follow this section of nestjs documentation:
https://docs.nestjs.com/faq/serverless#example-integration
let server: Handler;
async function bootstrap(): Promise<Handler> {
const app = await NestFactory.create(AppModule);
await app.init();
const expressApp = app.getHttpAdapter().getInstance();
return serverlessExpress({ app: expressApp });
}
export const handler: Handler = async (
event: any,
context: Context,
callback: Callback,
) => {
server = server ?? (await bootstrap());
return server(event, context, callback);
};
The "standalone application feature" from docs is useful if you want to call some service code, not a controller.
By the way, in the code snippet, you can see the variable server, they moved it outside of a handler function intentionally. Because in AWS lambdas it can be cached between different requests.
I found a/the way to do it, using the very poorly documented feature ExternalContextCreator. So basically the last code snippet I posted above, would become this
import { ExternalContextCreator } from '#nestjs/core/helpers/external-context-creator';
// the Module is accessible in the bootstrap via a closure, not shown in this code
const app = await bootstrap();
const appController = app.get(Controller);
// functionName is a string
const controllerFunction = appController[functionName];
const extContextCreator = app.get(ExternalContextCreator);
const boundControllerFunction = extContextCreator.create(
appController,
controllerFunction,
String(functionName),
);
const result = await boundControllerFunction(body);
This question references the method examples in AWS SDK for Javascript documentation.
The examples on the page (ie. Describing a table) splits up the code into modules:
ddbClient.js (has configuration settings).
ddb_describetable.js (has code that actually runs the method).
In ddb_describetable.js, why is there an export in front of the "const params..." and the "const run = async"? Can you explain the purpose with a high level example/best practices in relation to AWS or nodejs (if examples/best practices apply)?
ddbClient.js
// Create service client module using ES6 syntax.
import { DynamoDBClient } from "#aws-sdk/client-dynamodb";
// Set the AWS Region.
const REGION = "REGION"; //e.g. "us-east-1"
// Create an Amazon DynamoDB service client object.
const ddbClient = new DynamoDBClient({ region: REGION });
export { ddbClient };
ddb_describetable.js
// Import required AWS SDK clients and commands for Node.js
import { DescribeTableCommand } from "#aws-sdk/client-dynamodb";
import { ddbClient } from "./libs/ddbClient.js";
// Set the parameters
export const params = { TableName: "TABLE_NAME" }; //TABLE_NAME
export const run = async () => {
try {
const data = await ddbClient.send(new DescribeTableCommand(params));
console.log("Success", data);
// console.log("Success", data.Table.KeySchema);
return data;
} catch (err) {
console.log("Error", err);
}
};
run();
The actual DescribeTableCommand code documentation (which doesn't show an example) doesn't imply that this structure should be used.
I'm having doubts about which is the best strategy to manage the many service clients in this web app.
"Best" in terms of a good compromise between user's device RAM and Javascript execution speed (main thread ops).
This is what I'm doing right now, this is the main file:
main.ts:
import type { PlayerServiceClient } from './player.client';
import type { TeamServiceClient } from './team.client';
import type { RefereeServiceClient } from './referee.client';
import type { FriendServiceClient } from './friend.client';
import type { PrizeServiceClient } from './prize.client';
import type { WinnerServiceClient } from './winner.client';
import type { CalendarServiceClient } from './calendar.client';
let playerService: PlayerServiceClient;
export const player = async (): Promise<PlayerServiceClient> =>
playerService ||
((playerService = new (await import('./player.client')).PlayerServiceClient()),
playerService);
let teamService: TeamServiceClient;
export const getTeamService = (): TeamServiceClient =>
teamService ||
((teamService = new (await import('./team.client')).TeamServiceClient()),
teamService);
let refereeService: RefereeServiceClient;
export const getRefereeService = (): RefereeServiceClient =>
refereeService ||
((refereeService = new (await import('./referee.client')).RefereeServiceClient()),
refereeService);
let friendService: FriendServiceClient;
export const getFriendService = (): FriendServiceClient =>
friendService ||
((friendService = new (await import('./friend.client')).FriendServiceClient()),
friendService);
let prizeService: PrizeServiceClient;
export const getPrizeService = (): PrizeServiceClient =>
prizeService ||
((prizeService = new (await import('./prize.client')).PrizeServiceClient()),
prizeService);
let winnerService: WinnerServiceClient;
export const getWinnerService = (): WinnerServiceClient =>
winnerService ||
((winnerService = new (await import('./winner.client')).WinnerServiceClient()),
winnerService);
let calendarService: CalendarServiceClient;
export const getCalendarService = (): CalendarServiceClient =>
calendarService ||
((calendarService = new (await import('./calendar.client')).CalendarServiceClient()),
calendarService);
// and so on... a lot more...
As you can see there are many service clients.
I'm using this code because I thought it was better given my web app structure based on routes almost overlapping with client services:
I mean, if the player goes from /home to /players page I can use it like this:
components/players.svelte
import { getPlayerService } from "main";
const playerService = await getPlayerService();
const players = await playerService.queryPlayers();
In this way, if the PlayerService does not exist, it is imported at the moment and returned, otherwise it returns the one imported and instantiated before.
Since the user switches pages frequently this way I can avoid the sudden creation and destruction of those clients, right?
But in this way I am using global variables which I don't like to use and I'm using verbose, DRY and long code in each component.
Is there a way to use the below code in components instead?
import { playerService } from "main";
const players = await playerService.queryPlayers();
What do you suggest me to do?
The patterns you are implementing are "lazy loading" and "singleton".
You could have a single service factory which implements those patterns and use it for every service:
File serviceFactory.js
const serviceMap = {};
export function getService(serviceName) {
return serviceMap[serviceName] ?? (serviceMap[serviceName] = import(serviceName).then(x => new x.default));
}
The ECMAScript modules standard will take care of executing the serviceFactory.js code only once in the application (no matter how many times you import it), so you can hold the singletons in a map assigned to a private top-level variable of the serviceFactory.js module.
This service factory implies that every service is exported with the default keyword like that:
export default class SomeService {
constructor() {
// ...
}
fetchSomething() {
// ...
}
}
Then, use the services everywhere in your application with this code:
import { getService } from './serviceFactory.js';
const service = await getService('./services/some.service.js');
const something = await service.fetchSomething();
If you really want to remove the double await, you can encapsulate it in the service factory like that:
const serviceMap = {};
export function getService(serviceName) {
return serviceMap[serviceName] ?? (serviceMap[serviceName] = resolveService(serviceName));
}
function resolveService(name) {
const futureInstance = import(name).then(x => new x.default);
const handler = {
get: function (target, prop) {
return function (...args) {
return target.then(instance => instance[prop](...args));
}
}
}
return new Proxy(futureInstance, handler);
}
Which allows you to write this code:
const something = await getService('./services/some.service.js').fetchSomething();
This allows the service to be loaded at the exact line of code where you need it.
If it doesn't bothers you to load it with a static import because you need the import { playerService } from "main"; syntax, you can expose every service like this in one file per service:
export const playerService = getService('./services/player.service.js');
I have published the full working demo here: https://github.com/Guerric-P/lazy-singletons-demo
I don't think the code from #Guerric will work with build tools (like webpack.)
Specifically dynamic string imports import(modulePath) is not supported.
My recommendation is to reduce the repeating bits of code to their smallest representation... Hopefully, it'll end up feeling less noisy.
Solution #1/2
Here's an example using a higher-order memoize function to help with the caching.
// Minimal definition of service loaders
export const getPlayerService = memoize<PlayerServiceClient>(async () => new (await import('./player.client')).PlayerServiceClient());
export const getTeamService = memoize<TeamServiceClient>(async () => new (await import('./team.client')).TeamServiceClient());
export const getRefereeService = memoize<RefereeServiceClient>(async () => new (await import('./referee.client')).RefereeServiceClient());
export const getFriendService = memoize<FriendServiceClient>(async () => new (await import('./friend.client')).FriendServiceClient());
export const getPrizeService = memoize<PrizeServiceClient>(async () => new (await import('./prize.client')).PrizeServiceClient());
export const getWinnerService = memoize<WinnerServiceClient>(async () => new (await import('./winner.client')).WinnerServiceClient());
// Mock hacked together memoize fn
// TODO: Replace with some npm library alternative
const fnCache = new WeakMap();
function memoize<TReturn>(fn): TReturn {
let cachedValue = fnCache.get(fn);
if (cachedValue) return cachedValue;
cachedValue = fn();
fnCache.set(fn, cachedValue);
return cachedValue;
}
Solution #2/2
Depending on the version of the JS engine & transpiler, you could possibly cut out some code and use the nature of modules to cache singletons of your services.
(Note: I've occasionally run into gotchas here around how ES Modules rely on deterministic exports. The workaround is to assign the exports to pending promises which return the instance.)
The important feature to know about Promises: they are only resolved once, and can be used to effectively cache their result.
Each await or .then will get the initial resolved value.
// SUPER minimal definition of services
export const playerService = (async (): PlayerServiceClient => new (await import('./player.client')).PlayerServiceClient())();
export const teamService = (async (): TeamServiceClient => new (await import('./team.client')).TeamServiceClient())();
export const refereeService = (async (): RefereeServiceClient => new (await import('./referee.client')).RefereeServiceClient())();
export const friendService = (async (): FriendServiceClient => new (await import('./friend.client')).FriendServiceClient())();
export const prizeService = (async (): PrizeServiceClient => new (await import('./prize.client')).PrizeServiceClient())();
export const winnerService = (async (): WinnerServiceClient => new (await import('./winner.client')).WinnerServiceClient())();
Calling the Service Wrapper
import { playerService } from "./services";
// Example: Using async/await IIFE
const PlayerService = (async () => await playerService)();
function async App() {
// Example: Function-scoped service instance:
// const PlayerService = await playerService
const players = await PlayerService.queryPlayers();
}
I don't know if I'm missing something in the docs, but I have this situation:
// test.js
import User from './user'
it("should load initial data", async() => {
const users = new User()
const user = await users.load()
})
// User.js
import Api from './api'
export default class User {
async load() {
const res = await Api.fetch() // prevent/mock this in testing
}
}
What is the Jest-way to prevent/mock the external Api module in User.js. I do not want User.js to make a real network request within the test.
Further to this, I'm looking for a more generic mocking solution, ie. say I'm testing in React Native, and I want to mock NativeModules.SettingsManager.settings.AppleLocale, for example. Lets say Api.fetch() calls the line above, and doesn't make a HTTP request
spyOn in combination with mock functions like mockImplementation will provide what you are looking for.
Here is a working example:
// ---- api.js ----
export const getData = () => {
return Promise.resolve('hi');
}
// ---- user.js ----
import { getData } from './api'
export default class User {
async load() {
return await getData(); // mock this call in user.test.js
}
}
// ---- user.test.js ----
import User from './user'
import * as Api from './api'; // import * so we can mock 'getData' on Api object
describe('User', () => {
it('should load initial data', async() => {
const mock = jest.spyOn(Api, 'getData'); // create a spy
mock.mockImplementation(() => Promise.resolve('hello')); // give it a mock implementation
const user = new User();
const result = await user.load();
expect(result).toBe('hello'); // SUCCESS, mock implementation called
mock.mockRestore(); // restore original implementation when we are done
});
});
If you need to mock responses to HTTP requests, then you should check out nock. It has a clean API that allows a lot of flexibility in creating HTTP responses to specific requests.