# Serverless REST API on AWS Lambda

ဒီဆောင်းပါးမှာ မျှဝေချင်တာကတော့ NodeJs Serverless REST API တစ်ခုကို AWS services တွေပေါ်မှာ ဘယ်လို တည်ဆောက်မလဲ။ Local မှာ testing လုပ်ဖို့ environment ဘယ်လိုတွေဆောက်မလဲနဲ့၊ ဒီ setup ကို gitlab-ci ပေါ်ကနေ ဘယ်လို deploy လုပ်မလဲဆိုတာ တို့ပဲဖြစ်ပါတယ်။

Serverless သုံးရင် ရလာမဲ့ အားသာချက်တွေက ဘာတွေလဲ။

* Demand ပေါ်မူတည်ပြီး auto scale ဖြစ်နေတဲ့အတွက် server down မှာ စဉ်းစားဖို့ မလိုပါဘူး။
    
* အသုံးပြုတဲ့ မီလီစက္ကန့်အလိုက်ပဲ ပေးရတာဖြစ်တဲ့အတွက် traffic အမြဲ မရှိမနေတဲ့ api တွေအတွက်ဆိုရင် running cost ကို အများကြီး လျှော့ချပေးနိုင်ပါတယ်။
    
* servers တွေကို maintain လုပ်ရတဲ့ workload ကို လျှော့ချပေးနိုင်ပါတယ်။
    

ဘယ်လိုအခြေနေတွေမှာ serverless မသုံးသင့်ဘူးလဲ?

* ကိုယ့် application traffic က stable ဖြစ်တယ် (သို့) ကိုယ့်ရဲ့ DevOps team က traffice ပေါ်မူတည်ပြီး auto scale ကို သေချာ ကုန်ကျစရိတ် သက်သာအောင် ချိန်ဆနိုင်တယ်၊ server maintainance ကို ကောင်းကောင်း ပိုင်နိုင်တယ်။
    
* ကိုယ့် application requirement က ရှုပ်ထွေးပြီး serverless သုံးဖို့ အဆင်မပြေနိုင်ဘူး။
    
* ကိုယ့် application က server resource သိပ်မသုံးပဲ တစ်ခြားက api တွေရဲ့ response ကို စောင့်နေရဲ့ အချိန်တွေက များနေတယ်။
    

စတဲ့ အခြေတွေမှာဆိုရင်တော့ serverless မသုံးသင့်ပါဘူး။

### အသုံးပြုသွားမဲ့ AWS Services တွေကတော့

* Lambda
    
* API Gateway
    
* DynamoDB
    
* IAM
    
* S3
    

တို့ပဲ ဖြစ်ပါတယ်။

### Local setup

ExpressJS နဲ့ Serverless Framework ကို သုံးမှာ ဖြစ်ပြီး Local မှာစမ်းဖို့ DynamoDB Local အသုံးပြုမှာဖြစ်တဲ့အတွက် Node, npm နဲ့ Java Runtime Environment (JRE) တို့ကို install ထားဖို့ လိုအပ်ပါတယ်။

```basic
❯ node -v
v18.16.0
❯ npm -v
9.5.1
❯ java --version
openjdk 11.0.19 2023-04-18
```

ကျွန်တော့စက်မှာတော့ node 18, npm 9 နဲ့ jre 11 တို့ထည့်ထားပါတယ်။ ဒီဆောင်းပါးကို ဖတ်နေသူတွေဟာ ဒါတွေဘယ်လိုထည့်ရမလဲ သိပြီးသားဖြစ်တယ်လို့ ယူဆတဲ့အတွက် အသေးစိတ်မပြောတော့ပါဘူး။ ကိုယ့်စက်ထဲမှာ လိုနေတာတွေ ထည့်ပြီးပြီဆိုရင် အရင်ဆုံး serverless install လုပ်ပါမယ်။

```bash
npm install -g serverless
```

