# 使用AWS輕鬆打造RESTful API吧!
## 前言
對於一個沒有接觸過網頁後端,但想要接觸或瞭解後端的人,搜尋網頁後端的資料可能會出現從語言選擇、框架選擇、環境建置、資料庫選擇、伺服器作業系統到部署等非常多的資訊,這對於一個新手來說是非常容易卻步的。
所以寫這篇主要的目的,是希望提供給初步想接觸網頁後端,卻不知道從哪下手的人一個簡單快速產生一份屬於自己 API 的方式,來初步體驗後端,而上一段提到的基礎們日後還是需要慢慢去了解。
今天會使用到 AWS 提供的 API Gateway、Lambda、DynamoDB,來完成簡單的 TodoList API。
由於本篇主軸是透過 AWS 提供的 Service 來快速產生RESTful API,關於每一個 Service 詳細的介紹日後會再各自出介紹文章。
## 1. 建立 DynamoDB 資料表
首先我們進到 DynamoDB 的服務中,
create table,並且在 Table name 中輸入 `todo`,並在[Partition key](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey)中輸入 `id`,接著點選 ```create table``` 完成資料表建立。

建立完成後,就會發現 **Tables** 中有剛剛建立的資料表,我們進入資料表中並點選 ```Explore table Items```,可以看到資料表目前是空的,下一步我們就要透過 Lambda 來操作資料表。

## 2. 建立 Lambda function
上一節中,我們已經完成初步的 DB 建置了,接著我們進到 Lambda 服務中,並且點選 ```create function```。
接著我們透過以下步驟做初始化設定:
1. **選擇模板**:我們可以看到 Lambda 提供從頭開始寫也有簡易的模板,在這邊我們選從頭開始撰寫
2. **輸入 function 名稱**:在 Function name 輸入 `createTodo`
3. **選擇語言**:Lambda有提供多種語言可以選擇,可以根據自己喜好或需求做選擇,本篇將使用Node18.x 來做示範

4. **為 function 建立 role**:因為我們要操作 DynamoDB,前面我們也沒建立過 role,所以這邊我們要建立一個擁有操作 DynameDB 權限的 role :
1. 點開 **Change default execution role**
2. 選擇 **Create a new role from AWS policy templates**
3. Role name中輸入`DynamoDBUser`
4. Policy templates 中選擇 **Simple microservice permissions**及**Test harness permissions**
5. **點選 create function**
接著會進到 function 裡面,並看到基礎的程式碼,代表 lambda function 成功建立了,我們重複步驟把`listTodos`、`updateTodo`、`removeTodo`也建立起來。

## 3. 撰寫並發佈 Lambda function
上一章節我們已經建立好我們需要使用到的 function 了,這章節我們將撰寫並發布我的的API function,大家可以試著自己寫看看,如果只是想體驗建立到發布的人可以直接複製程式碼。
### createTodo
本篇為簡化教學,使用建立時的 timestamp 作為 id,正式開發可使用 [uuid](https://www.npmjs.com/package/uuid) 等套件建立 id 。
```javascript=
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
DynamoDBDocumentClient,
PutCommand
}
from "@aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({});
const dynamo = DynamoDBDocumentClient.from(client);
const tableName = "todo";
export const handler = async (event, context) => {
let body;
let statusCode = 200;
const headers = {
"Content-Type": "application/json",
};
try {
const { name } = JSON.parse(event.body)
// verify the request body.
if (!name) throw new Error(`Invalid input: missing key 'name'`)
// createTodo.
const param = {
id: `${new Date().getTime()}`,
name,
completed: false,
__typename: `todo`,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
await dynamo.send(new PutCommand({ TableName: tableName, Item: param, }));
const result = { success: true, message: `OK`, value: param }
body = result
}
catch (err) {
statusCode = 400;
body = err.message ?? err;
}
finally {
body = JSON.stringify(body);
}
return {
statusCode,
body,
headers,
};
};
```
### listTodos
```javascript=
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
DynamoDBDocumentClient,
ScanCommand,
}
from "@aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({});
const dynamo = DynamoDBDocumentClient.from(client);
const tableName = "todo";
export const handler = async (event, context) => {
let body;
let statusCode = 200;
const headers = {
"Content-Type": "application/json",
};
try {
// getTodos.
const { Items } = await dynamo.send(
new ScanCommand({ TableName: tableName })
);
const result = { success: true, message: `OK`, value: Items }
body = result
}
catch (err) {
statusCode = 400;
body = err.message ?? err;
}
finally {
body = JSON.stringify(body);
}
return {
statusCode,
body,
headers,
};
};
```
### updateTodo
```javascript=
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
DynamoDBDocumentClient,
PutCommand,
GetCommand
}
from "@aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({});
const dynamo = DynamoDBDocumentClient.from(client);
const tableName = "todo";
export const handler = async (event, context) => {
let body;
let statusCode = 200;
const headers = {
"Content-Type": "application/json",
};
try {
const { id, name, completed } = JSON.parse(event.body)
// verify the request body.
if (!id) throw new Error(`Invalid input: missing key 'id'`)
if (!name) throw new Error(`Invalid input: missing key 'name'`)
if (!completed) throw new Error(`Invalid input: missing key 'completed'`)
// get old todoInfo.
const { Item } = await dynamo.send(new GetCommand({ TableName: tableName, Key: { id } }))
// updateTodo.
const param = {
...Item,
name,
completed,
updatedAt: new Date().toISOString()
}
await dynamo.send(new PutCommand({TableName: tableName,Item: param,}));
const result = { success: true, message: `OK`, value: param }
body = result
}
catch (err) {
statusCode = 400;
body = err.message ?? err;
}
finally {
body = JSON.stringify(body);
}
return {
statusCode,
body,
headers,
};
};
```
### removeTodo
```javascript=
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
DynamoDBDocumentClient,
DeleteCommand
}
from "@aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({});
const dynamo = DynamoDBDocumentClient.from(client);
const tableName = "todo";
export const handler = async (event, context) => {
let body;
let statusCode = 200;
const headers = {
"Content-Type": "application/json",
};
try {
const { id } = event.pathParameters
// removeTodo.
await dynamo.send(new DeleteCommand({ TableName: tableName, Key: { id } }));
const result = { success: true, message: `OK` }
body = result
}
catch (err) {
statusCode = 400;
body = err.message ?? err;
}
finally {
body = JSON.stringify(body);
}
return {
statusCode,
body,
headers,
};
};
```
## 4. 建立 API Gateway
到這邊我們已經寫完 lambda 了,現在我們要透過 API Gateway 接收 request 並proxy 到 lambda。
首先,我們打開 API Gateway,可以看到多種 API 可以選擇,可以根據需要選擇你的 API type,本篇選擇HTTP API。
參考資料:[在REST API 與 HTTP API 之間選擇](https://docs.aws.amazon.com/zh_tw/apigateway/latest/developerguide/http-api-vs-rest.html)
### 如何建立API
1. **點選 ```build``` 來建立 API**
2. **API Name 輸入 todo 並一路下一頁到最後點選 ```Create```**
3. **建立 Routes:**
點選 ```Routes``` 並點選 ```Create``` 後,可以看到以下畫面,我們選 POST 並輸入 /api/todo,接著點選 ```Create```

4. **附加 Integration:**
點選 ```Integrations``` 然後我們點選剛剛建立的```Route```,然後在 target 選擇 Lambda ,並選擇剛剛寫好的 Lambda function,接著送出。

接著我們重複步驟3、4,將前面所有 Lambda function 綁到相應的 route 吧!
## 現在,我們來打打看 API 吧 !
