0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ExpressとMySQLを使用して検索機能を作る方法(部分検索も実装する!!)

Posted at

##目的
アパレル商品のデータを調べるときに用いる検索機能を作る。
該当する商品名を入力することで画面に出力。(部分検索も取り入れる)
該当しない商品はデータがありませんと出力する。

##使用ツール

"express": "^4.17.2"
"ejs": "^3.1.6", 
"morgan": "^1.10.0", 
"mysql2": "^2.3.3"

npmを使用してパッケージをインストール。
npm i express ejs morgan mysql2
※mysql2は[mysql]というパッケージがある。パフォーマンス重視ならmysql2を選択。

データベースGUIはA5:SQL Mk-2を使用。

##構成ファイル

datasearch
---node_modules
---roots
    routing.js
---views
    data.ejs
    style.css
---package-lock.json
---package.json
---script.js

##手順

####①script.jsにて下記コードを記載。

script.js
const express = require(`express`); 
const morgan  = require(`morgan`); 
const router = require(`./roots/routing`); 
const app = express(); 
const port = process.env.PORT || 4000; 
app.use(morgan('dev')); 
app.set(`view engine`,`ejs`); 
app.use(express.urlencoded({extended: false})); 
app.use(`/data`,router); 
app.use(express.static(`views`)); 
app.listen(port);

morganを用いることでGET,POSTなどのログをコンソールに出力できる。
スクリーンショット (30).png

HTTPログを可視化したい方はインストール必須。
ルーティング処理を別ファイルで行うためにrequire("./roots/routing")を使用してrouting.jsを読み込む。
express()インスタンスをapp変数に格納。
インスタンスの中にはuseやsetなどのメソッドやプロパティが含まれている。

process.env.PORT || 4000ポート番号を4000に設定。
app.useを用いてミドルウェア関数を実行。(useではパスの設定も可能)

app.use(morgan('dev'))
HTTPログを可視化する。

app.set(`view engine`,`ejs`)※この部分だけでは、ejsからHTMへコンパイルできない。
サーバーの動作を構成する。第一引数をview engineにして第二引数をテンプレートエンジンにする。

app.use(express.urlencoded({extended: false}))
クライアントがリクエストを送った際の本文のみを解析する。
inputで入力した値を取得する際に必要。

app.use(`/data`,router)
第一引数はURLパスを設定。
パスを設定することでrouterのパスと結び付けれる。
別ファイルのrouterミドルウェア関数を実行。

app.use(express.static(`views`))
静的ファイルを提供する。
引数に設定したルートディレクトリに存在する静的ファイルを画面へ出力させる。
CSS,HTMLなどが可能。

app.listen(port)
設定したポート番号でサーバーを立てる。
下記のようにコンソールで出力すれば、URLを確認できる。(エディターがVSコードなら直接URLから飛べる)

script.js
app.listen(port,() => console.log(`http://localhost:${port}/data`));

####②routing.jsで下記のコードを記載。

routing.js
const express = require(`express`);
const router = express.Router();
let mysql = require(`mysql2`);
let user = {
    host: `localhost`,
    user: `root`,
    password: `hiromu4675nogi`,
    database: `product`
}
let connection = mysql.createConnection(user);
router.get(`/`,(req,res) => {
    res.render(`data`,
    {
        no_data: null,
        navigation: '<P>どの商品をお探しですか?</p>'
    })
});
router.post(`/`,(req,res) => {
    let sql = "select 商品ID,商品名,販売金額,在庫数 from product_data where 商品名 like ?"
    let valus = ['%' + req.body.dataValue + '%']
    connection.query(sql,valus,(err,results) => {
        if(Object.keys(results).length === 0){
            res.render('data',
            {
                results_len: null,
                no_data: '<p>データがありません</p>'
            })
        }else{
            res.render('data',
            {
                obj_list: results,
                no_data: undefined,
                results_len:undefined
            })
        }
    });
});

module.exports = router;

routing.js
const express = require(`express`); 
const router = express.Router(); 

読み込んだexpressインスタンスからRouterオブジェクトを作成する。
Routerオブジェクトを用いることで別ファイルでもミドルウェア関数やルーディングを行える。

routing.js
let mysql = require(`mysql2`); 
let user = { 
    host: `localhost`, 
    user: `root`, 
    password: `hiromu4675nogi`, 
    database: `product` 
} 