serverless ထည့်ပြီးပြီဆိုရင်တော့ serverless ကိုသုံးပြီး ကျွန်တော်တို့ project create လုပ်လို့ရပါပြီ။ serverless project တစ်ခု ဆောက်ဖို့ ရိုက်ရတဲ့ command က တစ်ခုထဲလိုပါတယ်။ project ထားမဲ့ parent dir ထဲမှာ `serverless` (သို့) `sls` လို့ ရိုက်လိုက်ရုံပါပဲ။

```basic
❯ sls

Creating a new serverless project
? What do you want to make?
  AWS - Node.js - Starter
  AWS - Node.js - HTTP API
  AWS - Node.js - Scheduled Task
  AWS - Node.js - SQS Worker
  AWS - Node.js - Express API
❯ AWS - Node.js - Express API with DynamoDB
  AWS - Python - Starter
  AWS - Python - HTTP API
  AWS - Python - Scheduled Task
  AWS - Python - SQS Worker
  AWS - Python - Flask API
  AWS - Python - Flask API with DynamoDB
  Other
```

ပေါ်လာထဲ့ prompt ထဲကနေ AWS - Node.js - Express API with DynamoDB ကိုရွေးပြီး enter နှိပ်ပါ။ project name ပေးပါ။ ကျွန်တော်ကတော့ project name ကို `kalaung` လို့ ပေးလိုက်ပါတယ်။ `Do you want to deploy now?` လို့ မေးလာရင် no ပါ။

```basic
❯ sls

Creating a new serverless project

? What do you want to make? AWS - Node.js - Express API with DynamoDB
? What do you want to call this project? my-api

✔ Project successfully created in kalaung folder

? Do you want to deploy now? No

What next?
Run these commands in the project directory:

serverless deploy    Deploy changes
serverless info      View deployed endpoints and resources
serverless invoke    Invoke deployed functions
serverless --help    Discover more commands
```

project create လုပ်ပြီးပြီဆိုရင် ကျွန်တော်တို့ project dir ထဲ ဝင်ပြီး local မှာ offline စမ်းလို့ရအောင် လိုအပ်တဲ့ plugin တွေ ထည့်ပါမယ်။ ဘာတွေထည့်ရမလဲဆိုတော့ အောက်က command အတိုင်းသာ ကြည့်ရိုက်သွားလိုက်ပါ။

```bash
cd kalaung
sls plugin install -n serverless-dynamodb-local
sls plugin install -n serverless-offline
npm i @aws-sdk/client-dynamodb --save-dev
npm i @aws-sdk/lib-dynamodb --save-dev
sls dynamodb install
```

ပုံမှန်ဆိုရင်တော့ အကုန်လုံး အဆင်ပြေပြေပြီးသွားသင့်ပေမယ့် နောက်ဆုံး `sls dynamodb install` မှာ `Error: Error getting DynamoDb local latest tar.gz location undefined: 403` ဆိုပြီး error တက်ပါလိမ့်မယ်။ AWS ဘက်က `DynamoDb local` url ကို `http` ကနေ `https` ချိန်းလိုက်လို့ ဖြစ်နေတဲ့ error ပါ။ သူ့ package က အလုပ်မလုပ်ရင် ကျွန်တော်တို့ manual လုပ်ကြတာပေါ့ ခက်ခက်ခဲခဲတော့ မဟုတ်ပါဘူး အောက်က command လေးကို run ပေးလိုက်ပါ လိုအပ်တဲ့ file ကို ဒေါင်းပြီး extract လုပ်ပေးသွားပါလိမ့်မယ်။

```bash
mkdir -p .dynamodb && curl https://s3-us-west-2.amazonaws.com/dynamodb-local/dynamodb_local_latest.tar.gz | tar -xz -C .dynamodb
```

ဒါဆိုရင်တော့ လိုအပ်တဲ့ package တွေ စုံသွားပါပြီ ကျွန်တော်တို့ code စရေးကြရအောင်။

အရင်ဆုံး .gitignore file ကို ပြင်ပါမယ်။ `.gitignore` file ရဲ့ အောက်ဆုံးမှာ `.dynamodb` ဆိုပြီး တစ်ကြောင်း ထပ်ထည့်ပေးပါ။

ပြီးရင် serverless.yml ကိုပြင်ပါမယ်။

