Step 5: Pin API - solargis/cdk-workshop GitHub Wiki

In this step we will implement Pin API - API for saving and managing Pins in the map.

Pin is a point in the map containing map coordinates, geocoding information and image metadata. For storing Pins we will use DynamoDB database and for storing pin images we will use S3 bucket.

Pin API will have following endpoints:

  • GET /pin - get all pins with resolved public URLs to pin images
  • POST /pin - store new pin with image
  • GET /pin/{pointUrl} - get single pin with resolved public URL to image
  • DELETE /pin/{pointUrl} - delete pin and its image

Pin API will be backed by single lambda handler pin-lambda, which will recognize operation by HTTP method and path.

We will setup webpack build for API code, as pin-lambda uses other npm dependencies which needs to be deployed together with Lambda code.

Fast-forward to this step (optional)

git checkout step-5/pin-api
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

5.1 Lambda API code (skip if fast-forwarded)

Add npm dependencies:

npm install --save uuid @aws-cdk/aws-dynamodb
npm install --save-dev @types/uuid webpack webpack-cli ts-loader

Extract API code from step5-pin-api.zip to ./

./lib/api/pin-lambda.ts                     - Lambda handler code for Pin API
./lib/api/utils/s3.utils.ts                 - Helper functions for S3 operations
./lib/shared/                               - root of shared code between API and Angular
./lib/shared/types/nominatim.types.ts       - Types for Nominatim geocoder responses
./lib/shared/types/pin.types.ts             - Types used in Pin API
./lib/shared/utils/point.utils.ts           - Helper functions for LatLng points
./pin-request.json                          - example request body for: POST /pin

5.2 Setup API build with Webpack

Add webpack config file for API build: ./lib/webpack.api.js

const { path: rootPath } = require('app-root-path');
const { resolve } = require('path');

const awsModules = ['aws-sdk', 'aws-sdk/clients/dynamodb', 'aws-sdk/clients/s3'];

const config = {
  mode: 'none',
  context: resolve(rootPath),
  entry: {
    'hello-lambda': './lib/api/hello-lambda.ts',
    'pin-lambda': './lib/api/pin-lambda.ts'
  },
  externals: [...awsModules],
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: [/node_modules/],
        use: [{
          loader: 'ts-loader',
          options: { configFile: 'tsconfig.api.json' }
        }]
      }
    ]
  },
  resolve: { extensions: ['.ts', '.js'] },
  output: {
    filename: '[name].js',
    libraryTarget: 'commonjs2',
    path: resolve(rootPath, 'dist/api')
  },
  target: 'node',
  devtool: 'cheap-source-map'
};

module.exports = config;

Update api:build script in ./package.json

"api:build": "trash dist/api && webpack --config lib/webpack.api.js",
  • Build API code: npm run api:build
  • Check API production code in ./dist/api

5.3 Define cloud resources for Pin API

Add CORS utils into ./cdk/cors.utils.ts

import { IResource, MockIntegration, PassthroughBehavior } from '@aws-cdk/aws-apigateway';

export function addCorsOptions(apiResource: IResource, ...customHeaders: string[]) {
  apiResource.addMethod('OPTIONS', new MockIntegration({
    integrationResponses: [{
      statusCode: '200',
      responseParameters: {
        'method.response.header.Access-Control-Allow-Headers':
          "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent"
          + (customHeaders && customHeaders.length ? ',' + customHeaders.join(',') : '') + "'",
        'method.response.header.Access-Control-Allow-Origin': "'*'",
        'method.response.header.Access-Control-Allow-Credentials': "'false'",
        'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,GET,PUT,POST,PATCH,DELETE'",
      },
    }],
    passthroughBehavior: PassthroughBehavior.NEVER,
    requestTemplates: {
      "application/json": "{\"statusCode\": 200}"
    },
  }), {
    methodResponses: [{
      statusCode: '200',
      responseParameters: {
        'method.response.header.Access-Control-Allow-Headers': true,
        'method.response.header.Access-Control-Allow-Methods': true,
        'method.response.header.Access-Control-Allow-Credentials': true,
        'method.response.header.Access-Control-Allow-Origin': true,
      },
    }]
  })
}

Define S3 Bucket and DynamoDB table for Pin API: ./cdk/cdk-workshop-stack.ts

import { CfnOutput, Construct, RemovalPolicy, Stack, StackProps } from '@aws-cdk/core';
import { AttributeType, BillingMode, Table } from '@aws-cdk/aws-dynamodb';

// ...

// S3 bucket for hosting pin images
const imageBucket = new Bucket(this, 'ImageBucket');

// DynamoDB table to store pins
const pinTable = new Table(this, 'PinTable', {
  partitionKey: {
    name: 'pointUrl',
    type: AttributeType.STRING
  },
  billingMode: BillingMode.PAY_PER_REQUEST,
  removalPolicy: RemovalPolicy.DESTROY
});

Define CDK Lambda handler for Pin API: ./cdk/cdk-workshop-stack.ts

const pinHandler = new Function(this, 'PinHandler', {
  code: apiCode,
  runtime: Runtime.NODEJS_10_X,
  handler: 'pin-lambda.handler',
  environment: { // pass environment variables from CDK resources
    IMAGE_BUCKET: imageBucket.bucketName,
    PIN_TABLE: pinTable.tableName
  }
});
// grant access to image bucket and pin table
imageBucket.grantReadWrite(pinHandler);
pinTable.grantReadWriteData(pinHandler);

Define Pin API Endpoints: ./cdk/cdk-workshop-stack.ts

import { addCorsOptions } from './cors.utils';

const api = // ...

const pinApi = api.root.addResource('pin');

// OPTIONS /pin
addCorsOptions(pinApi);
// ANY /pin
pinApi.addMethod('ANY', new LambdaIntegration(pinHandler));

const pinPointApi = pinApi.addResource('{pointUrl}');

// OPTIONS /pin/{pointUrl}
addCorsOptions(pinPointApi);
// ANY /pin/{pointUrl}
pinPointApi.addMethod('ANY', new LambdaIntegration(pinHandler));

Build API and deploy

npm run api:build
cdk deploy

Test

Next Step 6: Pin Angular app