API 實作(二):以 Koa 實作 RESTful API

透過 Koa 來寫 Web 框架,很輕鬆就可以寫出一個 RESTful API。這篇會示範如何以 koa-router,寫一個簡單具有 CRUD 功能的 RESTful API。

API 實作(二):以 Koa 實作 RESTful API

透過 Koa 來寫 Web 框架,很輕鬆就可以寫出一個 RESTful API。這篇會示範如何以 koa-router,寫一個簡單具有 CRUD 功能的 RESTful API。

在開始之前,我會建議你先閱讀 API 實作(一):規劃 RESTful API 要注意什麼,以了解什麼是 HTTP Method、HTTP Status,甚至是一些命名原則。

什麼是 CRUD?
CRUD 是 Create、Read、Update、Delete,對應到中文的新增、查詢、修改、刪除。

設計 API 文件

這篇我們就來寫簡單的文章系統好了。大概想一下會有四支東西:新增文章、編輯文章、查看文章、刪除文章。對應到的 HTTP Method 分別是:POSTPUTGETDELETE

再來決定一個文章會有多少欄位:大概想一想應該會有標題內容時間作者,就先簡單四個就好。

所以新增文章應該除了時間以外三個欄位都要送,沒送的話就噴錯(400 錯誤)、編輯亦同,多送一個文章 ID。讀取的時候只需要送文章 ID 就好,刪除的時候也只要送一個文章 ID 就好。

大概整理下來 API 文件可能會長這樣:

## 新增文章
`POST` `/article`
### 傳入資料
- title: (必填)標題
- body: (必填)內容
- author:(必填)作者
### 回傳狀態
- 201:已新增
- 400:有欄位未填
### 回傳資料
- id:新增的文章 id

## 編輯文章
`PUT` `/article/:id`
### 傳入資料
- id:(必填)文章 ID
- title:(必填)標題
- body:(必填)內容
### 回傳狀態
- 204:已編輯
- 400:有欄位未填
- 404:文章不存在

## 查看文章
`GET` `/article/:id`
### 傳入資料
- id:文章 ID
### 回傳資料
- title
- body
- author
- time
### 回傳狀態
- 200:附上文章內容
- 404:文章不存在

## 刪除文章
`DELETE` `/article/:id`
### 傳入資料
- id:文章 id
### 回傳狀態
- 204:已刪除
- 404:文章不存在

API 文件其實可以用 jsDoc、apiDoc 等規範,但我個人推薦以 Markdown 格式撰寫。

以 Koa.js 實作 API

依照上一篇 Koa 文章以後,我們已經大概了解如何宣告 Koa、如何以 koa-router 去設定不同方法不同網址的路由、以及什麼是 Middleware,但我們還不知道怎麼讀取表單送過來的資料。這邊我們需要借用 koa-body 這個 Middleware,所以首先安裝:

npm install --save koa-body

接著應該可以弄出這樣的架構:

const Koa = require('koa');
const Router = require('koa-router');
const koaBody = require('koa-body');

const app = new Koa();
const router = new Router();

app.use(koaBody());

router
    .post('/article', ctx => { /* ... */ })
    .put('/article/:id', ctx => { /* ... */ })
    .get('/article/:id', ctx => { /* ... */ })
    .delete('/article/:id', ctx => { /* ... */ });

app.use(router.routes());
app.listen(3000);

中間四個分別填上 CURD 的邏輯,可能是資料庫系統:如 MongoDB、MySQL 等等。後面的文章會討論怎麼架設資料庫,但這邊我們先以一個整數來計算我們新文章的 ID 要塞什麼,並以 JSON Object 來儲存我們的文章(換句話說這支 JS 關掉資料就不在了),所以我們在 router 的四個 function 前先宣告一個變數 articles 陣列:

let lastId = 0;
let articles = [];

首先是新增文章的部分,我們需要讀取表單送過來的資料,表單送過來的資料會放在 ctx.request.body 裡面,把這些資料塞到 articles 裡面:

ctx => {
    // 把資料分別存在 title、body、author 等變數
    const { title } = ctx.request.body;
    const { body } = ctx.request.body;
    const { author } = ctx.request.body;
    
    if (title && body && author) {
        // 如果必填資料都有,就塞進 articles 裡面。然後依照文件回傳 201
        articles.push({
            id: ++lastId,
            title,
            body,
            author,
            time: new Date(),
        });
        ctx.status = 201;
        ctx.body = lastId;
    } else {
        // 如果有欄位沒有填,就依照文件回傳 400
        ctx.status = 400;
    }
}

編輯的部分大同小異,差別是網址的那個 :id 要從 ctx.parmas 拿:

ctx => {
    // 把資料分別存在 id、title、body、author 等變數
    const id = parseInt(ctx.params.id);
    const { title } = ctx.request.body;
    const { body } = ctx.request.body;
    const { author } = ctx.request.body;
    
    if (title && body && author) {
        // 如果必填資料都有,就編輯文章
        // 首先找出文章
        const article = articles.find(x => x.id === id);
        
        if (article) {
            // 如果有文章的話就編輯,並依照文件回傳 204
            article.title = title;
            article.body = body;
            article.author = author;
            article.time = new Date();
            ctx.status = 204;
        } else {
            // 沒有找到的話就依照文件回傳 404
            ctx.status = 404;
        }
    } else {
        // 如果有欄位沒有填,就依照文件回傳 400
        ctx.status = 400;
    }
}

查看的部分也是找出那篇文章,然後回傳:

ctx => {
    // 把資料分別存在 id 變數
    const id = parseInt(ctx.params.id);
    
    if (id) {
        // 首先找出文章
        const article = articles.find(x => x.id === id);
        
        if (article) {
            // 如果有文章的話就依照文件回傳文章內容(預設就是狀態 200)
            ctx.body = article;
        } else {
            // 沒有找到的話就依照文件回傳 404
            ctx.status = 404;
        }
    } else {
        // 如果沒送 id,文章就不存在,就依照文件回傳 404
        ctx.status = 404;
    }
}

刪除的部分也是這樣做:

ctx => {
    // 把資料分別存在 id 變數
    const id = parseInt(ctx.params.id);
    
    if (id) {
        // 首先找出文章
        const article = articles.find(x => x.id === id);
        
        if (article) {
            // 如果有文章的話就刪除文章,然後依照文件回傳 204
            articles = articles.filter(x => x.id !== id);
            ctx.status = 204;
        } else {
            // 沒有找到的話就依照文件回傳 404
            ctx.status = 404;
        }
    } else {
        // 如果沒送 id,文章就不存在,就依照文件回傳 404
        ctx.status = 404;
    }
}

這樣就寫出基本的 Koa API 了,如果想看完整程式碼,可以看看 這個完整範例。下一篇文章會介紹怎麼用工具測試 API。

本篇文章同步發表在 iT邦幫忙

API 實作 目錄

我們正降低廣告比例以提升閱讀體驗。如果你喜歡這篇文章,不妨按下 Like 按鈕分享到社群,以行動支持我寫更多文章。 當然,你也可以 點此用新臺幣支持我,或 點此透過 BTC、ETH、USDC 等加密貨幣支持我
分享到: