Cover Image for Shopify-Remix App in AWS Lambda local development setup

Shopify-Remix App in AWS Lambda local development setup

Shopify Remix AWS Lambda Local Development Setup

When deploying a Shopify-Remix app to AWS Lambda, one of the services that is required is a database. In my work environment that usually means MySQL or Postgres. Since we are deploying to AWS the cloud version of MySQL or Postgres is RDS.

The SST stack that I use as a reference creates the RDS intance inside of a VPC. This is a good practice for security reasons. However, it makes local development a bit more difficult. The VPC is a private network and the RDS instance is not accessible from the public internet. This means that you can't connect to the RDS instance from your local machine.

When you run npx sst dev and npm run dev the app will start up and you can access it from your browser. However, the app will not be able to connect to the RDS instance. For this reason I have created a local development setup that uses a local MySQL instance.

Prerequisites

This tutorial assumes some setup from Remix template that I used in the previous post.

Specifically, it assumes that Prisma has been replaced with Kyseley.

Create a local MySQL instance

For local development I will use a local MySQL instance instead of the AWS RDS instance. I will use Docker to run the instance.

Create the docker-compose file which will start a MySQL instance.

#####################################
# 
#####################################
services:
  db:
    platform: linux/x86_64
    image: mysql:8
    volumes:
      - mysqldb:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=password123
      - MYSQL_DATABASE=shopify_app
    ports:
      - "127.0.0.1:3306:3306"
volumes:
  mysqldb:

Run docker-compose up to start the MySQL instance.

Update the .env file

Update the .env file to point the local Remix app to the local MySQL instance.

SHOPIFY_API_KEY=somekey
SHOPIFY_API_SECRET=somesecret
SCOPES=read_products,write_products
DB_NAME=shopify_app
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=password123

Create a local stack

First let's create a "local" stack that we can use for local development. This stack will use a local MySQL instance instead of RDS.

See my previous post for the full setup. I am going to copy the stack that I created in that post and use it as a starting point.

Copy stacks/shopify.app.stack to shopify.local.app.stack. Next remove the rds definition so that the stack only contains: vpc, security group, and the RemixSite.

shopify.local.app.stack

import { SecurityGroup, Vpc } from "aws-cdk-lib/aws-ec2";
import { RemixSite, StackContext } from "sst/constructs";

const ShopifyLocalApp = (context: StackContext) => {
    const {app, stack} = context;
    
    /**
     * Create a VPC with a single NAT gateway
     * 
     * @param {Stack} stack
     * @param {App} app
     * @returns {Vpc}
     */
    const vpc = new Vpc(stack, app.logicalPrefixedName('net'), { natGateways: 1 });

    /**
     * Create a default security group for lambda functions
     * 
     * @param {Stack} stack
     * @returns {SecurityGroup}
     */
    const defaultLambdaSecurityGroup = new SecurityGroup(stack, 'DefaultLambda', {
        vpc: vpc,
        description: 'Default security group for lambda functions',
    });

    /**
     * Set the default function props
     */
    app.setDefaultFunctionProps({
        vpc: vpc,
        securityGroups: [defaultLambdaSecurityGroup],
    });

    /**
     * Create a Remix site
     * 
     * @param {Stack} stack
     * @returns {RemixSite}
     */
    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_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 || "",
        }    
    });

    stack.addOutputs({
        url: site.url,
    });    
};

export default ShopifyLocalApp;

We need to update the sst.config.ts file to switch between the local and prod stacks.

sst.config.ts

  stacks(app) {
    if (app.stage !== "prod") {
      app.stack(ShopifyLocalApp);
    } else {
      app.stack(ShopifyApp);      
    }
    app.setDefaultRemovalPolicy("destroy");

I use two stages, "prod" and "wwright". The "wwright" stage is my personal stage that I use for local development. The "prod" stage is the stage that I use for production.

Run npx sst dev to bring up the local stack and use the local config.

Start the Remix app

Run npm run dev to start the Remix app. The app should start up and connect to the local MySQL instance. The data migrations will be run automatically and the Shopify should be able to connect to the app.