今度はMongoDbのREST APIを作りたいと思います。若干前の記事とかぶるところはあるのはご了承ください。
プログラム構成
├─routes.ts (ルーティング用) ├─server.ts (mainの起動プログラム) ├─denon.json (denonという監視プログラムの設定ファイル) ├─.env (DBの接続情報) ├─types.ts (型、interface) │ └─controllers └─ products.ts(DBのやり方のプログラム)
わかりにくいのでgithubにアップしています
mysqlと違うのは.envファイルとdenon.jsonファイルが追加になった事です
事前作業
ローカルから接続できるmongodbを用意する。もしできない場合はsandboxで使えるmlabを使う。
環境変数の設定
vi .env
sample
mongo_host=XXX.mlab.com mongo_user=denouser mongo_password=password mongo_db=deno_db mongo_collection=denocollection mongo_port=nnnnn
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の有無と存在チェックをしています。
注意事項
実行されると.deno_pluginsというフォルダが作成されてそこにDLLがインストールされます。 これはmongodbのモジュールが不安定のためです。バージョンアップされると改善されるかと思います
感想
- 更新の部分がjsonのまま渡せないのはめんどくさい。
- 更新時にupsertedIdが何もセットされずはまった。バージョンアップしたら何か変わるかも。