# Build and Deploy REST API on AWS

If building and deploying REST APIs on on-premise servers is not strange to you and you are curious about how to do it in a serverless fashion, you are at the right place. This guide will show you how to build and deploy a simple REST API on AWS. There are multiple ways of achieving this goal, but in this guide, we will go through by using ***AWS Console*** clicking buttons and also by utilizing ***infrastructure as a code*** concept using the ***CloudFormation*** template. 

### Prerequisites
- AWS Account
- AWS CLI installed

### Intro

Just like the name ***Amazon Web Services*** itself suggests, the concept of serverless computing on AWS consists of using services created and managed by ***Amazon***. There are more than 200 services available and for the purpose of this guide we will use only two of them, ***Amazon API Gateway*** and ***Amazon Lambda***. A list of all available services can be found [here](https://www.eckher.com/c/21gjdl7gz4).

* **Amazon API Gateway** - fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale. ([Ref.](https://aws.amazon.com/api-gateway/))
* **Amazon Lambda** - serverless, event-driven compute service that lets you run code for virtually any type of application or backend service without provisioning or managing servers. ([Ref.](https://aws.amazon.com/lambda/))

The REST API we are going to build is the so-called "what's my IP" and it will consist of one simple **GET** endpoint that returns the current user IP address in a JSON format. Let's get started.


### 1. Deploy using AWS Console
**Steps**

1. In the AWS console navigate ***API Gateway > Create API > REST API > Build***.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1644746734389/426bbyHaq.png)

2. Select the ***New API*** option, fill in the name as shown on the screenshot below, and click on the ***Create API*** button.


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1644747615652/Z65pU08uu.png)

Endpoint type ***Regional*** means that the API will be deployed in the current account region. The current region can be found in the top right corner of the AWS console. It's out of the scope of this guide so I won't get into much more details. More about regions can be found [here](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html).

Now when we have an API created, navigate ***Actions > Create Resource***. 
The resource represents an API endpoint. We are going to set the name and path as ***ip***.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1644749196012/4nAU_uda-B.png)

To be able to access the created resource we need to assign a method to it. In our case, it will be a **GET** method and we can do it by navigating ***Actions > Create Method***.


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1644749703711/hTHNxEM2c.png)

Selecting the newly created GET method you can see various integration type options. In order to write our own logic on the backend side of an endpoint, we are going to use the ***Lambda Function*** integration type. Set the configuration as shown in the screenshot down below.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1644749997409/w--Yt1xY3.png)

Before connecting Lambda Function and filling in the ***Lambda function name*** field, we first need to create one, so let's do that. 

Navigate ***Lambda > Create function*** and select Python 3.9 Runtime. The name is provisional but the best practice is to set it as something descriptive of an endpoint we are going to be connecting to. To create a function click on the ***Create function***  button.


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1650398960739/KjOPxUDGm.png)
After the function has been created we can start writing our own code. Replace the given example with the following code snippet and then deploy the changes by clicking the ***Deploy*** button above the code section.

```python
import json


def lambda_handler(event, context):
    ip_address = event['requestContext']['identity']['sourceIp']
    return {
        'body': json.dumps({"ip_address": ip_address}),
        'headers': {
            'Content-Type': 'application/json'
        },
        'statusCode': 200
    }

``` 
In the given code we get an IP from the event variable and then we return it as a body parameter.
In order to use this newly created Lambda function, we need to connect it to our endpoint. Go back to API Gateway resources and fill in the lambda function field as shown in the screenshot below and click on the ***Save*** button.


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1650400523668/AJd74iSQS.png)

We are almost finished, in order to use an API we need to deploy it by navigating ***Actions > Deploy API***.
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1650400094987/5fozJB3NR.png)

Create a new stage and press ***Deploy***.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1650400284145/jkCk3rxlW.png)

You should now be able to invoke the API using the url provided in ***Stages > dev > ip > GET*** path.
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1650616671815/OwOaX11pt.png)
Output should be in JSON format as follows:
```json
{"ip_address": "223.91.73.113"}
``` 
### 2. Deploy using AWS CloudFormation

During the Console process at one point, you probably thought 

> this should be easier and done programmatically 

