Denoで簡単なAPIを作成してCORSも許可する

# TD;DR
- Denoを使えばTypeScriptネイティブにサーバーサイドjsを記述・実行できる
- oakでCORSの許可をとるには[このmodule](https://github.com/tajpouria/cors)が便利

# はじめに
Deno 1.0が爆誕してからかれこれ2ヶ月ほど立ちました!
https://deno.land/v1

当時はメジャーアップデートが出たこともあってTwitter界隈でも話題になっていたように思いますが、まだまだ使用例がないように思います。

そこで、実際にDenoで単純なTodoアプリのAPIを作成し、同一ドメイン以外からも叩けるようにする方法を紹介します。

# Deno側の実装
今回実装したAPIは[こちら](https://github.com/jiko21/deno-rest-api)にあげております。

主な構成として

- controller: リクエストに対するhandlerを定義、関数として返します
- model: 今回は単純なリストをRepositoryとして実装しています。
- app: アプリケーションのエントリポイントです
- router: ルーティングとそれに対応するhandlerを紐づけてます

という構成になっています。

また、フレームワークとして[oak](https://oakserver.github.io/oak/)を利用しています。

## modelの実装
まずはEntityを表す型を定義します

```task.ts
export type Task = {
  id: number|undefined;
  title: string;
  overview: string;
  isFinished: boolean;
};

```

そして、それらを保存、取得するためのクラスとして`TaskRepository.ts`を実装します。
今回は、簡単なapiなので、DBなどは使っておらず、クラス内のリストに保存、そこからのデータ取得を行うこととします。

```TaskRepository.ts
export default class TaskRepository {
  private tasks: Task[];
  private count: number;

  constructor() {
    this.tasks = [];
    this.count = 0;
  }

  getTasks(): Task[] {
    return this.tasks;
  }
  // 省略
}
```
## controller
次に、リクエストに応じてRepositoryを操作するControllerを実装します。

基本的にoakのhandlerは

```handler.ts
const handler = (context: RouterContext) => {
  context.response.body = taskRepository.getTasks();
  context.response.status = 200;
}
```

と記述できますが、今回、Repositoryを複数Handlerに渡って共通化(一種のDI)するため、ルートメソッドを新たに作成し、その内部で生成しています。

```todocontroller.ts
export const taskController = () => {
  const taskRepository = new TaskRepository();
  const getTasks = (context: RouterContext) => {
    context.response.body = taskRepository.getTasks();
    context.response.status = 200;
  }

  const addTask = async (context: RouterContext) => {
    const body = context.request.body({type: 'json'});
    const task: Task = await body.value as Task;
    try {
      taskRepository.addTask(task);
      context.response.body = {
        msg: 'OK',
      };
      context.response.status = 200;
    } catch(e) {
      context.response.body = {
        msg: e.message,
      };
      if (e instanceof NotFoundException) {
        context.response.status = 404;
      } else {
        context.response.status = 500;
      }
    }
  }

  // 省略
  return {
    getTasks,
    ...
   };
}
```
## router
最後に、controllerで作成したhandlerを各apiと
紐づけます。

```router.ts
export const routes = (): Router => {
  const router = new Router();
  const controller = taskController();
  router
    .get('/tasks', controller.getTasks)
    .get('/tasks/:id', controller.getTask)
    .post('/tasks', controller.addTask)
    .put('/tasks/:id', controller.updateTask)
    .delete('/tasks/:id', controller.deleteTask);
  return router;
}
```
## app
```app.ts
import {Application} from 'https://deno.land/x/oak/mod.ts';
import { routes } from './router.ts';

const env = Deno.env.toObject();
const PORT = env.PORT || 4000;
const HOST = env.HOST || '127.0.0.1';

const app = new Application();
const router = routes();
app.use(router.routes());
app.use(router.allowedMethods());

console.log(`Listening on port ${PORT}...`);

await app.listen(`${HOST}:${PORT}`);
```

最後に、先程用意したrouterをoakのapplicationに噛ませてやります。

# APIを叩いてみる
それでは、apiを起動してみましょう。denoは、node.jsとは違い、ネットワークやio等に対して、適切なパーミッションを付与して実行してやる必要があります。
今回は

``` bash
deno run --allow-net --allow-env app.ts
```

で実行ができます。
`POST: http://127.0.0.1:4000/tasks`に対して

``` json
{
	"title": "hogehoge2",
	"overview": "hoge2",
	"isFinished": false
}
```
を送信してから`GET: http://127.0.0.1:4000/tasks`にアクセスすると

``` json
[
  {
    "title": "hogehoge111",
    "overview": "hoge2",
    "isFinished": true,
    "id": 0
  }
]
```
のような形でデータが帰ってきます。
# CORSへの対応
それでは実際にフロントエンド側のアプリからAPIを叩いてみます。
今回、そのために使ったアプリケーションは[こちら](https://github.com/jiko21/reacoil-todo-app-withapi/)

実際に`npm run start`で開発用のサーバーを起動して`localhost:3000`にアクセスしていただいたらわかりますが、
Todoアプリの画面が表示されます。

しかし、同一生成元ポリシーのため、APIを叩くことはできません。
そこで、API側でCORSを有効にします。

CORS対応の方法は、oakにも[issue](https://github.com/oakserver/oak/issues/154#issuecomment-640657239)として上がっておりますが、
今回は、有志によって開発された[こちらのモジュール](https://github.com/tajpouria/cors)を利用していきます。

```app.ts
import {Application} from 'https://deno.land/x/oak/mod.ts';
// cors用のmodule
import { oakCors } from 'https://deno.land/x/cors/mod.ts';
import { routes } from './router.ts';

const env = Deno.env.toObject();
const PORT = env.PORT || 4000;
const HOST = env.HOST || '127.0.0.1';

const app = new Application();
const router = routes();
// corsを有効化
app.use(oakCors());
app.use(router.routes());
app.use(router.allowedMethods());

console.log(`Listening on port ${PORT}...`);

await app.listen(`${HOST}:${PORT}`);
```

これだけでCORSにも対応できます。

再度サーバーを実行して、画面から操作してみると、無事、タスクを投稿できます。

# 参考
https://github.com/oakserver/oak
https://github.com/tajpouria/cors
https://github.com/oakserver/oak/issues/154
https://deno.land/v1