# 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 也完全相容, 因此就決定採用這個解決方案
---
## 專案初始化

根據官網的說明, 建議使用 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 上顯示:


不過在開發過程也發現了 tsoa 在 swagger-ui 上的 Authorize 有問題, 編譯完成的 swagger 文件無法手動輸入 token

因此在專案中我先將需要用到 `jwt token` 的部分放在個別 controller 處理, 這部份還需要研究原因
###### tags: `koa` `NodeJS` `Typescript` `OpenAPI`