ブログにも記載したのですが誰も突っ込んでもらえないのでこちらに記載。
注意事項
-
RESTのrouterの部分は参考にした部分を丸パクリです。そちらは目をつぶってほしです。
-
テストの画面はpostmanを使っています。
-
実行されると.deno_pluginsというフォルダが作成されてそこにDLLがインストールされます。
これはmongodbのモジュールが不安定のためです。バージョンアップされると改善されるかと思います
プログラム構成
├─routes.ts (ルーティング用)
├─server.ts (mainの起動プログラム)
├─denon.json (denonという監視プログラムの設定ファイル)
├─.env (DBの接続情報)
├─types.ts (型、interface)
│
└─controllers
└─ products.ts(DBのやり方のプログラム)
わかりにくいのでgithubにアップしています
事前作業
MongoDBをローカルに作ってコレクションを作成してください。
もしできない場合は500Mまで無料で使えるMlabというサービスがありますのでそちらを使ってください。
登録方法は
https://masalib.hatenablog.com/entry/2018/04/19/211340
環境変数の設定
.envのサンプルは以下です(ローカルの環境によって違うのでもし使う場合は修正してください)
mongo_host=XXX.mlab.com
mongo_user=denouser
mongo_password=password
mongo_db=deno_db
mongo_collection=denocollection
mongo_port=nnnnn
このファイルはgithubにあがっていないので、もし動かす場合は作成してください
denon install
変更時に自動的に再起動してくれるツールのインストール。環境設定とかも楽です。
deno install --allow-read --allow-run --allow-write -f --unstable https://deno.land/x/denon/denon.ts
実行
denon start
Routes
GET /api/v1/products
GET /api/v1/products/:id
POST /api/v1/products
PUT /api/v1/products/:id
DELETE /api/v1/products/:id
ソース解説
server.ts
import {Application,Router} from "https://deno.land/x/oak/mod.ts";
import router from './routes.ts'
const port = 5000
const app = new Application()
app.use(router.routes())
app.use(router.allowedMethods())
console.log(`Server Running on port ${port}`)
await app.listen({port })
起動用のプログラムなのでルーティング用のファイルを読んでいて適用させているだけです。
routes.ts
import {Router} from "https://deno.land/x/oak/mod.ts";
import {getProducts,getProduct, addProduct ,updateProduct ,deleteProduct} from './controllers/products.ts'
const router = new Router()
router.get('/api/v1/products', getProducts )
.get('/api/v1/products/:id', getProduct )
.post('/api/v1/products', addProduct )
.put('/api/v1/products/:id', updateProduct )
.delete('/api/v1/products/:id', deleteProduct )
export default router
ルーティング用のプログラムで振るまい(コントローラー)の設定をしています
:id になっているところはgetのパラメータです。
types.ts
export interface Product{
id: string;
name: string;
description: string;
price: number;
}
型宣言してるだけです。javascriptと違い型宣言できるのはいいですね。
denon.json
{
"$schema": "https://deno.land/x/denon/schema.json",
"scripts": {
"start": {
"cmd": "deno run server.ts",
"allow": [
"env",
"read",
"write",
"net",
"plugin"
],
"unstable": true
}
},
"logger": {
"debug": true
},
"watcher": {
"interval": 350,
"exts": ["js", "ts", "json"],
"match": ["*.*"],
"skip": ["*/.git/*" ,"README.md" ],
"legacy": false
}
}
- 起動時の設定や監視ファイルの無視ファイルを設定しています。allowの設定とか多くて困る・・・この設定があると本当に楽~♪
products.ts
長いの分割になります。全文をみたい場合はgithubのソースを参照してください。
####ライブラリーの読み込み
import { v4 } from "https://deno.land/std/uuid/mod.ts";
import { config } from "https://deno.land/x/dotenv/mod.ts";
import { MongoClient } from "https://deno.land/x/mongo@v0.7.0/mod.ts";
import { Product } from '../types.ts'
- /std/uuid/mod.tsはuuidというユニークのIDを作ってくれる関数です。stdなので標準ライブラリーです。
- /x/dotenv/mod.tsは環境変数をファイルから読み込むプログラムです。
- /x/mongo@v0.7.0/mod.tsはmongodbに接続するためのプログラムです。xがついているのでサードパーティのライブラリーです。
- /types.tsは自分がつくった型宣言用のプログラムです。
####mongodbの接続
//環境変数
const ENV_PATH = '.env';//deno runしたところをカレントディレクトリになるみたい
const config_env:any = config({ path: ENV_PATH });
const user:string = config_env.mongo_user
const password_data:string = config_env.mongo_password
const host:string = config_env.mongo_host
const port_num:number = config_env.mongo_port
const db_name:string = config_env.mongo_db
//mongodb://<dbuser>:<dbpassword>@<host>:<port>/<db>
const url:string = 'mongodb://' + user + ':' + password_data + '@' + host + ':' + port_num + '/' + db_name
//mongoDB接続
console.log("mongodb connection start")
const client = new MongoClient()
client.connectWithUri(url)
//DBの設定
const db = client.database(db_name)
//コレクションの設定
const collection_name:string = config_env.mongo_collection
const datas = db.collection(collection_name)
環境変数を変数に設定してmongodbのconnectionのClientを作成しています。本番で使うならtrycatchした方がいいかも。
データ取得(全件)
const getProducts = async({ response }: { response: any }) => {
try{
const data = await datas.find();
//判定が微妙な感じ・・・
if ( data.toString() ===""){
response.status = 404
response.body = {
success: false,
msg: 'No Product found'
}
} else {
response.body = {
success: true,
data: data
}
}
} catch(error){
response.status = 500
response.body = {
success: false,
msg: 'Server Error'
}
}
}
- データを取得するのでasyncとawaitの記載が必須です。
- 「datas.find()」で全件取得しています。本番で動かすなら件数の制限や取得順の指定をするべき
- データの取得判定は「data.toString()」で配列を文字列に変換する事でおこなっています。単純にif ( data)を記載してしまうと0件でも成功したと判定されてしまいます。色々と試してみたけど2020/06/03時点だとこれしかないみたい
- 念のためにtrycatchをしています。
データ取得(1件)
const getProduct = async({ params ,response }: { params:{id :string },response: any }) => {
try{
const data = await datas.find({ "id": params.id });
//console.log(params.id)
//判定が微妙な感じ・・・
if ( data.toString() ===""){
response.status = 404
response.body = {
success: false,
msg: 'No Product found'
}
} else {
response.status = 200
response.body = {
success: true,
data:data
}
}
} catch (error){
response.status = 500
response.body = {
success: false,
msg: 'Server Error'
}
}
}
- getのIDのパラメータをもとに条件式にいれています。ユニークの設定をしていないと複数件とれてしまいますwww
新規作成
const addProduct = async ({ request, response }: { request: any , response: any }) => {
const body = await request.body()
if (!request.hasBody){
response.status = 400
response.body = {
success: false,
msg: 'No Data'
}
}else {
//バリデーション・・・省略
const product: Product = body.value
product.id = v4.generate()
try{
const insertId = await datas.insertOne(product);
const product_data = await datas.findOne({ _id: insertId });//$oidを表示するため
if ( product_data.toString() ==="" ){
response.status = 400
response.body = {
success: false,
msg: 'Insert false'
}
} else {
response.status = 200
response.body = {
success: true,
data: product_data
}
}
} catch(error){
response.status = 500
response.body = {
success: false,
msg: 'Server Error'
}
}
}
}
- バリデーションはめんどくさいのしていません
- idはuuidでユニークのIDを作成しています
- パラメータではなくリクエストの内容(JSON)を利用してデータをインサートしてます
- 関数の部分が1件取得時と違います
1件取得 ↓パラメータ ↓パラメータ
const getProduct = async({ params ,response }: { params:{id :string },response: any }) => {
新規作成 ↓リクエスト ↓リクエスト
const addProduct = async ({ request, response }: { request: any , response: any }) => {
- JSONのサンプル
{
"name": "Product1_add",
"description": "description1_add",
"price": 200
}
mysqlと違いmongodbが独自に発行しているIDが付与されています。
更新
const updateProduct = async({ params, request, response }: { params: { id: string }, request: any, response: any }) => {
//console.log("params.id:"+ params.id)
const body = await request.body()
if (!request.hasBody){
response.status = 400
response.body = {
success: false,
msg: 'No Data'
}
}else {
//バリデーション・・・省略
const product: Product = body.value
product.id = params.id
try{
const { matchedCount, modifiedCount, upsertedId } = await datas.updateOne(
{ "id": params.id },
{ $set: {
"name": product.name,
"description": product.description,
"price": product.price
} }
);
console.log("upsertedId:" + upsertedId) //NULLがセット??どうやったらセットされるのか不明
console.log("matchedCount:" + matchedCount)
console.log("modifiedCount:" + modifiedCount)
const data = await datas.find({ "id": params.id }); //$oidを表示するため
//成功時には{ matchedCount 1, modifiedCount 1 }が返ってくる。もう少し欲しいけど・・・
if (matchedCount ===1 && modifiedCount ===1 ){
response.status = 200
response.body = {
success: true,
data: data
}
} else {
response.status = 404
response.body = {
success: false,
msg: 'No product found'
}
}
} catch(error){
response.status = 500
response.body = {
success: false,
msg: 'Server Error'
}
}
}
}
- パラメータとリクエストデータを使って更新しています.
- bodyをそのまま渡す事ができず・・・分解してセットしています
- 公式に書いてあるとおりにやっているのですが、upsertedId にIDがセットされません。NULLがセットされるので今回は使っていません
- 更新の有無はmatchedCount: 1とmodifiedCount: 1 で判別しています。
削除
const deleteProduct = async({ params, response }: { params: { id: string }, response: any }) => {
if (params.id ===""){
response.status = 400
response.body = {
success: false,
msg: 'No Data'
}
}else {
//バリデーション・・・省略
try{
const data = await datas.find({ "id": params.id });
//console.log(params.id)
//判定が微妙な感じ・・・
if ( data.toString() ===""){
response.status = 404
response.body = {
success: false,
msg: 'No Product found'
}
} else{
//const deleteCount = await datas.deleteOne({ "id": params.id }); mongodbのIDじゃないといけないのか?わからないのでmanyに変更
const deleteCount = await datas.deleteMany({ "id": params.id });
//console.log(deleteCount)
//成功時には{ deleteCount 1 }が返ってくる。もう少し欲しいけど・・・
if (deleteCount ===1 ){
response.status = 200
response.body = {
success: true,
msg: 'Product removed'
}
} else {
response.status = 404
response.body = {
success: false,
msg: 'No product found'
}
}
}
} catch(error){
response.status = 500
response.body = {
success: false,
msg: 'Server Error'
}
}
}
}
- パラメータを使って削除しています。deleteOneだとうまくいかなかったのでdeleteManyでやっています
- IDの有無と存在チェックをしています。
感想
- 更新の部分がjsonのまま渡せないのはめんどくさい。
- 更新時にupsertedIdが何もセットされずはまった。バージョンアップしたら何か変わるかも。