LoginSignup
0
1

More than 3 years have passed since last update.

DenoでREST API(mysql)

Last updated at Posted at 2020-06-07

ブログにも記載したのですが誰も突っ込んでもらえないのでこちらに記載。

注意事項

  • RESTのrouterの部分は参考にした部分を丸パクリです。そちらは目をつぶってほしです。
  • テストの画面はpostmanを使っています。
  • denonを知る前のソースなので環境変数のやり方が古いです

プログラム構成

├─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を開放する処理はいれていません。いれると同時アクセスで落ちる事がありました。
  • 全件取得していますが、本番で動かすなら件数を絞り込むべき

20200603021635.png

データ取得(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進数では試していません)

20200603021820.png

新規作成

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
  }

20200603022619.png

更新

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で判別しています。個人的にはイケてないな~と思うけどこれしかない。
  • 更新に失敗した場合はデータがなかったと判別しています。

20200603023416.png

削除

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1