你好世界!在本教程中,我們將使用 Node JS 和 MySQL 建立一個簡單的 CRUD 應用程式。
上圖顯示了該應用程式。它是一個應用程式,可讓您將玩家加入到資料庫並顯示資料庫中的詳細資訊。您也可以刪除和編輯玩家詳細資料。
在加入本教學之前,假設您符合下列要求:
Node JS 安裝在您的 PC 上。
對 Node JS 和 Express JS 有基本了解。
了解 SQL,您應該知道並理解如何查詢資料庫。
phpmyadmin 安裝在您的電腦上。我建議安裝xampp,因為它已經包含 phpmyadmin。
了解如何使用模板引擎——我們將在本教程中使用 ejs)。
您選擇的文字編輯器或 IDE。
這就是該專案的結構。
├── node-mqsql-crud-app (main directory)
├── node_modules
├── public
├── assets
├── img
├── routes
├── index.js
├── player.js
├── views
├── partials
├── header.ejs
├── index.ejs
├── add-player.ejs
├── edit-player.ejs
├── app.js
在適當的目錄中開啟命令提示字元並鍵入以下命令:
mkdir node-mysql-crud-app
然後透過鍵入以下命令更改到該目錄
cd node-mysql-crud-app
在專案目錄中開啟命令提示字元並鍵入以下命令:
npm init
成功建立應用程式需要以下模組。
express :用於建立句柄路由並處理來自客戶端的請求。
express-fileupload :圍繞 Busboy 的簡單快速檔案上傳中間件。
body-parser :用於解析來自客戶端的傳入請求。
mysql :MySQL 的 Node JS 驅動程式。
ejs :為應用程式渲染 html 頁面的模板引擎。
req-flash :用於向視圖發送 flash 訊息
nodemon :全域安裝。它用於監視文件的更改並自動重新啟動伺服器。
鍵入以下命令將前 7 個模組安裝為依賴項。
npm install express express-fileupload body-parser mysql ejs req-flash --save
然後鍵入以下命令以在您的 PC 上全域安裝最後一個模組。
npm install nodemon -g
複製下面的命令並導航到 phpmyadmin 儀表板,然後在控制台(通常位於頁面底部)中執行以下查詢,以便為應用程式建立資料庫和表格。
CREATE DATABASE socka;
CREATE TABLE IF NOT EXISTS `players` (
`id` int(5) NOT NULL AUTO_INCREMENT,
`first_name` varchar(255) NOT NULL,
`last_name` varchar(255) NOT NULL,
`position` varchar(255) NOT NULL,
`number` int(11) NOT NULL,
`image` varchar(255) NOT NULL,
`user_name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;
header.ejs檔案將位於/views/partials資料夾中,該資料夾將包含在專案的其餘部分中。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<!--<link rel="stylesheet" href="/assets/css/custom.css">-->
<title><%= title %></title>
</head>
<style>
.table-wrapper {
margin-top: 50px;
}
.player-img {
width: 40px;
height: 40px;
}
.add-player-form {
margin-top: 50px;
}
</style>
<body>
<div class="page-wrapper">
<nav class="navbar navbar-light bg-light">
<span class="navbar-brand mb-0 h1" ><a href="/">Socka Players</a></span>
<a class="float-right" href="/add" title="Add a New Player">Add a Player</a>
</nav>
這是應用程式的主頁,其中包含一個顯示所有玩家清單的表格。
<% include partials/header.ejs %>
<div class="table-wrapper">
<% if (players.length > 0) {%>
<table class="table table-hovered">
<thead class="thead-dark">
<tr>
<th scope="col">ID</th>
<th scope="col">Image</th>
<th scope="col">First Name</th>
<th scope="col">Last Name</th>
<th scope="col">Position</th>
<th scope="col">Number</th>
<th scope="col">Username</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
<% players.forEach((player, index) => { %>
<tr>
<th scope="row"><%= player.id %></th>
<td><img src="/assets/img/<%= player.image %>" class="rounded-circle player-img" alt=""></td>
<td><%= player.first_name %></td>
<td><%= player.last_name %></td>
<td><%= player.position %></td>
<td><%= player.number %></td>
<td>@<%= player.user_name %></td>
<td>
<a href="/edit/<%= player.id %>" target="_blank" rel="noopener" class="btn btn-sm btn-success">Edit</a>
<a href="/delete/<%= player.id %>" class="btn btn-sm btn-danger">Delete</a>
</td>
</tr>
<% }) %>
</tbody>
</table>
<% } else { %>
<p class="text-center">No players found. Go <a href="/add" >here</a> to add players.</p>
<% } %>
</div>
</div>
</body>
</html>
此頁麵包含將新玩家新增至資料庫的表單。
<% include partials/header.ejs %>
<div class="container">
<% if (message != '') { %>
<p class="text-center text-danger"><%= message %></p>
<% } %>
<form class="add-player-form" action="" method="post" enctype="multipart/form-data">
<div class="form-row">
<div class="form-group col-md-4">
<input type="text" class="form-control" name="first_name" id="first-name" placeholder="First Name" required>
</div>
<div class="form-group col-md-4">
<input type="text" class="form-control" name="last_name" id="last-name" placeholder="Last Name" required>
</div>
<div class="form-group col-md-4">
<input type="text" class="form-control" name="username" id="username" placeholder="Username" required>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<input type="number" class="form-control" name="number" id="number" placeholder="Number" required>
</div>
<div class="form-group col-md-6">
<select id="position" name="position" class="form-control" required>
<option selected disabled>Choose position</option>
<option>Goalkeeper</option>
<option>Defender</option>
<option>Midfielder</option>
<option>Forward</option>
</select>
</div>
<div class="col-md-12">
<label for="player-img"><b>Player Image</b></label><br>
<input type="file" name="image" id="player-img" class="" required>
</div>
</div>
<button type="submit" class="btn btn-primary float-right">Add Player</button>
</form>
</div>
</div>
</body>
</html>
此頁麵包含用於編輯新增至資料庫中的玩家的表單。
<% include partials/header.ejs %>
<div class="container">
<% if (message) { %>
<p class="text-center text-danger"><%= message %></p>
<% } %>
<% if (player) { %>
<form class="add-player-form" action="" method="post" enctype="multipart/form-data">
<div class="form-row">
<div class="form-group col-md-4">
<label for="first-name">First Name</label>
<input type="text" class="form-control" name="first_name" id="first-name" value="<%= player.first_name %>" required>
</div>
<div class="form-group col-md-4">
<label for="last-name">Last Name</label>
<input type="text" class="form-control" name="last_name" id="last-name" value="<%= player.last_name %>" required>
</div>
<div class="form-group col-md-4">
<label for="username">Username</label>
<input type="text" class="form-control" name="username" id="username" value="<%= player.user_name %>" required disabled title="Username cannot be edited.">
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="number">Number</label>
<input type="number" class="form-control" name="number" id="number" placeholder="Number" value="<%= player.number %>" required>
</div>
<div class="form-group col-md-6">
<label for="position">Position</label>
<select id="position" name="position" class="form-control" required>
<option selected><%= player.position %></option>
<option>Goalkeeper</option>
<option>Centre Back</option>
<option>Right Back</option>
<option>Left Back</option>
<option>Defensive Midfielder</option>
<option>Central Midfielder</option>
<option>Attacking Midfielder</option>
<option>Right Wing Forward</option>
<option>Left Wing Forward</option>
<option>Striker</option>
</select>
</div>
</div>
<button type="submit" class="btn btn-success float-right">Update Player</button>
</form>
<% } else { %>
<p class="text-center">Player Not Found. Go <a href="/add">here</a> to add players.</p>
<% } %>
</div>
</div>
</body>
</html>
const express = require('express');
const fileUpload = require('express-fileupload');
const bodyParser = require('body-parser');
const mysql = require('mysql');
const path = require('path');
const app = express();
// const {getHomePage} = require('./routes/index');
// const {addPlayerPage, addPlayer, deletePlayer, editPlayer, editPlayerPage} = require('./routes/player');
const port = 5000;
// create connection to database
// the mysql.createConnection function takes in a configuration object which contains host, user, password and the database name.
const db = mysql.createConnection ({
host: 'localhost',
user: 'root',
password: '',
database: 'socka'
});
// connect to database
db.connect((err) => {
if (err) {
throw err;
}
console.log('Connected to database');
});
global.db = db;
// configure middleware
app.set('port', process.env.port || port); // set express to use this port
app.set('views', __dirname + '/views'); // set express to look in this folder to render our view
app.set('view engine', 'ejs'); // configure template engine
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json()); // parse form data client
app.use(express.static(path.join(__dirname, 'public'))); // configure express to use public folder
app.use(fileUpload()); // configure fileupload
// routes for the app
/*
app.get('/', getHomePage);
app.get('/add', addPlayerPage);
app.get('/edit/:id', editPlayerPage);
app.get('/delete/:id', deletePlayer);
app.post('/add', addPlayer);
app.post('/edit/:id', editPlayer);
*/
// set the app to listen on the port
app.listen(port, () => {
console.log(`Server running on port: ${port}`);
});
在上面的程式碼中,需要模組,然後建立與資料庫的連接。 mysql.createConnection函數接受一個物件,其中包含所連接的資料庫的配置。在下一條語句中,正在連接資料庫。在命令提示字元下執行以下程式碼來執行伺服器。
nodemon app.js
您的控制台應顯示以下結果:
將以下程式碼複製到/routes目錄中的 index.js 檔案中。
module.exports = {
getHomePage: (req, res) => {
let query = "SELECT * FROM `players` ORDER BY id ASC"; // query database to get all the players
// execute query
db.query(query, (err, result) => {
if (err) {
res.redirect('/');
}
res.render('index.ejs', {
title: Welcome to Socka | View Players
,players: result
});
});
},
};
db.query函數查詢資料庫。它接受查詢和字串以及一個接受兩個參數的回調,如果查詢成功,結果將傳遞到res.render函數中的視圖。
player.js檔案將包含玩家頁面的所有路由,例如新增玩家、更新玩家詳細資訊和刪除玩家。
const fs = require('fs');
module.exports = {
addPlayerPage: (req, res) => {
res.render('add-player.ejs', {
title: Welcome to Socka | Add a new player
,message: ''
});
},
addPlayer: (req, res) => {
if (!req.files) {
return res.status(400).send("No files were uploaded.");
}
let message = '';
let first_name = req.body.first_name;
let last_name = req.body.last_name;
let position = req.body.position;
let number = req.body.number;
let username = req.body.username;
let uploadedFile = req.files.image;
let image_name = uploadedFile.name;
let fileExtension = uploadedFile.mimetype.split('/')[1];
image_name = username + '.' + fileExtension;
let usernameQuery = "SELECT * FROM `players` WHERE user_name = '" + username + "'";
db.query(usernameQuery, (err, result) => {
if (err) {
return res.status(500).send(err);
}
if (result.length > 0) {
message = 'Username already exists';
res.render('add-player.ejs', {
message,
title: Welcome to Socka | Add a new player
});
} else {
// check the filetype before uploading it
if (uploadedFile.mimetype === 'image/png' || uploadedFile.mimetype === 'image/jpeg' || uploadedFile.mimetype === 'image/gif') {
// upload the file to the /public/assets/img directory
uploadedFile.mv(`public/assets/img/${image_name}`, (err ) => {
if (err) {
return res.status(500).send(err);
}
// send the player's details to the database
let query = "INSERT INTO `players` (first_name, last_name, position, number, image, user_name) VALUES ('" +
first_name + "', '" + last_name + "', '" + position + "', '" + number + "', '" + image_name + "', '" + username + "')";
db.query(query, (err, result) => {
if (err) {
return res.status(500).send(err);
}
res.redirect('/');
});
});
} else {
message = "Invalid File format. Only 'gif', 'jpeg' and 'png' images are allowed.";
res.render('add-player.ejs', {
message,
title: Welcome to Socka | Add a new player
});
}
}
});
},
editPlayerPage: (req, res) => {
let playerId = req.params.id;
let query = "SELECT * FROM `players` WHERE id = '" + playerId + "' ";
db.query(query, (err, result) => {
if (err) {
return res.status(500).send(err);
}
res.render('edit-player.ejs', {
title: Edit Player
,player: result[0]
,message: ''
});
});
},
editPlayer: (req, res) => {
let playerId = req.params.id;
let first_name = req.body.first_name;
let last_name = req.body.last_name;
let position = req.body.position;
let number = req.body.number;
let query = "UPDATE `players` SET `first_name` = '" + first_name + "', `last_name` = '" + last_name + "', `position` = '" + position + "', `number` = '" + number + "' WHERE `players`.`id` = '" + playerId + "'";
db.query(query, (err, result) => {
if (err) {
return res.status(500).send(err);
}
res.redirect('/');
});
},
deletePlayer: (req, res) => {
let playerId = req.params.id;
let getImageQuery = 'SELECT image from `players` WHERE id = "' + playerId + '"';
let deleteUserQuery = 'DELETE FROM players WHERE id = "' + playerId + '"';
db.query(getImageQuery, (err, result) => {
if (err) {
return res.status(500).send(err);
}
let image = result[0].image;
fs.unlink(`public/assets/img/${image}`, (err) => {
if (err) {
return res.status(500).send(err);
}
db.query(deleteUserQuery, (err, result) => {
if (err) {
return res.status(500).send(err);
}
res.redirect('/');
});
});
});
}
};
該文件處理玩家頁面的所有 post 和 get 請求。新增玩家函數包含將玩家的圖像上傳到/public/assets/img目錄並將玩家的詳細資訊傳送到資料庫的函數。
前往app.js並取消註解以下行
// const {getHomePage} = require('./routes/index');
// const {addPlayerPage, addPlayer, deletePlayer, editPlayer, editPlayerPage} = require('./routes/player');
/*
app.get('/', getHomePage);
app.get('/add', addPlayerPage);
app.get('/edit/:id', editPlayerPage);
app.get('/delete/:id', deletePlayer);
app.post('/add', addPlayer);
app.post('/edit/:id', editPlayer);
*/
刪除註解行後,檢查命令提示字元以確保程式碼沒有錯誤,然後前往瀏覽器並開啟http://localhost:5000 。將顯示索引頁面,由於尚未新增任何玩家,因此該頁面將類似於以下頁面:
點擊頁面上的“新增玩家”連結,將加載“新增玩家”頁面,然後填寫表格以新增玩家。新增球員後,主頁會以表格形式顯示已新增的球員。像這樣
如果遇到錯誤,請在評論區提請我注意,或查看github上專案的repo。
該程式碼並不完美,請隨意改進並發送拉取請求。
原文出處:https://dev.to/achowba/build-a-simple-app-using-node-js-and-mysql-19me