Step 8: Local development environment - solargis/cdk-workshop GitHub Wiki

In this step we will configure all the environment to run on local machine. So we can easily test and develop the application without the build-deploy roundtrip to the cloud.

For that purpose we will use Serverless framework and its plugins to setup AWS resources locally. With hot-reload features of Angular CLI and Webpack we will configure a development mode supporting hot-code reload for both lambda code and angular code.

NOTE: Java JRE 1.8+ is required to run AWS DynamoDB Local

Fast-forward to this step (optional)

git checkout step-8/local-env
npm install

NOTE: if you have uncommited work in project repository it is not possible to checkout new Git branch, you have 3 options:

  • git stash - move all local changes into a 'drawer', more info here
  • git commit -a -m "my change" - commit all changes to local branch
  • git reset --hard HEAD - remove all local changes

8.1 Configure local Serverless

Add npm dependencies:

npm install --save-dev concurrently serverless serverless-dynamodb-local serverless-offline serverless-s3-local

Add Serverless configuration: ./serverless.yml

service: cdk-workshop-local

plugins:
  - serverless-dynamodb-local
  - serverless-s3-local
  - serverless-offline

provider:
  name: aws
  runtime: nodejs10.x
  region: eu-west-1
  stage: local
  environment:
    S3_ENDPOINT: 'http://localhost:9000'
    DYNAMODB_ENDPOINT: 'http://localhost:8000'
    IMAGE_BUCKET: ImageBucketLocal
    PIN_TABLE: PinLocal
    TEMP_DIR: '.local/tmp'

custom:
  serverless-offline:
    port: 4000
  dynamodb:
    stages:
      - local
    start:
      port: 8000
      dbPath: .local/dynamo
      sharedDb: true
      migrate: true
  s3:
    host: 0.0.0.0
    port: 9000
    directory: .local/s3

resources:
  Resources:
    imageBucketLocal:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:provider.environment.IMAGE_BUCKET}
    pinTableLocal:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:provider.environment.PIN_TABLE}
        AttributeDefinitions:
          - AttributeName: pointUrl
            AttributeType: S
        KeySchema:
          - AttributeName: pointUrl
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1

functions:
  hello:
    handler: 'dist/api/hello-lambda.handler'
    events:
      - http:
          method: get
          path: /hello
          cors: true

  pin:
    handler: 'dist/api/pin-lambda.handler'
    events:
      - http:
          method: any
          path: /pin
          cors: true
      - http:
          method: any
          path: /pin/{pointUrl}
          cors: true

  thumbnail:
    handler: 'dist/api/thumbnail-lambda.handler'
    events:
      - s3:
          bucket: ${self:provider.environment.IMAGE_BUCKET}
          event: s3:ObjectCreated:*
          rules:
            - prefix: original

Create empty folders for local resources

  • .local/dynamo
  • .local/s3
  • .local/tmp

Add npm scripts for local development: ./package.json

"dev": "concurrently -c gray,yellow,green npm:api:watch npm:api:serve npm:web:serve --kill-others-on-fail",
"api:watch": "npm run api:build -- --watch --progress",
"api:serve": "sls dynamodb install && sls offline start",
"web:serve": "ng serve"

Set local API url (http://localhost:4000) as API base: ./lib/web/src/index.html

<meta name="x-api-base" content="http://localhost:4000/">

8.2 Support local endpoints in lambda code

Update Lambda code to support local S3 endpoint: ./lib/api/utils/s3.utils.ts

const localEndpoint = process.env.S3_ENDPOINT as any;

const s3 = new S3(localEndpoint && {
  accessKeyId: 'S3RVER',
  secretAccessKey: 'S3RVER',
  region: 'local',
  endpoint: localEndpoint
});

// ...

function getPublicUrl(s3key: string): string {
  return localEndpoint
    ? `${localEndpoint}/${imageBucket}/${s3key}`
    : `https://${imageBucket}.s3-${region}.amazonaws.com/${s3key}`;
}

function getDownloadUrl(s3key: string, name: string) {
  return localEndpoint
    ? `${localEndpoint}/${imageBucket}/${s3key}`
    : s3.getSignedUrl('getObject', {
        Bucket: imageBucket,
        Key: s3key,
        ResponseContentDisposition: `attachment; filename="${name}"`
      });
}

Update Lambda code to support local DynamoDB endpoint:

./lib/api/pin-lambda.ts

const dynamo = new DynamoDB.DocumentClient({
  endpoint: process.env.DYNAMODB_ENDPOINT
});

./lib/api/thumbnail-lambda.ts

const tempDir = process.env.TEMP_DIR || '/tmp';

const dynamo = new DynamoDB.DocumentClient({
  endpoint: process.env.DYNAMODB_ENDPOINT
});

Test

Launch local development environment:

npm run dev

Next Step 9: CloudFront CDN