Cover Image for Using SST Config Variables with Shopify Remix deployed to AWS Lambda

Using SST Config Variables with Shopify Remix deployed to AWS Lambda

Introduction

SST provides for two ways to manage environment variables. The first is through .env files and the second is through the Config class. SST recommends using the Config class, you can read about the reasoning for that in the SST documentation.

Under the hood, SST uses AWS Systems Manager Parameter Store to store the values. That means that in order to use Config through your entire workflow you will need to use SST's local development process "Live Lambda". I wrote an article about developing locally here: Developing Shopify Remix with AWS Lambda.

.env method

Let's look at the using .env files first since it's the more common way of managing environment variables. SST comes out of the box with dotenv support so there is a naming convention setup for managing values in different stages. Read more about that in the Config dotenv.

My local stage is named wwright so my .env file is named .env.wwright. I have a variable DB_HOST that I want to use in my Lambda function. I set the variable in the .env.wwright file like this:

DB_NAME=shopify_app
DB_HOST=notcorrect
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=password123

In order to use this value in the app I need to pass it into the stack definition. shopify.local.app.ts

const site = new RemixSite(stack, "site", {
    runtime: "nodejs20.x",
    cdk: {
        server: {
        vpc: vpc,
        securityGroups: [defaultLambdaSecurityGroup]
        }    
    },
    bind: [rds],
    environment: {
        SHOPIFY_APP_URL: process.env.SHOPIFY_APP_URL || "", 
        SHOPIFY_API_KEY: process.env.SHOPIFY_API_KEY || "",
        SHOPIFY_API_SECRET: process.env.SHOPIFY_API_SECRET || "",
        SCOPES: process.env.SCOPES || "",
        DB_HOST: process.env.DB_HOST || "",
        DB_NAME: process.env.DB_NAME || "",
        DB_USERNAME: process.env.DB_USERNAME || "",
        DB_PASSWORD: process.env.DB_PASSWORD || "",
        DB_PORT: process.env.DB_PORT || "",
    }    
});

Now using it in the shopify server function.

shopify.server.ts

console.log("DB_HOST", process.env.DB_HOST);

I can verify that the Lambda function has the .env value by inspect the function in the AWS Lambda UI. Goto the "Template" tab and the values should all be shown.

Verify env variables in AWS

Config method

The Config method is a bit more involved but it's the recommended way to manage environment variables.

First, you need to define the variable in the app.stack file.

...
const DB_HOST = new Config.Secret(stack, "DB_HOST");
    
const site = new RemixSite(stack, "site", {
    runtime: "nodejs20.x",
    cdk: {
...

Then you need to bind the variable to the Lambda function. Use bind to make the object available to the Lambda function instead of passing the value in the environment object.

const site = new RemixSite(stack, "site", {
    runtime: "nodejs20.x",
    cdk: {
        server: {
        vpc: vpc,
        securityGroups: [defaultLambdaSecurityGroup]
        }    
    },
    environment: {
        SHOPIFY_APP_URL: process.env.SHOPIFY_APP_URL || "", 
        SHOPIFY_API_KEY: process.env.SHOPIFY_API_KEY || "",
        SHOPIFY_API_SECRET: process.env.SHOPIFY_API_SECRET || "",
        SCOPES: process.env.SCOPES || "",
        DB_NAME: process.env.DB_NAME || "",
        DB_USERNAME: process.env.DB_USERNAME || "",
        DB_PASSWORD: process.env.DB_PASSWORD || "",
        DB_PORT: process.env.DB_PORT || "",
    },
    bind: [DB_HOST],
});

To read the value in the Lambda function you need to import the Config class. Note that the object comes from sst/node/config and NOT sst/constructs. sst/constructs is for the Config class that is used to define the variable in the app.stack NOT to use it in the Lambda function. shopify.server.ts

import { Config } from "sst/node/config";

console.log("DB_HOST from config", Config.DB_HOST);

I will change the MySQLSessionStorage constuctor while I'm in here so that the shopify.server function will work with the new DB_HOST variable. shopify.server.ts

...
const shopify = shopifyApp({
  apiKey: process.env.SHOPIFY_API_KEY,
  apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
  apiVersion: LATEST_API_VERSION,
  scopes: process.env.SCOPES?.split(","),
  appUrl: process.env.SHOPIFY_APP_URL || "",
  authPathPrefix: "/auth",
  sessionStorage: MySQLSessionStorage.withCredentials(
    Config.DB_HOST || "",
    process.env.DB_NAME || "",
    process.env.DB_USERNAME || "",
    process.env.DB_PASSWORD || "",
    {
      sessionTableName: 'session',
      connectionPoolLimit: 10
    }
  ),
...

Lastly the value needs to be set using the CLI.

npx sst secrets set DB_HOST 127.0.0.1

To keep the local environment working we need to make a small update to the command for starting Remix.

Open package.json and update the dev command.

package.json

"scripts": {
  ...
  "dev": "npx sst bind --script shopify app dev",
  ...
}

Wrapping the shopify CLI command with bind will ensure that the values are read from AWS and made available to the locally running app.

Run the app locally and you should see the value in the console.

npm run dev

Verify that the variable is coming from Config and not .env.

Verify Config variables in AWS

Top level awaits

The first time that I tried using the Config method I got an error from Remix about top level awaits. The full error is:

[ERROR] Top-level await is not available in the configured target environment ("chrome87", "edge88", "es2020", "firefox78", "safari14" + 2 overrides)

In order to fix the solution for me was modifying the vite.config with the following.

vite.config.ts

Add to defineConfig

export default defineConfig({
  ...
  optimizeDeps: {
    esbuildOptions: {
      target: "esnext"
    } 
  }
  ...