参考にしているyoutubeがRESTAPIを作っていたのでそちらを参考にMYSQLのRESTAPIを作りたいと思います。 RESTの処理の部分はかなり参考(ぱくり)にしている
プログラム構成
├─routes.ts (ルーティング用) ├─simpleServer.ts (mainの起動プログラム) ├─types.ts (型、interface) │ └─controllers └─ products.ts(DBのやり方のプログラム)
わかりにくいのでgithubにアップしています
事前作業
mysql table 作成
CREATE TABLE `Product` ( `id` varchar(64) NOT NULL, `name` varchar(256) DEFAULT NULL, `description` varchar(1024) DEFAULT NULL, `price` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
環境変数の設定
set MYSQL_HOST_ENV=XXX.XXX.XXX.XXX set MYSQL_USERNAME_ENV=XXXXXXXXXX set MYSQL_DB_ENV=XXXXXXXXXX set MYSQL_PASSWORD_ENV=XXXXXXXXXX set MYSQL_PORT_ENV=3306
export MYSQL_HOST_ENV=XXX.XXX.XXX.XXX export MYSQL_USERNAME_ENV=XXXXXXXXXX export MYSQL_DB_ENV=XXXXXXXXXX export MYSQL_PASSWORD_ENV=XXXXXXXXXX export MYSQL_PORT_ENV=3306
実行
deno run --allow-net --allow-env server.ts
Routes
GET /api/v1/products GET /api/v1/products/:id POST /api/v1/products PUT /api/v1/products/:id DELETE /api/v1/products/:id
ソース解説
simpleServer.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と違い型宣言できるのはいいですね。
products.ts
長いの分割になります。全文をみたい場合はgithubのソースを参照してください。
ライブラリーの読み込み
import { v4 } from "https://deno.land/std/uuid/mod.ts"; import { Client } from "https://deno.land/x/mysql/mod.ts"; import { Product } from '../types.ts'
/std/uuid/mod.tsはuuidというユニークのIDを作ってくれる関数です。stdなので標準ライブラリーです。
/x/mysql/mod.tsはmysqlに接続するためのプログラムです。xがついているのでサードパーティのライブラリーです。
/types.tsは自分がつくった型宣言用のプログラムです。
mysqlの接続
//環境変数 const hostname = Deno.env.get("MYSQL_HOST_ENV") as string const username = Deno.env.get("MYSQL_USERNAME_ENV") as string const db = Deno.env.get("MYSQL_DB_ENV") as string const password = Deno.env.get("MYSQL_PASSWORD_ENV") as string const port = parseInt(Deno.env.get("MYSQL_PORT_ENV") as string) //環境変数はstringなので string to numberにする //mysqlに接続する const client = await new Client().connect({ hostname: hostname, username: username, db: db, password: password, port:port });
環境変数を変数に設定してmysqlのconnectionのClientを作成しています。本番で使うならtrycatchした方がいいかも。
データ取得(全件)
const getProducts = async({ response }: { response: any }) => { try{ const data = await client.query(`SELECT id , name , description, price FROM Product;`) //判定が微妙な感じ・・・ 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の記載が必須です。
- データの取得判定は「data.toString()」で配列を文字列に変換する事でおこなっています。単純にif ( data)を記載してしまうと0件でも成功したと判定されてしまいます。色々と試してみたけど2020/06/03時点だとこれしかないみたい
- 念のためにtrycatchをしています。
- 取得後にconnectionを開放する処理はいれていません。いれると同時アクセスで落ちる事がありました。
データ取得(1件)
const getProduct = async({ params ,response }: { params:{id :string },response: any }) => { try{ const data = await client.query( "select id,name,description,price from Product where id = ? ", [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のパラメータをもとにSQLを作っています。SQLインジェクションはできていました(16進数では試していません)
新規作成
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{ let result = await client.execute(` INSERT INTO Product (id,name, description, price) VALUES (?,?,?,?); `, [ product.id, product.name, product.description, product.price ]); //成功時には{ affectedRows: 1, lastInsertId: 0 }が返ってくる。もう少し欲しいけど・・・ if (result.affectedRows ===1 ){ response.status = 200 response.body = { success: true, data: product } } else { response.status = 400 response.body = { success: false, msg: 'Insert false' } } } 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 }
更新
const updateProduct = async({ params, request, response }: { params: { id: string }, 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 = params.id try{ let result = await client.execute(` UPDATE Product SET name = ?, description = ?, price = ? WHERE id = ?; `, [ product.name, product.description, product.price, product.id, ]); //成功時には{ affectedRows: 1, lastInsertId: 0 }が返ってくる。もう少し欲しいけど・・・ if (result.affectedRows ===1 ){ response.status = 200 response.body = { success: true, data: product } } else { response.status = 404 response.body = { success: false, msg: 'No product found' } } } catch(error){ response.status = 500 response.body = { success: false, msg: 'Server Error' } } } }
- パラメータとリクエストデータを使って更新しています.
- 更新の有無はaffectedRows: 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{ let result = await client.execute(` DELETE FROM Product WHERE id = ?; `, [ params.id, ]); //成功時には{ affectedRows: 1, lastInsertId: 0 }が返ってくる。もう少し欲しいけど・・・ if (result.affectedRows ===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' } } } }
- パラメータを使ってDELETEのSQLを作っています。
- IDの有無ぐらいしかチェックしていません
感想
- ベースとなる部分ができたのでうれしい。
- 他のDBの部分もClientの部分を変えればいけると思うので挑戦したい
- ORラッパーもあるみたいなので次はそちらで作りたい