---
# System prepended metadata

title: 使用AWS輕鬆打造RESTful API吧！

---

# 使用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``` 完成資料表建立。

![](https://i.imgur.com/XgPBvZB.png)

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

![](https://i.imgur.com/CJfcy4p.png)

## 2. 建立 Lambda function
上一節中，我們已經完成初步的 DB 建置了，接著我們進到 Lambda 服務中，並且點選 ```create function```。

接著我們透過以下步驟做初始化設定：
1. **選擇模板**：我們可以看到 Lambda 提供從頭開始寫也有簡易的模板，在這邊我們選從頭開始撰寫
2. **輸入 function 名稱**：在 Function name 輸入 `createTodo`
3. **選擇語言**：Lambda有提供多種語言可以選擇，可以根據自己喜好或需求做選擇，本篇將使用Node18.x 來做示範

![](https://i.imgur.com/yBfnBtK.png)

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`也建立起來。
![](https://i.imgur.com/Q85qUVb.png)

## 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```
![](https://i.imgur.com/67uflfS.png)

4. **附加 Integration:**
	點選 ```Integrations``` 然後我們點選剛剛建立的```Route```，然後在 target 選擇 Lambda ，並選擇剛剛寫好的 Lambda function，接著送出。
	
	![](https://i.imgur.com/HUsMxV8.png)
	
	接著我們重複步驟3、4，將前面所有 Lambda function 綁到相應的 route 吧！


## 現在，我們來打打看 API 吧 !

![](https://i.imgur.com/FAia7sy.png)



