LoginSignup
5
3

More than 3 years have passed since last update.

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

Posted at

TD;DR

  • Denoを使えばTypeScriptネイティブにサーバーサイドjsを記述・実行できる
  • oakでCORSの許可をとるにはこのmoduleが便利

はじめに

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

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

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

Deno側の実装

今回実装したAPIはこちらにあげております。

主な構成として

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

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

また、フレームワークとして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等に対して、適切なパーミッションを付与して実行してやる必要があります。
今回は

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

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

{
    "title": "hogehoge2",
    "overview": "hoge2",
    "isFinished": false
}

を送信してからGET: http://127.0.0.1:4000/tasksにアクセスすると

[
  {
    "title": "hogehoge111",
    "overview": "hoge2",
    "isFinished": true,
    "id": 0
  }
]

のような形でデータが帰ってきます。

CORSへの対応

それでは実際にフロントエンド側のアプリからAPIを叩いてみます。
今回、そのために使ったアプリケーションはこちら

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

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

CORS対応の方法は、oakにもissueとして上がっておりますが、
今回は、有志によって開発されたこちらのモジュールを利用していきます。

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

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3