```yaml
custom:
  tableName: 'users-table-${sls:stage}'
```

အပေါ်က custom: &gt; tableName: ဆိုတဲ့ code နေရာမှာ အောက်က code တွေနဲ့ အစားထိုးလိုက်ပါ။

```yaml
custom:
  tableName: '${self:service}-${sls:stage}-users-table'
  dynamodb:
    start:
      migrate: true
    stages:
      - dev

package:
    excludeDevDependencies: true
    patterns:
      - '!.dynamodb/**'
      - '!README.md'
```

tableName ကို သူပေးတဲ့ ပုံစံအတိုင်းမထားပဲ service name နဲ့ stage ကို prefix ပေးလိုက်တာက IAM Permission တွေပေးတဲ့နေရာမှာ resource filter ပိုင်း ပိုအဆင်ပြေအောင်လို့ပါ။ `dynamodb:` ဆိုတဲ့ block ကတော့ DynamoDB ကို offline မှာသုံးဖို့နဲ့ package build တဲ့အခါမှာ မလိုတဲ့ ဖိုင်တွေ ချန်ခဲ့ဖို့ ဖြစ်ပါတယ်။

ပြီးရင် `index.js` file ကိုပြင်ပါမယ်။

```javascript
const client = new DynamoDBClient();
```

အပေါ်က code နေရာမှာ အောက်က code တွေနဲ့ အစားထိုးပါမယ်။ offline စမ်းတဲ့အခါမှာ AWS ရဲ့ DynamoDB service ကို သွား connect မလုပ်ပဲ localhost မှာ port 8000 နဲ့ serve မဲ့ DynamoDB local ပဲသုံးဖို့အတွက်ပါ။

```javascript
const dynamoDbClientParams = {};
if (process.env.IS_OFFLINE) {
  dynamoDbClientParams.region = 'localhost'
  dynamoDbClientParams.endpoint = 'http://localhost:8000'
}
const client = new DynamoDBClient(dynamoDbClientParams);
```

`app.use(express.json());` ရဲ့ အောက်မှာ `app.get("/". ......` ဆိုပြီး အောက်က code တွေ ထပ်ထည့်ပါမယ်။

```javascript
app.use(express.json());

app.get("/", function (req, res) {
  res.json({hello: "KalaungTech"});
});
```

အကုန်ပြင်ပြီးရင်တော့ local မှာ စမ်းကြည့်ဖို့အတွက် `sls offline start` ဆိုပြီး run ကြည့်လိုက်လို့ရပါပြီ။ DynamoDB local က port 8000 နဲ့ run သွားပြီး api server ကတော့ port 3000 နဲ့ run သွားပါလိမ့်မယ်။

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1687284216432/a15e623d-2f5f-4132-85da-10a89ead8654.png align="center")

ကျွန်တော်တို့ api ကို curl နဲ့ စမ်းကြည့်လို့ရပါပြီ။