let connection = mysql.createConnection(user);

mysql2を変数に格納。
userオブジェクトを作成してデーターベース設定を行う。
hostのデフォルトはlocalhost。hostを変えてない人は省略可能。
※user,password,databaseは必須。
下記のコードでも実行できる。

routing.js
let connection = mysql.createConnection({
    host: `localhost`, 
    user: `root`, 
    password: `hiromu4675nogi`, 
    database: `product`
);

routing.js
router.get(`/`,(req,res) => { 
    res.render(`data`, 
    { 
        no_data: null, 
        navigation: '<P>どの商品をお探しですか?</p>' 
    }) 
});

getを受け取った際のルーティングを決める。
上記のgetはクライアントがページに訪れた際の処理です。
get,postなどのHTTPメソッドでは二つの引数を取る。

第一引数はパス設定。
パス設定で/を用いることで現在のURLパスを指します。
script.jsでapp.use(`/data`,router)を設定しているため、現在のパスはhttp://localhost:4000/dataです。

第二引数ではコールバック。
コールバックには二つの引数を設定。
request→HTTPリクエスト(パラメータ、本文、HTTPヘッダーなどのプロパティがある)
response→HTTPレスポンス(サーバーから送信する処理が含まれる)
※慣例によってreq,resと表記します。

res.renderではレンダリングをレスポンスとして返す。
renderはデフォルトでviewsディレクトリーのファイルをレンダリングする。
今回の場合はdata.ejsをレンダリングしたい。そのためには識別子を無くしてdataに設定。

renderではローカル変数を定義できる。例えば下記のコード。

routing.js
res.render(`data`,
{
    navigation: '<P>どの商品をお探しですか?</p>'
})

続いてejsコード。

data.ejs
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<%- no_data %>
    
</body>
</html>

簡単に説明するとejsでは<% %>タグを記すことでjs側で設定した値を出力できる。
つまり**<% %>の空間だけJavascript領域**となる。
(<%- %>ではHTMLタグを検知してくれる。ブラウザで確認すると、pタグが付く)

上記のコードで言えば、 navigationでは'どの商品をお探しですか?'を定義している。
jsで定義している変数名をejsで見つけると、値が格納される。
結果、画面にどの商品をお探しですか?を描画できる。

続いてpostルーティングを設定。

routing.js
router.post(`/`,(req,res) => { 
    let sql = "select 商品ID,商品名,販売金額,在庫数 from product_data where 商品名 like ?" 
    let valus = ['%' + req.body.dataValue + '%'] 
    connection.query(sql,valus,(err,results) => { 
        if(Object.keys(results).length === 0){ 
            res.render('data', 
            { 
                results_len: null, 
                no_data: '<p>データがありません</p>' 
            }) 
        }else{ 
            res.render('data', 
            { 
                obj_list: results, 
                no_data: undefined,
                results_len:undefined
            }) 
        } 
    }); 
});

sql文とプレースホルダーを変数に格納。
プレースホルダーはsql文の?に値が入る。

let valus = ['%' + req.body.dataValue + '%']では、クライアントがリクエストを送った際のbodyを取得しています。
inputで入力された値を取得する際もreq.bodyで取得します。

bodyの続きにはinputタグで設定したnameの値です。
例えばejsで下記のinputタグを設定したと仮定する。

data.ejs
<input type="search" name="dataValue">

nameと合わせないとエラーになる。
更にscript.jsで設定したapp.use(express.urlencoded({extended: false}))も無いとbodyが解析されないので要注意!

sql文ではlike句を設定。この句を使うことで部分検索を実現させています。
例えばパンツを検索すると、データーベースにアクセスして商品名からパンツを含んだ名前を取得する。

mysql.createConnectionを格納した変数connectionを使ってsqlの結果を res.renderで返す。

routing.js
    connection.query(sql,valus,(err,results) => {
        if(Object.keys(results).length === 0){
            if(err) thow err;

            res.render('data',
            {
                results_len: null,
                no_data: '<p>データがありません</p>'
            })
        }else{
            res.render('data',
            {
                obj_list: results,
                no_data: undefined,
                results_len:undefined
            })
        }
    });

queryのコールバック引数にはerr,resultsが付けれます。
errはエラー時の処理。resultsはsql文の結果。(エラー時の処理は、if(err) thow err;のみでも構わない)

resultsの型タイプはobjectです。
objectのkeysのlengthが0ならデータがありませんとレンダリング。
lengthが1以上ならresultsを画面にレンダリング。

if文を用いることでデータがない処理とデータがある処理を行えます。
注意点を述べると、ejsに設定した変数が格納されてないとエラーが起きる。
下記はejsコード。

data.ejs
<body>
    <div class="form_block">
        <form action="/data" method="post" class="form_input">
            <input type="search" name="dataValue" value="">
        </form>
        <div class="productlist">
            <% if (no_data === null) { %>
                
               <%- navigation %>
            <% } else if (results_len === null) { %>
             
                <%- no_data %> 
                
            <% } else { %>
                <table border="1">
                    <tr>
                        <th>商品ID</th>
                        <th>商品名</th>
                        <th>販売金額</th>
                        <th>在庫数</th>
                    </tr>
                    <% for( let i = 0; i< obj_list.length; i++ ) { %>
                        <% let product_ID = obj_list[i].商品ID %>
                        <% let product_name = obj_list[i].商品名 %>
                        <% let product_value = obj_list[i].販売金額 %>
                        <% let pruduct_stock = obj_list[i].在庫数 %> 
                        <tr>
                            <%- `<td>${product_ID}</td>`%>
                            <%- `<td>${product_name}</td>`%>
                            <%- `<td>${product_value}円</td>` %>
                            <%- `<td>${pruduct_stock}</td>` %> 
                        </tr>
                    
                    <% } %>
                </table>
            <% } %> 
        </div>
    </div>
</body>

ejs側でもif文を用いて結果を分岐させています。

<% if (no_data === null) { %>では最初の画面を出力させる処理(検索する前)
<% } else if (results_len === null) { %>ではデータない際の処理。
<% } else { %>ではデータがある際の処理。

仮にデータが存在してもif文を設定しているため、ejsは条件式を読み込んでしまう。
もし、no_dataや results_len の変数を定義しないとエラーが発生する
(no_data is not defined`変数が定義されてないとエラーログが出ます)

エラー防止策として、データを見つけてもno_dataとresults_lenを定義すること。

routing.js
no_data: undefined,
 results_len:undefined

勿論,nullや``でも代用可能です。(if文の条件式を考えながら変数を定義する)

他の方法としては、パスを設定して別画面で結果を返す方法もあります。
しかしその方法では他のファイルも作成したり、レンダリングする必要がある。
検索した結果を返すだけなら一つのファイルでも完結できる。

データがある際の処理は下記の通り。

data.ejs
            <% } else { %>
                <table border="1">
                    <tr>
                        <th>商品ID</th>
                        <th>商品名</th>
                        <th>販売金額</th>
                        <th>在庫数</th>
                    </tr>
                    <% for( let i = 0; i< obj_list.length; i++ ) { %> 
                        <% let product_ID = obj_list[i].商品ID %> 
                        <% let product_name = obj_list[i].商品名 %> 
                        <% let product_value = obj_list[i].販売金額 %> 
                        <% let pruduct_stock = obj_list[i].在庫数 %>  
                        <tr>
                            <%- `<td>${product_ID}</td>`%>
                            <%- `<td>${product_name}</td>`%>
                            <%- `<td>${product_value}円</td>` %>
                            <%- `<td>${pruduct_stock}</td>` %> 
                        </tr>
                    
                    <% } %>
                </table>
            <% } %>

obj_list変数にはsqlの結果が入っています。(jsにて定義しているから)

型タイプはobjectです。
このまま出力させると、オブジェクトのまま画面に表示される。
実用的に施すなら下記のように出力する方が綺麗に作れる。
スクリーンショット (32).png

まず、検索して取得した商品を出力させるためにfor文で回します。

data.ejs
    <% for( let i = 0; i< obj_list.length; i++ ) { %>
        <% let product_ID = obj_list[i].商品ID %>
        <% let product_name = obj_list[i].商品名 %>
        <% let product_value = obj_list[i].販売金額 %>
        <% let pruduct_stock = obj_list[i].在庫数 %> 
    <% } %> 

変数に格納したら画面に出力させる。

data.ejs
<%- `<td>${product_ID}</td>`%>
<%- `<td>${product_name}</td>`%>
<%- `<td>${product_value}円</td>` %>
<%- `<td>${pruduct_stock}</td>` %>

CSSやブラウザ上でjsを行いたい時は、最初に説明した下記ミドルウェア関数を実行する。

app.use(express.static(`views`))

テーブルにカラーをつけることも可能です。

ejsタグについては、他の方が詳しく説明したものがあります。
下記URLからご覧ください。
https://qiita.com/miwashutaro0611/items/36910f2d784ff70a527d

全文コードは下記です。

script.js
const express = require(`express`);
const morgan  = require(`morgan`);
const router = require(`./roots/routing`);
 
const app = express();
const port = process.env.PORT || 4000;
app.use(morgan('dev'));
app.set(`view engine`,`ejs`);
app.use(express.urlencoded({extended: false}));
app.use(`/data`,router);
app.use(express.static(`views`));
app.listen(port,() => console.log(`http://localhost:${port}/data`));
data.ejs
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>search</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="form_block">
        <form action="/data" method="post" class="form_input">
            <input type="search" name="dataValue" value="">
        </form>
        <div class="productlist">
            <% if (no_data === null) { %>
                
               <%- navigation %>
            <% } else if (results_len === null) { %>
             
                <%- no_data %> 
                
            <% } else { %>
                <table border="1">
                    <tr>
                        <th>商品ID</th>
                        <th>商品名</th>
                        <th>販売金額</th>
                        <th>在庫数</th>
                    </tr>
                    <% for( let i = 0; i< obj_list.length; i++ ) { %>
                        <% let product_ID = obj_list[i].商品ID %>
                        <% let product_name = obj_list[i].商品名 %>
                        <% let product_value = obj_list[i].販売金額 %>
                        <% let pruduct_stock = obj_list[i].在庫数 %> 
                        <tr>
                            <%- `<td>${product_ID}</td>`%>
                            <%- `<td>${product_name}</td>`%>
                            <%- `<td>${product_value}円</td>` %>
                            <%- `<td>${pruduct_stock}</td>` %> 
                        </tr>
                    
                    <% } %>
                </table>
            <% } %> 
        </div>
    </div>
</body>
</html>
routing.js
const express = require(`express`);
const router = express.Router();
let mysql = require(`mysql2`);
let user = {
    host: `localhost`,
    user: `root`,
    password: `hiromu4675nogi`,
    database: `product`
}
let connection = mysql.createConnection(user);
router.get(`/`,(req,res) => {
    res.render(`data`,
    {
        no_data: null,
        navigation: '<P>どの商品をお探しですか?</p>'
    })
});
router.post(`/`,(req,res) => {
    let sql = "select 商品ID,商品名,販売金額,在庫数 from product_data where 商品名 like ?"
    let valus = ['%' + req.body.dataValue + '%']
    connection.query(sql,valus,(err,results) => {
        if(err) throw err;
        if(Object.keys(results).length === 0){
            res.render('data',
            {
                results_len: null,
                no_data: '<p>データがありません</p>'
            })
        }else{
            res.render('data',
            {
                obj_list: results,
                no_data: ``,
                results_len:``
            })
        }
    });
});

module.exports = router;

※CSSは省略。

##総評

制作期間は1週間程度掛かりました。
初めてのサーバ領域での開発。試行錯誤の末、何とか形にできた。
ejsでfor文やif文も回せるということは制作の幅も広がる。
覚えておいて損はないでしょう。

後、サーバーに触れるならHTTPの理解は必要です。(僕もHTTPに関してはまだまだ理解が必要です笑)
GET,POST辺りを知れると、理解速度が早くなるかもしれない。

下記URLの記事を参考にしました。
https://www.youtube.com/watch?v=SccSCuHhOw0
https://expressjs.com/ja/4x/api.html
https://qiita.com/Sekky0905/items/dff3d0da059d6f5bfabf

##宣伝
Twitterではプログラミングに関する知見を発信しています。
プログラミングを始めた3ヶ月以内の人に向けた内容です。
ブランク期間を含めると1年半程度、僕はプログラミングに触れてきました。
ぜひ、質問などがあればTwitterのリプやDMでお待ちしております。
投稿頻度は遅めですが、ぜひフォローしていただけると嬉しいです!
@hiromu_edit

尚、過去に制作したポートフォリオをまとめたサイトを公開中です。
ぜひ、参考程度に眺めていってください!
portfolio-0105.jp

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?