File types, purposes, and directory skeleton - Part 5 of Implementing a RESTful API on AWS
Tags: aws, serverless, rest, cognito
Published: 2021-05-06 21:16:38

Quick recap: this is a soup to nuts series covering implementing a full-featured REST API on AWS from the perspective of a long-time infrastructure engineer with a historical preference for Java-based solutions.

In the previous parts of this series, we (a) set up our build environment; (b) registered our AWS account; (c) created our Administrative users; and (d) stubbed out our default Serverless service.  

We'll now cover (I) the various file types we will be using; and (II) our directory structure; while endeavoring to explain the design decisions behind why we are proceeding in the way we are.

#I. Various File Types

##A. Serverless / CloudFormation Configuration Files
At its core, Serverless Framework is an extremely powerful javascript module that translates a YAML configuration file named `serverless.yml` into vendor-specific cloud service commands.  In the case of AWS, these are AWS CloudFormation commands such as `create-function` for AWS Lambda or `create-table` for DynamoDB.

But why introduce this intermediate Serverless Framework instead of just using the AWS CLI?  A number of reasons, including: (1) the veneer of avoiding vendor lock-in; (2) the additional variables; and (3) the ability to develop and debug our code offline without using AWS resources.  

With that, let's look at the default `serverless.yml` file that we created in the last part of this series.  Switch to your working directory and type `code serverless.yml` to open the file in VS Code. 

### 1) Initial Serverless.yml Skeleton

{% details Full contents of the default serverless.yml %} 
```yml
# Welcome to Serverless!
#
# This file is the main config file for your service.
# It's very minimal at this point and uses default values.
# You can always add more config options for more control.
# We've included some commented out config examples here.
# Just uncomment any of them to get that config option.
#
# For full config options, check the docs:
#    docs.serverless.com
#
# Happy Coding!

service: myrestproject
# app and org for use with dashboard.serverless.com
#app: your-app-name
#org: your-org-name

# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
frameworkVersion: '2'

provider:
  name: aws
  runtime: nodejs12.x
  lambdaHashingVersion: 20201221

# you can overwrite defaults here
#  stage: dev
#  region: us-east-1

# you can add statements to the Lambda function's IAM Role here
#  iamRoleStatements:
#    - Effect: "Allow"
#      Action:
#        - "s3:ListBucket"
#      Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ]  }
#    - Effect: "Allow"
#      Action:
#        - "s3:PutObject"
#      Resource:
#        Fn::Join:
#          - ""
#          - - "arn:aws:s3:::"
#            - "Ref" : "ServerlessDeploymentBucket"
#            - "/*"

# you can define service wide environment variables here
#  environment:
#    variable1: value1

# you can add packaging information here
#package:
#  include:
#    - include-me.js
#    - include-me-dir/**
#  exclude:
#    - exclude-me.js
#    - exclude-me-dir/**

functions:
  hello:
    handler: handler.hello
#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#    Check the event documentation for details
#    events:
#      - httpApi:
#          path: /users/create
#          method: get
#      - websocket: $connect
#      - s3: ${env:BUCKET}
#      - schedule: rate(10 minutes)
#      - sns: greeter-topic
#      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
#      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
#      - iot:
#          sql: "SELECT * FROM 'some_topic'"
#      - cloudwatchEvent:
#          event:
#            source:
#              - "aws.ec2"
#            detail-type:
#              - "EC2 Instance State-change Notification"
#            detail:
#              state:
#                - pending
#      - cloudwatchLog: '/aws/lambda/hello'
#      - cognitoUserPool:
#          pool: MyUserPool
#          trigger: PreSignUp
#      - alb:
#          listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/
#          priority: 1
#          conditions:
#            host: example.com
#            path: /hello

#    Define function environment variables here
#    environment:
#      variable2: value2

# you can add CloudFormation resource templates here
#resources:
#  Resources:
#    NewResource:
#      Type: AWS::S3::Bucket
#      Properties:
#        BucketName: my-new-bucket
#  Outputs:
#     NewOutput:
#       Description: "Description for the output"
#       Value: "Some output value"
```
{% enddetails %}

You'll see that most of the default file consists of commented out formatted examples, with only 9 lines of actual configuration in YAML format:

```yml
service: myrestproject
frameworkVersion: '2'

provider:
  name: aws
  runtime: nodejs12.x
  lambdaHashingVersion: 20201221
functions:
  hello:
    handler: handler.hello
```
Each of these is a key/value pair.  An important point to remember is that the key names, like variable names, will be constants that cannot be dynamically set.  Rather, the names of the constants will--depending on the situation--be set by either: (a) serverless; (b) AWS; or (c) you the end user.  The values, on the other hand, will often be dynamically set.  

