ブログにも記載したのですが誰も突っ込んでもらえないのでこちらに記載。
注意事項
プログラム構成
├─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;
環境変数の設定
windowsの場合
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
Linuxの場合
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 }) => {
- postする時の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の部分を変えればいけると思うので挑戦したい
- mysqlのORラッパーもあるみたいなので次はそちらで作りたい
参考URL