```basic
❯ curl --request GET 'http://localhost:3000/'
{"hello":"KalaungTech"}%
❯ curl --request POST 'http://localhost:3000/users' --header 'Content-Type: application/json' --data-raw '{"name": "John", "userId": "1"}'
{"userId":"1","name":"John"}%
❯ curl --request GET http://localhost:3000/users/1
{"userId":"1","name":"John"}%
❯
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1687247391781/bc6612a9-1c9a-429e-bf7f-603b7f75faf8.png align="center")

Request တစ်ခုခြင်းဆီအတွက် ကျသင့်တဲ့ Billed Duration ကိုပါပြပေးတဲ့အတွက် ကုန်ကျစရိတ်ကို ချိန်ဆဖို့အတွက်နဲ့ code ကို optimize လုပ်ဖို့လည်း တော်တော် အသုံးဝင်ပါတယ်။

ကျွန်တော်တို့ စမ်းလို့ အဆင်ပြေပြီဆိုရင် aws ပေါ်ကို deploy ပါမယ်။

## Creating AWS IAM User

ကျွန်တော်တို့ deploy လုပ်ဖို့ AWS user create မလုပ်ခင် Permission အတွက် IAM Policy တစ်ခု အရင်ရေးပါမယ်။ အောက်က policy ကို ကော်ပီကူးပြီး IAM policy အသစ် တစ်ခု ပြုလုပ်ပါ။ Policy Name ကို ကျွန်တော်ကတော့ `ServerlessDeployPermission` လို့ ပေးလိုက်ပါတယ်။ ဒီ policy မှာ တစ်ခု သတိထားရမှာက Resource တွေထဲမှာ `kalaung-prod` ဆိုတဲ့ prefix ကို သုံးထားပါတယ်။ kalaung က service name ကို ယူထားတာဖြစ်ပြီး prod ကတော့ deploy လုပ်တဲ့ stage ပါ။ ကိုယ်သုံးမဲ့ service name နဲ့ stage ကို ပြင်ပြီးတော့ ဒီ policy ကို အသုံးပြုရမှာ ဖြစ်ပါတယ်။ ဥပမာ ကိုယ့် serverless.yml ထဲက service name က myapi လို့ ပေးထားပြီး dev stage နဲ့ deploy မှာဆိုရင် `kalaung-prod` ဆိုတဲ့ နေရာတွေမှာ `myapi-dev` နဲ့ ပြောင်းပြီးမှ policy ကို save ရမှာ ဖြစ်ပါတယ်။

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ServerlessUpdatePolicies",
            "Effect": "Allow",
            "Action": [
                "cloudformation:List*",
                "cloudformation:Describe*",
                "cloudformation:UpdateStack",
                "cloudformation:CreateChangeSet",
                "cloudformation:DeleteChangeSet",
                "cloudformation:ExecuteChangeSet",
                "lambda:*",
                "logs:Describe*",
                "logs:TagResource",
                "logs:CreateLogGroup",
                "logs:DeleteLogGroup",
                "iam:GetRole",
                "iam:TagRole",
                "iam:PassRole",
                "iam:CreateRole",
                "iam:PutRolePolicy",
                "iam:getRolePolicy",
                "iam:DeleteRolePolicy",
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket",
                "s3:DeleteObject",
                "s3:PutBucketPolicy",
                "s3:GetBucketPolicy",
                "s3:GetBucketPolicyStatus",
                "s3:DeleteBucketPolicy",
                "s3:GetEncryptionConfiguration",
                "s3:PutEncryptionConfiguration",
                "dynamodb:CreateTable",
                "dynamodb:DescribeTable",
                "dynamodb:DeleteTable"
            ],
            "Resource": [
                "arn:aws:s3:::kalaung-prod-*",
                "arn:aws:iam::*:role/kalaung-prod-*",
                "arn:aws:dynamodb:*:*:table/kalaung-prod-*",
                "arn:aws:lambda:*:*:function:kalaung-prod-*",
                "arn:aws:cloudformation:*:*:stack/kalaung-prod",
                "arn:aws:cloudformation:*:*:stack/kalaung-prod/*",
                "arn:aws:logs:*:*:log-group:/aws/lambda/kalaung-prod-*"
            ]
        },
        {
            "Sid": "ServerlessExtraPolicies",
            "Effect": "Allow",
            "Action": [
                "s3:CreateBucket",
                "lambda:GetFunction",
                "cloudformation:GetTemplate",
                "cloudformation:ValidateTemplate"
            ],
            "Resource": "*"
        },
        {
            "Sid": "ServerlessApiPolicies",
            "Effect": "Allow",
            "Action": [
                "apigateway:*"
            ],
            "Resource": [
                "arn:aws:apigateway:*::/apis",
                "arn:aws:apigateway:*::/apis/*",
                "arn:aws:apigateway:*::/apikeys/*",
                "arn:aws:apigateway:*::/tags/*"
            ]
        }
    ]
}
```

Policy ရေးပြီးသွားရင် IAM user အသစ် create လုပ်ပါ permission မှာ အခုရေးထားတဲ့ policy `ServerlessDeployPermission` ကို attach တွဲပေးလိုက်ပါ။ User Name ကို ကျွန်တော်ကတော့ `ServerlessDeployUser` လို့ ပေးလိုက်ပါတယ်။