Let's cover each of the nine functional lines in the default configuration file:

---
```yml
service: myrestproject
```
####a) service
The value of the `service` key will be used to generate the name your AWS deployment bundle.  AWS refers to these--unsurprisingly--as a 'stack'.  

Serverless follows the recommended practice of appending a development stage modifier to the base stack name.  By default, this value is 'dev', giving us a full stack name of 'myrestproject-dev'.

You can see the list of deployed stacks through both the web UI (be sure your region in the top right is set correctly):

  https://console.aws.amazon.com/cloudformation/

and the AWS CLI:
```shell
$ aws cloudformation list-stacks
``` 
---
```yml
frameworkVersion: '2'
```
####b) frameworkVersion

The version of the Serverless Framework.  The supplied value here is pinned to any version 2 of the Framework.  No need to change it.

---
```yaml
provider:
  name: aws
  runtime: nodejs12.x
  lambdaHashingVersion: 20201221
```
####c) provider
Provider is our first structured entry.  All of our vendor-specific entries will be children below this key.  There are a number of additional ones, but we are only covering the basic ones for now:


#####i) name
The name of our cloud service vendor: 'aws'.  Other options would include 'azure' or whatever that thing is that Google offers.

#####ii) runtime
The default version of Node.js needed to run our functions.  (We can override this on a per-function basis on the off-chance it is necessary).  AWS claims to support Node.js version 14, but at least some features were not working correctly last time I checked in March 2021 (e.g. nullish coalescing).  Go ahead and keep it as `nodejs12.x` for the time being.

#####iii) lambdaHashingVersion

The Serverless Framework occasionally deprecates and changes underlying functionality.  In this instance, they apparently are transitioning the underlying hash function used to determine if a function has been altered.  Since we are just starting out, we should switch right now to what will be the default in Serverless Framework 3.0 by setting this value to `20201221` as indicated in the default serverless file.

---
```yml
functions:
  hello:
    handler: handler.hello
```
####d) functions
Back up to the top level, `functions` contains a structured description of our lambda functions.  Each function will be its own entry.

#####i) hello
This is a developer-selected name used for referencing the function elsewhere in our serverless.yml and AWS CloudFormation configuration files.  Here, the provided name is `hello`, but it could be `yolo` or `mydogspot` or something else, subject to the caveat noted before that key names are required to be constants (e.g. it cannot be named `${self.custom.myLambdaName}`).

######(1) handler
The javascript function that will process executions of this lambda function.  The format is &lt;filename&gt;.&lt;function name&gt; (e.g. handler.hello points to the hello function in handler.js within the base directory, whereas ./src/aws/AWSController.findOne points at the findOne function in AWSController.js within the ./src/aws/ directory).

### 2) Modified Serverless.yml
With this basic understanding of our serverless.yml configuration file out of the way, let's start putting together a working REST template.

