# Koa 開發筆記 利用 [koa - A NodeJS framework](https://koajs.com/#introduction) 開發 RESTful API 的筆記 ## 事前準備 因為過去的開發經驗, 一開始預想要使用的開發架構是: koa(2) + Typescript + openapi 在看了 [npm trend](https://npmtrends.com/koa-joi-swagger-vs-koa2-swagger-ui-vs-swagger-express-ts-vs-tsoa-vs-typescript-rest-swagger) 之後, 花了點時間研究了這個最熱門的 [tsoa](https://tsoa-community.github.io/docs/introduction.html) >tsoa is a framework with integrated OpenAPI compiler to build Node.js serve-side applications using TypeScript. It can target express, hapi, koa and more frameworks at runtime. tsoa applications are type-safe by default and handle runtime validation seamlessly. 根據官網的說法, 他整合了 Typescript, OpenAPI, 並且支援各種 web framwork, 包含 express, hapi, 以及 koa 看起來非常符合我的需求, 不但可以確保 type-safe, 有了 swagger 也方便測試, 對 koa router/middleware 也完全相容, 因此就決定採用這個解決方案 --- ## 專案初始化 ![](https://i.imgur.com/K4629fk.png) 根據官網的說明, 建議使用 yarn 來管理 package, 實際測試使用 npm, 目前沒有出現錯誤 初始化過程非常簡單 ```shell! # Create a new folder for our project mkdir tsoa-project cd tsoa-project # Create a package.json and initialize git git init yarn init -y # Add our dependencies yarn add tsoa express yarn add -D typescript @types/node @types/express # Initialize tsconfig.json yarn run tsc --init ``` 這邊要注意的是, 官方範例是用 express, 如果要用 koa 就是另外更改 yarn 指令 接下來就是最重要的部分, tsoa 支援 cli mode, 可以利用 `tsoa` 指令操作, 例如編譯專案, 產生 swagger 文件等等, 所以要先設定 tsoa config: ```json! // tsoa.json { "entryFile": "src/app.ts", "noImplicitAdditionalProperties": "throw-on-extras", "controllerPathGlobs": ["src/**/*Controller.ts"], "spec": { "outputDirectory": "build", "specVersion": 3 }, "routes": { "routesDir": "build" } } ``` 除了這些預設值以外, 還可以更加的客製化, 例如: ```json! "routes": { "routesDir": "build", "middleware": "koa", "iocModule": "src/ioc", "authenticationModule": "./src/auth/authentication.ts" } ``` 指定 middleware 的 framework, 這邊我當然是用 koa tsoa 也支援依賴注入(Dependency Injection), 只要照[官網的說明](https://tsoa-community.github.io/docs/di.html): ```typescript! // src/ioc.ts import { IocContainer, IocContainerFactory } from "@tsoa/runtime"; import { Container } from "di-package"; // Assign a container to `iocContainer`. const iocContainer = new Container(); // Or assign a function with to `iocContainer`. const iocContainer: IocContainerFactory = function ( request: Request ): IocContainer { const container = new Container(); container.bind(request); return container; }; // export according to convention export { iocContainer }; ``` 然後在 tsoa.json 中指定 ioc module 的路徑就可以 之後在需要使用的地方加上 decorator: `@inject()` 就可以自動注入 另外還有 auth module, 這邊參考[官網的範例](https://tsoa-community.github.io/docs/authentication.html)作點修改: ```typescript! import {Request} from 'koa'; import {verify} from 'jsonwebtoken'; export const secret = 'my@secrEte'; export function koaAuthentication( request: Request, securityName: string, _?: string[] ): Promise<unknown> { if (securityName === 'jwt') { const token = request.get('token') || request.query.token || request.headers['x-access-token']; return new Promise((resolve, reject) => { if (!token) { reject(new Error('No token provided')); } verify(<string>token, secret, function (err: unknown, decoded: unknown) { if (err) { reject(err); } else { resolve(decoded); } }); }); } return new Promise((_, reject) => { reject(new Error('securityName not support')); }); } ``` 做了一個簡單的 auth middleware for koa, 之後再 tsoa.json 指定檔案路徑即可 使用方式也很簡單, 在需要使用的 router 前加上 decorator: ``` @Security('jwt') public async someMethod( ... ): Promise<void> { ... } ``` 就可以自動驗證 token 另外我在專案中也自訂了兩個 middleware 當作練習, 一個是將每個請求加上 [`request-id`](https://github.com/Edwardz43/fund-demo/blob/main/src/middlewares/request-id.ts), 另一個則是簡單的 [`request-logger`](https://github.com/Edwardz43/fund-demo/blob/main/src/middlewares/request-log.ts) 顯示請求的明細 --- ## 編譯 tsoa 支援 `tsoa` cli 操作, 可以透過: ```shell! tsoa spec-and-routes ``` 編譯出 `routes.ts` 與 `swagger.json` , 其中 `routes.ts` 就是用來將自定的 router(controller) 與其餘的 bussiness logic, data-layered component 自動與 koa app mapping 的腳本: [routes.ts source code](https://github.com/Edwardz43/fund-demo/blob/main/routes.ts) 個人覺得這樣的方式讓開發者只要專心於撰寫業務邏輯, 不需要手動的去安插每一個自定義元件, 相當方便 例如新增一個 component: `funds`, 我們只需要寫好對應的 controller, tsoa 會透過前面設定好的 `tsoa.json` 找出所有元件的位置並自動編譯 --- ## OpenAPI - Swagger tsoa 支援自動編譯 swagger document, 只需要在的 model 前面加上 comment / decorator, 例如: ```typescript! /** * UserCreationParams objects represent the parameters of the user create endpoint. * * @example { * "username": "John Doe", * "email" : "johndoe@example.com", * "phone" : "886912345678", * "password" : "asdf1234" * } */ export interface UserCreationParams { /** * The name the user used to register his account */ username: string; /** * The email the user used to register his account */ email: string; /** * The password the user used to register his account */ password: string; /** * The phone number the user used to register his account */ phone: string; } ``` tsoa 在編譯時會自動更新 swagger 文件, 在 swagger-ui 上顯示: ![](https://i.imgur.com/W5VX5Ry.png) ![](https://i.imgur.com/B31QXpF.png) 不過在開發過程也發現了 tsoa 在 swagger-ui 上的 Authorize 有問題, 編譯完成的 swagger 文件無法手動輸入 token ![](https://i.imgur.com/PzAYTJE.png) 因此在專案中我先將需要用到 `jwt token` 的部分放在個別 controller 處理, 這部份還需要研究原因 ###### tags: `koa` `NodeJS` `Typescript` `OpenAPI`