User account ဆောက်ပြီးသွားရင် `ServerlessDeployUser` User account ရဲ့ Security credentials ထဲ သွားပြီးတော့ Access Key ထုတ်ပါ။

Access key ရလာပြီဆိုရင် serverless မှာ သုံးဖို့ configure လုပ်ပါမယ်။ အောက်က command မှာ ကိုယ့်ရဲ့ accessKey, secretKey အစားထိုးပြီး run လိုက်ပါ။

```bash
serverless config credentials \
  -o --provider aws \
  --key YOUR_ACCESS_KEY \
  --secret YOUR_SECRET_VALUE
```

credential ထည့်ပြီးပြီဆိုရင်တော့ ကျွန်တော်တို့ ရဲ့ api ကို AWS ပေါ် deploy လို့ရပါပြီ။ `serverless deploy --region ap-southeast-1 --stage prod` လို့ ရိုက်ပြီး deploy လိုက်ပါ။

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1687286115647/136585aa-4249-4b33-8503-add5ff9f5723.png align="center")

ပုံထဲကလို ပေါ်လာရင်တော့ deploy လုပ်တာ အောင်မြင်သွားပါပြီ။ ထွက်လာတဲ့ endpoint မှာ ကိုယ့် ရဲ့ api အလုပ် လုပ်နေပါလိမ့်မယ်။

## GitLab CI

ကျွန်တော်တို့ Local မှာ စမ်းလို့လည်းရပြီ deploy လို့လည်း ရပြီဆိုတော့ gitlab ပေါ်တင်ပြီး CI/CD pipeline run ဖို့အတွက် `.gitlab-ci.yml` ဖိုင် create လုပ်ပါမယ်။

```yaml
# .gitlab-ci.yml
image: node:18-alpine

before_script:
  - npm install -g serverless

stages:
  - build
  - deploy

build-production:
    when: manual
    stage: build
    script:
        - npm install --legacy-peer-deps --progress=false 
        - sls package --region ${AWS_REGION} --stage prod  
    artifacts:
      paths:
        - .serverless
        - node_modules
    only:
        - master

deploy-production:
  when: on_success
  stage: deploy
  needs: ["build-production"]
  script:
      - sls deploy --region ${AWS_REGION} --stage prod 
  only:
      - master
```

GitlabCI file create လုပ်ပြီးပြီဆိုရင် gitlab မှာ repo ဆောက်ပါမယ်။ ပြီးရင် repo အသစ်ရဲ့ CI/CD setting ထဲကိုသွားပြီး Variables ထဲမှာ အောက်က variables သုံးခုကို ထည့်ပေးပါ။

```basic
Key: AWS_ACCESS_KEY_ID
Value: YOUR_ACCESS_KEY
Protected: ✓

Key: AWS_SECRET_ACCESS_KEY
Value: YOUR_SECRET_VALUE
Protected: ✓

Key: AWS_REGION
Value: ap-southeast-1
```

Gitlab repo ထဲမှာ variable တွေ ထည့်ပြီးပြီဆိုရင်တော့ ကျွန်တော်တို့ရဲ့ project ကို repo အသစ်ပေါ်တင်ပြီး CI/CD pipeline ကို run ကြည့်လို့ရပါပြီ။

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1687288395462/9ad7931e-9232-4a47-8544-1ccbb167f594.png align="center")

Pipeline result လေး pass ဖြစ်သွားပြီဆိုရင်တော့ ပြီးပါပြီ။

ကိုယ့်ရဲ့ api ကို ကိုယ်ပိုင် domain နဲ့ သုံးချင်တယ်ဆိုရင်တော့ ApiGateway dashboard ထဲက [**Custom domain names**](https://ap-southeast-1.console.aws.amazon.com/apigateway/main/publish/domain-names?region=ap-southeast-1) မှာသွားပြီး manual ထည့်ပေးလို့ရပါတယ်။

RepoUrl: [https://gitlab.com/thixpin/serverlessapi/](https://gitlab.com/thixpin/serverlessapi/)