{% details Full contents of the modified serverless.yml %} 
```yml
# app and org for integration with dashboard.serverless.com
# Note that using this will cause Serverless to inject a logging handler proxy
# before the calls to your lambda functions, resulting in the renaming of your handler
# in the AWS interface
#org: <your_org_name_here>
#app: <your_dashboard_appname_here>

# name of our microservice
service: ${self:custom.resourceName}Service

frameworkVersion: '>=2.0'
variablesResolutionMode: 20210219

#############################################################################################
# CUSTOM VARIABLES - all variables necessary to get up and running should be in this section
#############################################################################################
custom:
  resourceName: teams
  # Email address of primary contact; embedded into internal "Tags" to aid in
  # internal maintenance
  primaryContact: <YOUR EMAIL ADDRESS>
  region: <YOUR AWS REGION>

  dynamodb:
    # For tables, I have adopted a naming convention of <SERVICE>-Table-<EntityName>-<Stage>; e.g. SportsApp-Table-Players-dev
    resourceTable: ${self:service}-Table-${self:custom.resourceName}-${self:provider.stage}
    # For indexes, I have adopted a naming convention of <SERVICE>-TableIdx-<EntityName>-<Constraint/SearchKey>-<Stage>; e.g. SportsApp-TableIdx-Players-LastName-dev
    resourceTableIdx: ${self:service}-TableIdx-${self:custom.resourceName}-Name-${self:provider.stage}

provider:
  name: aws
  # AWS claims they support NodeJS14, but as of March 2021, certain Node14 features don't appear to function correctly, e.g. nullish coalescing
  runtime: nodejs14.x
  lambdaHashingVersion: 20201221
  stage: dev  
  region: ${self:custom.region}
  
  apiGateway:
    # setting required for the transition from Serverless Framework 2.0 to 3.0+
    shouldStartNameWithService: true

    # we can have AWS API Gateway validate incoming requests prior to sending it to our lambda,
    # saving us the processing cost for malformed requests
    request:
      schemas:
        resource-create-model:
          name: ${self:custom.resourceName}CreateModel
          schema: ${file(src/schema/createResourceSchema.json)}
          description: "A Model validation for creating ${self:custom.resourceName}"

  # Environment variables our Javascript code relies on
  environment:
    NODE_ENV: ${self:provider.stage}
    TABLE_NAME_TEAM: ${self:custom.dynamodb.resourceTable}
    TABLE_IDX_TEAM_NAME: ${self:custom.dynamodb.resourceTableIdx}

  # Base IAm role that the lambdas will execute with
  iam: ${file(./resources/aws/iam/LambdaRole.yml)}

# Serverless Framework plugins
plugins:
  # support for typescript
  - serverless-plugin-typescript

  # Allows us to use a local instance during development, eliminating need
  # to publish to cloud to see changes to functions and allowing use of a debugger
  - serverless-offline

# define all of our REST microservice endpoints
functions: ${file(./resources/aws/RESTFunctions.yml)}

# Settings relating to how our module will be bundled for execution
package:
  # Don't include node_modules like typescript and unit testing when
  # publishing our bundle to the cloud; results in notably smaller upload bundle
  excludeDevDependencies: true

resources:
  # Values that we want to export out of this service for use in other services
  # Including an export name pollutes the global namespace and is only necessary
  # when you don't know this Stack's name, as you can otherwise obtain the value
  # using the key name you chose below, e.g. 'ApiGatewayRestApiExport'
  Outputs:
    ApiGatewayRestApiExport:
      Value: !Ref 'ApiGatewayRestApi'
#      Export:
#        Name: ${self:service}-${self:provider.stage}-api

  # The various resources can be given custom developer-friendly names, as shown below.
  # What the resource consists of is determined by the 'type' sub-property found in the files
  Resources:

    ############################################################
    # DATABASE RESOURCES
    ############################################################
    # Data table for this microserver
    ResourceTable:  ${file(./resources/aws/dynamodb/ResourceTable.yml)}



```
{% enddetails %}

#### a) Change our service name
Change the service value from `myrestproject` to 
```yaml
service: ${self:custom.resourceName}Service
```
Note that for this value, we have introduced for the first time the use of a variable value.  These come in the format: `${source:key}`.  For references to the present serverless file, the source is `self`.  So the value for our `service` key is now going to consist of the value located at `custom.resourceName` concatenated with 'Service'.  Note that we are referencing a value that has not yet been defined at this point in the file--it is okay if it is defined further down in the file.  So let's get to it.

#### b) 'custom' top level entry
The Serverless Framework defines at top level of our YAML hierarchy a `custom` key, which can be used to hold many of our installation-specific values.  The various key-value pairs in this `custom` section are user-defined however we see fit.

We'll go ahead and add the following:
```yaml
#############################################################################################
# CUSTOM VARIABLES - all variables necessary to get up and running should be in this section
#############################################################################################
custom:
  # Name of our REST resource, e.g. 'players', 'team', 'league', etc.
  resourceName: teams
  # Email address of primary contact; embedded into internal "Tags" to aid in
  # internal maintenance
  primaryContact: <YOUR EMAIL ADDRESS>
  region: <YOUR AWS REGION>

  dynamodb:
    # For tables, I have adopted a naming convention of <SERVICE>-Table-<EntityName>-<Stage>; e.g. SportsApp-Table-Players-dev
    resourceTable: ${self:service}-Table-${self:custom.resourceName}-${self:provider.stage}
    # For indexes, I have adopted a naming convention of <SERVICE>-TableIdx-<EntityName>-<Constraint/SearchKey>-<Stage>; e.g. SportsApp-TableIdx-Players-LastName-dev
    resourceTableIdx: ${self:service}-TableIdx-${self:custom.resourceName}-Name-${self:provider.stage}
```
For this example, we'll create a RESTful API for a 'teams' resource.  (Note the use of plural and lowercase).  This means the service key we defined earlier will be set to 'teamsService'.

Be sure to put in your email address and your AWS region (e.g. `us-east-2`).

We have also defined a key-value branch called `dynamodb` (again, we could have called it anything, like `myNotSqlParameters`) with two child values: (1) the eventual name of our database "table"; and (2) the eventual name of our  "table"'s uniqueness index (we'll explain this later when discussion data storage).

You'll recall that ${self:service} = `teamsService` and ${self:custom.resourceName} = `teams` and the default stage is `dev` so:

```yaml
resourceTable: ${self:service}-Table-${self:custom.resourceName}-${self:provider.stage}
```
will become
```yaml
resourceTable: teamsService-Table-teams-dev
```
By including the stage (`dev`) in name of all deployed resource such as the table and index names, we ensure our development environment won't interfere with our production (`prod`) environment.

#### c) 'provider'
We are now going to add a few more keys to the provider section:
```yaml
provider:
  name: aws
  runtime: nodejs14.x
  lambdaHashingVersion: 20201221
  stage: dev  
  region: ${self:custom.region}

  apiGateway:
    # setting required for the transition from Serverless Framework 2.0 to 3.0+
    shouldStartNameWithService: true


  # Environment variables our Javascript code relies on
  environment:
    NODE_ENV: ${self:provider.stage}
    TABLE_NAME_TEAM: ${self:custom.dynamodb.resourceTable}
    TABLE_IDX_TEAM_NAME: ${self:custom.dynamodb.resourceTableIdx}

  # Base IAm role that the lambdas will execute with
  iam: ${file(./resources/aws/iam/LambdaRole.yml)}
```
To start, we now explicitly set the stage to `dev`, 
```yml
  stage: dev  
```
which you will recall is the default.  We can override this during deployment using command line parameters:
```shell
$ serverless deploy --stage prod
```
We next introduce for the first time the re-use of our custom parameters from higher in the file by setting `region` using the previously defined `${self.custom.region}`.  
```yml
  region: ${self:custom.region}
```
Again, this lets us keep most of our deployment configuration in one place under `custom`.

This is followed by another Serverless transition variable.  
```yml
apiGateway:
    shouldStartNameWithService: true
```
Starting in 3.0, the naming convention in the deployments will change from `{stage}-{service}` to `{service}-{stage}`.   We should just adopt this approach from the get-go.

Next we set environment variables that will be passed into the Node.js runtime environment.  This is a means for communicating our configuration to our javascript code
```yml
  environment:
    NODE_ENV: ${self:provider.stage}
    TABLE_NAME_TEAM: ${self:custom.dynamodb.resourceTable}
    TABLE_IDX_TEAM_NAME: ${self:custom.dynamodb.resourceTableIdx}
```
Our javascript code will be able to look these up by examining the runtime environment variables.  We pass: (1) the deployment stage; (2) our "table" name; and (3) our "table" uniqueness index name.

Finally, we add our first IAm configuration.  
```yml
  # Base IAm role that the lambdas will execute with
  iam: ${file(./resources/aws/iam/LambdaRole.yml)}
```
Notably, we do this by introducing the file import concept.  Serverless allows us to break individual portions of our base configuration file into discrete files.  Separating our configuration file into subfiles provides a number of benefits, including: (1) more granular version control; (2) smaller more readable files; and (3) a consistent, known location for finding settings in any given module.

We'll go over the contents of this file in a bit, but first let's stay focused on our `serverless.yml`.

#### d) `plugins`
We are next going to add a new top-level entry called `plugins`.  This key contains a `yaml` list of Serverless Framework extensions used by our project.  I generally try to keep these to a minimum due to inconsistency in the maintenance frequency of the various plugins.  The two I find absolutely essential are (a) `serverless-plugin-typescript`, which provides TypeScript support on top of Javascript; and (b) `serverless-offline`, which allows offline development of our lambda modules.
```yaml
# Serverless Framework plugins
plugins:
  # support for typescript
  - serverless-plugin-typescript

  # Allows us to use a local instance during development, eliminating need
  # to publish to cloud to see changes to functions and allowing use of a debugger
  - serverless-offline
```

#### e) `functions`
You'll recall this previously consisted of a structured key-value tree listing the base `hello` function.  We are going to replace this with a file import.

```yml
# define all of our REST microservice endpoints
functions: ${file(./resources/aws/RESTFunctions.yml)}
```

#### f) `package`
Another new top-level key.  This defines setting relating to the packaging of our stack for deployment.  The only value we will include is `excludeDevDependencies`, which will filter out of our deployment modules relating to our precompiler (Typescript) and our testing harness (Jest).  (Note that this value is `true` by default, so this value is functionally useless as set.)

```yml
# Settings relating to how our module will be bundled for execution
package:
  # Don't include node_modules like typescript and unit testing when
  # publishing our bundle to the cloud; results in notably smaller upload bundle
  excludeDevDependencies: true
```

