Deno+MySQL+DockerでAPIサーバーをさくっと作る
Deno
TypeScriptを標準サポートしているDenoのv1.0がリリースされたということで興味があったので触ってみました。 あと恐竜がかわいい。
作るもの
deno + MySQL + docker-composeでローカル環境にシンプルなAPIサーバーです。 適当にどう森の村民を登録して編集するようなやつとします。
全体がみたい方はこちら
https://github.com/rydein/deno_first_api
インストールとか下準備
公式に書いてある通りですが以下のコマンドを流すだけです
$ brew install deno
エディタはVSCodeを使いますがdenoのimport記法に対応させるためこちらのプラグインを追加しましょう。
Docker + docker-compose
deno用のDockerfileとdocker-compose.ymlはこちらを使います
FROM hayd/ubuntu-deno:1.0.0
EXPOSE 3000
WORKDIR /app
USER deno
ADD . .
RUN deno cache index.ts
CMD ["run", "--allow-net", "index.ts"]
version: '3'
services:
deno:
build: .
ports:
- "3000:3000"
db:
image: mysql:8
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=Passw0rd
- MYSQL_DATABASE=deno-dev
volumes:
- db-volume:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
volumes:
db-volume:
ローカル環境立ち上げ
とりあえずMySQLをたちあげておきたいので動く環境だけ用意します
import { Application, Router } from "https://deno.land/x/denotrain@v0.5.0/mod.ts";
const app = new Application();
const router = new Router();
app.get("/", (ctx) => {
return {"hello": "world"};
});
await app.run();
立ち上げます
$ docker-compose build
$ docker-compose up -d
DB migration
まずはDBのテーブル定義を作成していきます。 migrationにはこちらのツールを使います。
init
# nessie.config.ts を作成してdb接続情報を追記する
$ deno run --allow-net --allow-read --allow-write https://deno.land/x/nessie/cli.ts init
MySQL以外のデフォルトコードが生成されますが今回は不要なので消してしまいましょう。
configMySql
にDB接続情報を追記します。
const configMySql = {
migrationFolder: `./migrations`,
connection: {
hostname: "localhost", // hostからDockerのMySQLコンテナに繋ぐ
port: 3306,
username: "root",
password: "Passw0rd",
db: "deno-dev",
},
dialect: "mysql",
};
export default configMySql;
接続情報が作成できたのでmigrationファイルを作成します。
create_villagers
の部分は作成するテーブルによって適宜読み替えてください。
# 村民のmigration
$ deno run --allow-net --allow-read --allow-write https://deno.land/x/nessie/cli.ts make create_villagers
migration/
ディレクトリにmigrationファイルが生成されるので必要な情報を追記していきます。
村民には名前、性別、性格、誕生日があるので追加します。
import { Schema } from "https://deno.land/x/nessie/mod.ts";
// migration ファイルにテーブル情報を追加する
export const up = (schema: Schema): void => {
schema.create("villagers", (table) => {
table.id();
table.string("name", 100).nullable(); // 名前
table.string("gender", 100).nullable(); // 性別
table.string("personality", 100).nullable(); // 性格
table.string("birthday", 100).nullable(); // 誕生日
});
};
export const down = (schema: Schema): void => {
schema.drop("villagers");
};
migrationを実行しましょう。
#migration の実行
$ deno run --allow-net --allow-read https://deno.land/x/nessie/cli.ts migrate
Query Builder
DBからデータを参照したり登録したりするにはこちらのクエリビルダーを使います。
場所はどこでも良いですがmodels/villagers.ts
にDBに登録、取得する処理を作成します。
import Dex from "https://deno.land/x/dex/mod.ts";
import client from "./config.ts";
let dex = Dex({client: "mysql"});
let table = "villagers";
interface Villager {
id?: number,
name: string,
gender: string,
personality: string,
birthday: string
}
///
/// 新しい村民を追加して追加したデータを返す
///
function addVillager(villager: Villager) {
const insertQuery = dex.queryBuilder().insert([villager]).into(table).toString();
return client.execute(insertQuery).then((result: any) => {
const getQuery = dex.queryBuilder().select().from(table).where({id: result.lastInsertId}).toString();
return client.execute(getQuery).then((result: any) => result.rows ? result.rows[0] : {});
})
}
///
/// 全ての村民を返す
///
function getAllVillagers() {
const getQuery = dex.queryBuilder().select("*").from(table).toString();
return client.execute(getQuery);
}
///
/// 村民の更新を行う、更新されたデータを返す
///
function editVillager(id: number, villager: Villager) {
const editQuery = dex.queryBuilder().from(table).update(villager).where({id}).toString();
return client.execute(editQuery).then(() => {
const getQuery = dex.queryBuilder.select().from(table).where({id}).toString();
return client.execute(getQuery).then((result: any) => result.rows ? result.rows[0] : {});
});
}
///
/// 村民の削除
///
function deleteVillager(id: number) {
const deleteQuery = dex.queryBuilder().from(table).delete().where({id}).toString();
return client.execute(deleteQuery)
}
export {
addVillager,
getAllVillagers,
editVillager,
deleteVillager
}
Http Server
controllers/villagers.ts
にコントローラーを作成し、APIのルーティングを作成してリクエストを処理していきます。
import { Router } from "https://deno.land/x/denotrain@v0.4.4/mod.ts";
import { addVillager, getAllVillagers, editVillager, deleteVillager } from "../models/villagers.ts";
const api = new Router();
api.get("/", (ctx) => {
return getAllVillagers().then((result: any) => {
return result.rows;
})
})
api.post("/", (ctx) => {
const body = {
name: ctx.req.body.make,
gender: ctx.req.body.gender,
personality: ctx.req.body.personality,
birthday: ctx.req.body.birthday,
}
return addVillager(body).then((villager: any) => {
ctx.res.setStatus(201);
return villager;
})
})
api.patch("/:id", (ctx) => {
const body = {
name: ctx.req.body.make,
gender: ctx.req.body.gender,
personality: ctx.req.body.personality,
birthday: ctx.req.body.birthday,
}
return editVillager(ctx.req.params.id as number, body).then((result: any) => {
return result;
})
});
api.delete("/:id", ctx => {
return deleteVillager(ctx.req.params.id as number).then(() => {
ctx.res.setStatus(204);
return true;
})
});
export default api;
index.ts
にサーバーのエントリーポイントを用意し、作成したRouterとURLをセットします。
/api/villagers
に作成したRouterをセットすることで、このURL配下に上記で作成したルーティングが用意されます。
import { Application, Router } from "https://deno.land/x/denotrain@v0.4.4/mod.ts";
import api from "./controllers/villagers.ts";
const app = new Application({});
app.use("/api/villagers", api);
app.run();
起動
サーバーの用意ができたので起動しましょう、以下のURLでAPIが起動しているはずです。 http://localhost:3000/
$ docker-compose build
$ docker-compose up -d
まずは最初のAPIを作ってみましたが、ライブラリを探すのも公式ページから検索できてドキュメントも読みやすくそろっているのでとくにハマることなく進みました。
Dockerのイメージも40MBぐらいで小さいので良さそうですね、次はクラウド環境にデプロイしてみたいと思います。