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を表す型を定義します
export type Task = {
id: number|undefined;
title: string;
overview: string;
isFinished: boolean;
};
そして、それらを保存、取得するためのクラスとしてTaskRepository.ts
を実装します。
今回は、簡単なapiなので、DBなどは使っておらず、クラス内のリストに保存、そこからのデータ取得を行うこととします。
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は
const handler = (context: RouterContext) => {
context.response.body = taskRepository.getTasks();
context.response.status = 200;
}
と記述できますが、今回、Repositoryを複数Handlerに渡って共通化(一種のDI)するため、ルートメソッドを新たに作成し、その内部で生成しています。
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と
紐づけます。
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
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として上がっておりますが、
今回は、有志によって開発されたこちらのモジュールを利用していきます。
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