#### g) `resources`
Finally, our last top level key.   Various catch-all resource creation happens here, including the definition of our "table".  (Later on, we are going to define our user authentication here as well but we'll skip this for now).

```yml
resources:
  # Values that we want to export out of this service for use in other services
  # Including an export name pollutes the global namespace and is only necessary
  # when you don't know this Stack's name, as you can otherwise obtain the value
  # using the key name you chose below, e.g. 'ApiGatewayRestApiExport'
  Outputs:
    ApiGatewayRestApiExport:
      Value: !Ref 'ApiGatewayRestApi'
#      Export:
#        Name: ${self:service}-${self:provider.stage}-api

  # The various resources can be given custom developer-friendly names, as shown below.
  # What the resource consists of is determined by the 'type' sub-property found in the files
  Resources:

    ############################################################
    # DATABASE RESOURCES
    ############################################################
    # Data table for this microserver
    ResourceTable:  ${file(./resources/aws/dynamodb/ResourceTable.yml)}
```
#####i) `Outputs`
Here we instruct AWS what values should be "exported" from our stack.  This is done in either of two ways: (1) we can make a value available in our AWS global namespace; or (2) we can make a value available as a parameter attached to our stack name.  I personally hate polluting the global namespace and see little value to it, so I take the second approach (but leave an example of the 1st approach commented out in the code).
```yml
  Outputs:
    ApiGatewayRestApiExport:
      Value: !Ref 'ApiGatewayRestApi'
```
Here, we define an export key named `ApiGatewayRestApiExport`--this could have been anything we wanted, such as `MyDogSpot`.  For the value, we for the first time use the `!Ref` command.  The !Ref command instructs AWS to inject the as-deployed unique name of our ApiGatewayRestAPI object.  

Now, if you've been paying attention, you might be saying right now "WHAT ApiGatewayRestApi object!??"  When deploying our stack, Serverless creates a resource in AWS's system named 'ApiGatewayRestApi' that routes requests to our lambda.  By exporting its value, we will be able to get a handle to it at a future stage.

#####i) `Resources`
Somewhat confusingly, the `resources` key has a child named `Resources`.
```yml
  Resources:

    ############################################################
    # DATABASE RESOURCES
    ############################################################
    # Data table for this microserver
    ResourceTable:  ${file(./resources/aws/dynamodb/ResourceTable.yml)}
```
As noted in the comments, each 'Resources' entry will be of a particular type as indicated by a `Type` key with a value such as `AWS::DynamoDB::Table`.  These would normally appear here, but because we are using file imports, they will instead be in our sub-files. 

With that, our final `serverless.yml` file looks like this for the time being:
```yml
# app and org for integration with dashboard.serverless.com
# Note that using this will cause Serverless to inject a logging handler proxy
# before the calls to your lambda functions, resulting in the renaming of your handler
# in the AWS interface
#org: <your_org_name_here>
#app: <your_dashboard_appname_here>

# name of our microservice
service: ${self:custom.resourceName}Service

frameworkVersion: '>=2.0'
variablesResolutionMode: 20210219

#############################################################################################
# CUSTOM VARIABLES - all variables necessary to get up and running should be in this section
#############################################################################################
custom:
  resourceName: teams
  # Email address of primary contact; embedded into internal "Tags" to aid in
  # internal maintenance
  primaryContact: <YOUR EMAIL ADDRESS>
  region: <YOUR AWS REGION>

  dynamodb:
    # For tables, I have adopted a naming convention of <SERVICE>-Table-<EntityName>-<Stage>; e.g. SportsApp-Table-Players-dev
    resourceTable: ${self:service}-Table-${self:custom.resourceName}-${self:provider.stage}
    # For indexes, I have adopted a naming convention of <SERVICE>-TableIdx-<EntityName>-<Constraint/SearchKey>-<Stage>; e.g. SportsApp-TableIdx-Players-LastName-dev
    resourceTableIdx: ${self:service}-TableIdx-${self:custom.resourceName}-Name-${self:provider.stage}

provider:
  name: aws
  # AWS claims they support NodeJS14, but as of March 2021, certain Node14 features don't appear to function correctly, e.g. nullish coalescing
  runtime: nodejs14.x
  lambdaHashingVersion: 20201221
  stage: dev  
  region: ${self:custom.region}
  
  apiGateway:
    # setting required for the transition from Serverless Framework 2.0 to 3.0+
    shouldStartNameWithService: true

    # we can have AWS API Gateway validate incoming requests prior to sending it to our lambda,
    # saving us the processing cost for malformed requests
    request:
      schemas:
        resource-create-model:
          name: ${self:custom.resourceName}CreateModel
          schema: ${file(src/schema/createResourceSchema.json)}
          description: "A Model validation for creating ${self:custom.resourceName}"

  # Environment variables our Javascript code relies on
  environment:
    NODE_ENV: ${self:provider.stage}
    TABLE_NAME_TEAM: ${self:custom.dynamodb.resourceTable}
    TABLE_IDX_TEAM_NAME: ${self:custom.dynamodb.resourceTableIdx}

  # Base IAm role that the lambdas will execute with
  iam: ${file(./resources/aws/iam/LambdaRole.yml)}

# Serverless Framework plugins
plugins:
  # support for typescript
  - serverless-plugin-typescript

  # Allows us to use a local instance during development, eliminating need
  # to publish to cloud to see changes to functions and allowing use of a debugger
  - serverless-offline

# define all of our REST microservice endpoints
functions: ${file(./resources/aws/RESTFunctions.yml)}

# Settings relating to how our module will be bundled for execution
package:
  # Don't include node_modules like typescript and unit testing when
  # publishing our bundle to the cloud; results in notably smaller upload bundle
  excludeDevDependencies: true

resources:
  # Values that we want to export out of this service for use in other services
  # Including an export name pollutes the global namespace and is only necessary
  # when you don't know this Stack's name, as you can otherwise obtain the value
  # using the key name you chose below, e.g. 'ApiGatewayRestApiExport'
  Outputs:
    ApiGatewayRestApiExport:
      Value: !Ref 'ApiGatewayRestApi'
#      Export:
#        Name: ${self:service}-${self:provider.stage}-api

  # The various resources can be given custom developer-friendly names, as shown below.
  # What the resource consists of is determined by the 'type' sub-property found in the files
  Resources:

    ############################################################
    # DATABASE RESOURCES
    ############################################################
    # Data table for this microserver
    ResourceTable:  ${file(./resources/aws/dynamodb/ResourceTable.yml)}



```
Before we go on to define the subfiles of our configuration, let's first talk about some of the other file types we will be dealing with.

##B. Dependencies / `Package.json`
As discussed in the previous post, `npm` uses a JSON-based `package.json` manifest file to track information about our project dependencies.  This file will be located in our root directory.  The key parts are as follows:
### 1) `name`, `version`, `description`
These are self-explanatory, being (a) the name of our application; (b) a version identifier; and (c) a textual description.
### 2) `main`
The primary entry point of our module.
### 3) `scripts`
Now things begin to get interesting.  `scripts` provides us a way to define a list of commands that we can run using the command `npm run <command>`.  For example, `npm run coverage` or `npm run deploy`.

### 4) `devDependencies`
A list of dependencies that are necessary during the compilation and testing phases of development, such as modules to support Typescript, Serverless Framework offline testing, and unit tests.

Although we can add modules to this list by hand, the normal method is through the command:

```shell
$ npm i --save-dev <module>
```
Note the use of the flag `--save-dev`.  This tells NPM that the module is a development dependency, rather than a runtime deployment dependency.  If you forget to use the flag, just go ahead and run the command again with the flag and NPM will move the module to the appropriate part of the configuration file.

NPM will install each of these modules in the "node_modules" subdirectory of our project.

I use the following as a baseline:
#### a) Serverless modules
The following adds Serverless Framework support, offline Serverless Framework testing and debugging, and Serverless Framework Typescript support.

```shell
$ npm i --save-dev serverless
$ npm i --save-dev serverless-offline
$ npm i --save-dev serverless-plugin-typescript
```
#### b) Typescript modules
The first of the following adds Typescript support.  The rest provide Typescript "definitions" for various Javascript modules that do not necessarily have Typescript-support built into them.

```shell
$ npm i --save-dev typescript
$ npm i --save-dev @types/aws-lambda
$ npm i --save-dev @types/jest
$ npm i --save-dev @types/node
$ npm i --save-dev @types/uuid
```

#### c) Jest Unit Testing modules
The first includes Jest as our unit tester.  The second adds dynamodb support to Jest.  The rest allow Jest to use the "babel" javascript compiler, along with babel support for environment variables and typescript.
```shell
$ npm i --save-dev jest
$ npm i --save-dev @shelf/jest-dynamodb

$ npm i --save-dev babel-jest
$ npm i --save-dev @babel/core
$ npm i --save-dev @babel/preset-env
$ npm i --save-dev @babel/preset-typescript
```

### 5) `dependencies`
A list of dependencies required by our module at runtime.  We ideally want to keep this list small.  We add items to this list by running the `npm i` command without the `--save-dev` flag. e.g.
```shell
$ npm i dynamoose
```
I include only dynamoose (a framework for interacting with DynamoDB) and uuid (a library for generating unique identifiers).
```shell
$ npm i dynamoose
$ npm i uuid
```

### 6) Final package.json file
Our package.json file ends up looking like:

```json
{
  "name": "teamMicroservice",
  "version": "1.0.0",
  "description": "This is the Team Microservice",
  "main": "index.js",
  "scripts": {
    "lint": "tslint -p tsconfig.json -c tslint.json",
    "local": "serverless offline",
    "deploy": "serverless deploy",
    "test": "jest",
    "coverage": "jest --coverage",
    "clean": "git clean -fXd -e \\!node_modules -e \\!node_modules/**/*"
  },
  "devDependencies": {
    "@babel/core": "^7.13.10",
    "@babel/preset-env": "^7.13.12",
    "@babel/preset-typescript": "^7.13.0",
    "@shelf/jest-dynamodb": "github:shelfio/jest-dynamodb",
    "@types/aws-lambda": "^8.10.51",
    "@types/jest": "^26.0.21",
    "@types/node": "^14.0.23",
    "@types/uuid": "^8.3.0",
    "babel-jest": "^26.6.3",
    "jest": "^26.6.3",
    "serverless": "^2.30.3",
    "serverless-offline": "^6.8.0",
    "serverless-plugin-typescript": "^1.1.9",
    "typescript": "^3.8.3"
  },
  "dependencies": {
    "dynamoose": "^2.7.3",
    "uuid": "^8.3.2"
  }
}
```

##C. `launch.json`
We'll next briefly touch on this file, which does not yet exist.  The `launch.json` file is used to configure the Visual Studio Code debugger.  Later on, we will define multiple debug configurations, including ones to launch (1) Serverless offline in the debugger; (2) all of our Jest unit tests; and (3) debugging our current Jest file.  For now, just know that it will exist and we will customize it when we get into our debugger configuration.

###D. jest.config.js
Jest is our unit test harness.  This file will contain its configuration settings.  To create it, we run:

```shell
$ jest --init
The following questions will help Jest to create a suitable configuration for your project

? Would you like to use Jest when running "test" script in "package.json"? › (Y/n) Y
? Would you like to use Typescript for the configuration file? › (y/N) N
? Choose the test environment that will be used for testing › - Use arrow-keys. Return to submit.
❯   node
    jsdom (browser-like)
? Do you want Jest to add coverage reports? (y/N) N
? Which provider should be used to instrument code for coverage? › - Use arrow-keys. Return to submit.
❯   v8
    babel
? Automatically clear mock calls and instances between every test? › (y/N) Y
  Modified <MYPATH>/MyRESTProject/package.json

  Configuration file created at <MYPATH>/MyRESTProject/jest.config.js
```
We'll configure it further later to add DynamoDB support.  For now, let's move on.

#II. Our Directory Structure
Like those of you coming from a Spring Maven world, I generally prefer a project hierarchy consisting of:
```shell
|____src
| |____main
| | |____resources
| | | | . . .
| | |____java
| | | | . . .
| |____test
| | | . . .
|____target
| | . . .
```
Although we will try to keep the source/resource directory distinction, we will be leaving behind the distinct test tree.  The reason behind this is that with a very concise microservice, we will be placing the unit test for each file in the same directory as the source.  Later on, we will introduce a separate `test` tree that will contain only our integration tests.

Our new directory structure will be akin to:
```shell
.
|____resources
| |____aws
| | |____dynamodb
| | | |____ResourceTable.yml
| | |____RESTFunctions.yml
| | |____iam
| | | |____LambdaRole.yml
|____src
| |____utils
| | |____Response.ts
| | |____rest
| | | |____responses
| | | | |____ . . . .
| | | |____exceptions
| | | | |____ . . . .
| |____schema
| | |____ . . . .
| |____model
| | |____Team.unit.test.ts
| | |____Team.ts
| |____aws
| | |____AWSRestController.ts
| | |____AWSTeamController.ts
| | |____AWSTeamController.unit.test.ts
| |____service
| | |____TeamService.unit.test.ts
| | |____TeamService.ts
```
##A. `src` Folder
We will store our various typescript files in a `src` folder.  I'm still somewhat wed to the model/view/controller structure, which influences my tree selection.  From our base project folder:

```shell
$ mkdir -p src/aws/
$ mkdir -p src/model/
$ mkdir -p src/schema/
$ mkdir -p src/service/
$ mkdir -p src/utils/rest/responses
$ mkdir -p src/utils/rest/exceptions
```
We'll discuss our various source files and their contents in the next post.  For now, let's finish our initial resources files.
##B. `resources` Folder
We will be storing our various `serverless.yml` configuration files in a `resources` folder.  Under this folder, we'll be creating subfolders for our various AWS components.  From our base project folder:

```shell
$ mkdir -p resources/aws/dynamodb
$ mkdir -p resources/aws/iam
```
###1. `ResourceTable.yml`
You'll recall that in our `serverless.yml` file, we declared our DynamoDB resource as being defined in the file `resources/aws/dynamodb/ResourceTable.yml`.  Let's go ahead and create this file and set its contents as:

```yml
Type: AWS::DynamoDB::Table
Properties:
  TableName: ${self:custom.dynamodb.resourceTable}
  ProvisionedThroughput:
    ReadCapacityUnits: 1
    WriteCapacityUnits: 1
  AttributeDefinitions:
    - AttributeName: id
      AttributeType: S
    - AttributeName: name
      AttributeType: S
    - AttributeName: sortField
      AttributeType: S
  KeySchema:
    - AttributeName: id
      KeyType: HASH
    - AttributeName: sortField
      KeyType: RANGE       
  LocalSecondaryIndexes:
    - IndexName: ${self:custom.dynamodb.resourceTableIdx}
      KeySchema:
          - AttributeName: id
            KeyType: HASH
          - AttributeName: name
            KeyType: RANGE
      Projection:
        ProjectionType: KEYS_ONLY
```
We'll go into detail on this later.  

###2. `LambdaRole.yml`
Next, we create our `resources/aws/iam/LambdaRole.yml` file:

```yml
# This is the permissions that our lambda functions need
role:
  # grant access to dynamoDB table and associated table index
  statements:
    - Effect: "Allow"
      Action:
        - "dynamodb:Query"
        - "dynamodb:PutItem"
        - "dynamodb:Scan"
        - "dynamodb:DeleteItem"
        - "dynamodb:UpdateItem"
      Resource:
        - !GetAtt ResourceTable.Arn
        - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${self:custom.dynamodb.resourceTable}/index/*    
```

###3. `RestFunctions.yml`
Followed by our `resources/aws/RestFunctions.yml`
```yml
# For example purposes, finding all entities is an unrestricted operation open to the entire world
findAll:
  handler: ./src/aws/AWSTeamController.findAll 
  events:
    - http:
        path: /${self:custom.resourceName}  
        method: get 
        cors: true
        # solely for creating a pretty label; sets OperationName in ApiGateway, useful for e.g. Swagger API documentation          
        operationId: getTeams 

# For example purposes, looking up the details on a single entity requires obtaining authorization from IAM
findOne:
  handler: ./src/aws/AWSTeamController.findOne
  events:
    - http:
        path: /${self:custom.resourceName}/{id}
        method: get
        cors: true
        # authorization provided by IAm, rather than a custom authorizer or the COGNITO user pool
        # even though there is a Cognito UserPool at the bottom of this all
        # this AWS_IAM approach allows the restrictions to be based on a IAm Policy
        # and we previously handed out policies through our Cognito Identity Pool based on a user's
        # highest priority UserPool group; This also avoids a lambda execution in order to run a custom authorizer
        # authorizer:
        #  type: AWS_IAM   

# Generally, deleting items should always require authorization
deleteOne:
  handler: ./src/aws/AWSTeamController.deleteOne
  events:
    - http:
        path: /${self:custom.resourceName}/{id}
        method: delete
        cors: true
        # Again we use a AWS_IAM authorizer and not a Cognito User Pool or custom authorizer
        #authorizer:
        #  type: AWS_IAM

# Generally, updating items should always require authorization
update:
  handler: ./src/aws/AWSTeamController.update
  events:
    - http:
        path: /${self:custom.resourceName}/{id}
        method: put
        cors: true
        #authorizer:
        #  type: AWS_IAM
        # Listing a schema here will have AWS validate the request prior to calling our 
        # lambda, saving us execution costs for invalid requests; note that the schema for this
        # extremely simplistic model is the same as for creates, so we re-use the create request schema        
        #request:
        #  schemas:
        #    application/json: resource-create-model
            
# Generally, creating items should always require authorization
create:
  handler: ./src/aws/AWSTeamController.create
  events:
    - http:
        path: /${self:custom.resourceName}
        method: post
        cors: true
        #authorizer:
        #  type: AWS_IAM
        # Listing a schema here will have AWS validate the request prior to calling our 
        # lambda, saving us execution costs for invalid requests        
        #request:
        #  schemas:
        #    application/json: resource-create-model
```
Note that we currently have our authorizers disabled, as well as our schema validators.  We'll cover those plus the other various parameters listed above later.  For now, its time to wrap up this post before it gets any longer!