and you were right. There is a way of achieving the same thing by utilizing the ***infrastructure as a code*** concept using the ***CloudFormation*** service.


* **AWS Cloudformation ** - a service that gives developers and businesses an easy way to create a collection of related AWS and third-party resources, and provision and manage them in an orderly and predictable fashion. ([Ref.](https://aws.amazon.com/cloudformation/faqs/#:~:text=AWS%20CloudFormation%20is%20a%20service,an%20orderly%20and%20predictable%20fashion.))

Infrastructure as a code is a concept of managing infrastructure using some sort of configuration files. In our case that would be managing AWS resources using the ***CloudFormation*** template in YAML format instead of clicking around the console. To achieve such a thing we first need to understand how to construct mentioned template. The structure of the CloudFormation template looks as follows:

~~~yaml
---
AWSTemplateFormatVersion: "version date"

Description:
  String

Metadata:
  template metadata

Parameters:
  set of parameters

Rules:
  set of rules

Mappings:
  set of mappings

Conditions:
  set of conditions

Transform:
  set of transforms

Resources:
  set of resources

Outputs:
  set of outputs
~~~

We are going to stick with the ***Resources*** section only. Full template anatomy can be found on [this](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-anatomy.html) URL.

The ***Resources*** section correlates to the resources we clicked on in the console. For example, API Gateways REST API would be a resource, and endpoint configuration would be its property, Lambda function would be a resource and its property would be a code, and so on. A complete replica of REST API made using console looks as follows:

~~~yaml
AWSTemplateFormatVersion: '2010-09-09'

Description: REST API using AWS API Gateway with a Lambda Integration

Resources:

  SimpleRestAPI:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Description: REST API that gets users IP
      EndpointConfiguration:
        Types:
          - REGIONAL
      Name: whats-my-ip-cf

  ApiGatewayResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId: !GetAtt SimpleRestAPI.RootResourceId
      PathPart: 'ip'
      RestApiId: !Ref SimpleRestAPI

  ApiGatewayMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: GET
      AuthorizationType: NONE
      Integration:
        Credentials: !GetAtt ApiGatewayIamRole.Arn
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations'
      ResourceId: !Ref ApiGatewayResource
      RestApiId: !Ref SimpleRestAPI

  ApiGatewayModel:
    Type: AWS::ApiGateway::Model
    Properties:
      ContentType: 'application/json'
      RestApiId: !Ref SimpleRestAPI
      Schema: {}

  ApiGatewayStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId: !Ref ApiGatewayDeployment
      Description: REST API dev stage
      RestApiId: !Ref SimpleRestAPI
      StageName: 'dev'

  ApiGatewayDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn: ApiGatewayMethod
    Properties:
      Description: Lambda API Deployment
      RestApiId: !Ref SimpleRestAPI

  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import json
          def lambda_handler(event, context):
              ip_address = event['requestContext']['identity']['sourceIp']
              return {
                  'body': json.dumps({"ip_address": ip_address}),
                  'headers': {
                      'Content-Type': 'application/json'
                  },
                  'statusCode': 200
              }

      Description: AWS Lambda function
      FunctionName: 'get_ip'
      Handler: index.lambda_handler
      MemorySize: 256
      Role: !GetAtt LambdaIamRole.Arn
      Runtime: python3.9
      Timeout: 60

  ApiGatewayIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: ''
            Effect: 'Allow'
            Principal:
              Service:
                - 'apigateway.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      Policies:
        - PolicyName: LambdaAccessPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: 'Allow'
                Action: 'lambda:*'
                Resource: !GetAtt LambdaFunction.Arn

  LambdaIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'lambda.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
~~~

In order to see the end result of the given template, we need to deploy it. To do so we are going to use AWS CLI and run the following command:

~~~bash
aws cloudformation deploy \
  --template template.yml \
  --stack-name restapi-cloudformation --capabilities CAPABILITY_IAM
~~~

After running the command in the console under the ***CloudFormation*** services tab we should see a newely created CloudFormation stack as shown in the screenshot below.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1650827378351/9d6fe__9X.png)

Congratulations, you have successfully created an API using AWS CloudFormation. You can invoke it the same way as described before.

Thank you for reading.
