๐ฉ๐ป ๋ฐฑ์๋(Back-End)/Node js
[CRUD์ ์ด์ฉํ File upload Web] (11) ์ต์ข ์ฝ๋์
์ง์ง์ํ์นด
2023. 3. 11. 21:40
728x90
๋ฐ์ํ
<๋ณธ ๋ธ๋ก๊ทธ๋ DCodeMania ์ ์ ํ๋ธ๋ฅผ ์ฐธ๊ณ ํด์ ๊ณต๋ถํ๋ฉฐ ์์ฑํ์์ต๋๋ค :-)>
=> CRUD App With Image Upload Using NodeJs, ExpressJs, MongoDB & EJS Templating Engine
๐ฅ user์ ํ๋กํ ๋ชฉ๋ก์ ์์ฑํ๊ณ ์์ ํ๊ณ ์ญ์ ํ๊ณ MongoDB์ ์ ์ฅํ๊ธฐ
์ฐธ๊ณ : https://github.com/gani0325/2023/tree/main/Web/ImageUpload
๐ง models/user.js
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
phone: {
type: String,
required: true,
},
image: {
type: String,
required: true,
},
created: {
type: Date,
required: true,
default: Date.now,
}
}, { collection: 'node_crud' });
module.exports = mongoose.model("User", userSchema);
๐ง routes/routes.js
const express = require("express");
const router = express.Router();
const User = require("../models/user");
const multer = require("multer");
const fs = require("fs");
const { resourceLimits } = require("worker_threads");
// image upload
var storage = multer.diskStorage({
// ํ์ผ์ด ์
๋ก๋๋ ๊ฒฝ๋ก ์ค์
destination: function (req, file, cb) {
cb(null, "./uploads");
},
// timestamp๋ฅผ ์ด์ฉํด ์๋ก์ด ํ์ผ๋ช
์ค์
filename: function (req, file, cb) {
cb(null, file.fieldname + "_" + Date.now() + "_" + file.originalname);
}
})
var upload = multer({
storage: storage,
}).single("image"); // ํ ๊ฐ์ ์ด๋ฏธ์ง ์ฒ๋ฆฌ
// Insert an user into database route
router.post("/add", upload, (req, res) => {
const user = new User({
name: req.body.name,
email: req.body.email,
phone: req.body.phone,
image: req.file.filename,
});
user.save().then((err) => {
// index.js ์ res.locals.message ์์
req.session.message = {
type: "success",
message: "User added successsfull!"
};
// ์ง์ ๋ ๋ค๋ฅธ URL๋ก ์ฌ์์ฒญ (ํ)
res.redirect("/");
}).catch((err) => {
res.json({ message: err.message, type: "danger" });
})
});
// 1) Get all users route
router.get("/", async (req, res) => {
const user = await User.find();
try {
// Query๋ฅผ ์ด์ฉํ ๋ ํ๋ก๋ฏธ์ค๋ฅผ ๋ฆฌํด๋ฐ๊ณ ์ถ๋ค๋ฉด exec() ๋ฉ์๋๋ฅผ ์ด์ฉ
// find()๋ฅผ ์คํํ์ฌ Query์ ์ธ์คํด์ค๋ฅผ ๋ฆฌํด
res.render("index", {
title: "Home Page",
// ๊ถ๊ธ : ๋ ์ error ์กํ๋?
users: user
});
} catch (error) {
(error) => {
res.json({ message: error.message });
}
};
});
router.get("/add", (req, res) => {
res.render("addUsers", { title: "Add users" });
})
// 2) Edit an user routes
router.get("/edit/:id", async (req, res) => {
let id = req.params.id;
const user = await User.findById(id);
try {
if (user == null) {
res.redirect("/");
} else {
res.render("editUsers", {
title: "Edit User",
user: user,
});
}
} catch (err) {
res.redirect("/");
}
});
// 3) Update user route
router.post("/update/:id", upload, async (req, res) => {
let id = req.params.id;
let new_image = "";
if (req.file) {
new_image = req.file.filename;
try {
// ๋๊ธฐ ๋ฐฉ์์ผ๋ก ํ์ผ ์ ๋ณด ์ฝ๊ธฐ
fs.readFileSync("./uploads/" + req.body.old_image);
} catch (err) {
console.log(err);
}
} else {
req.image = req.body.old_image;
}
// ์ฒซ ๋ฒ์งธ ์ธ์๋ ์
๋ฐ์ดํธ ํ๊ณ ์ ํ๋ ๋ฌธ์์ id, ๋ ๋ฒ์งธ ์ธ์๋ ์
๋ฐ์ดํธ ํ ์ ๋ณด ํน์ ๋ด์ฉ
const user = await User.findByIdAndUpdate(id, {
name: req.body.name,
email: req.body.email,
phone: req.body.phone,
image: new_image
});
try {
req.session.message = {
type: "success",
message: "User updated successfully!",
};
res.redirect("/");
} catch (err) {
res.json({ message: err.message, type: "danger" });
}
});
// 4) Delete user route
router.get("/delete/:id", async (req, res) => {
let id = req.params.id;
// id ๋ฅผ ์ฐพ์์ ์ง์
await User.findByIdAndRemove(id);
try {
req.session.message = {
type: "info",
message: "User deleted successfully!"
};
res.redirect("/");
} catch (err) {
res.json({ message: err.message });
}
});
module.exports = router;
๐ง views/layout/footer.ejs
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"
integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.0-beta2/js/bootstrap.bundle.min.js"
integrity="sha512-43iShtbiyImxjjU4a9rhXBy7eKtIsrpll8xKhe1ghKqh5NyfME8phZs5JRFZpRBe1si44WM3tNmnqMym7JRmDQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- ์ด์ /๋ค์ ํ์ด์ง ์ด๋ -->
<script src="https://cdn.datatables.net/v/bs4/dt-1.13.3/datatables.min.js"></script>
<script>
// ํ์๋ ํ
์ด๋ธ์ ์ด๊ธฐํํ์๋ ํ
์ด๋ธ์ ์ด๊ธฐํ => ์ด์ /๋ค์ ํ์ด์ง ๊ธฐ๋ฅ ํด์ค
$(document).ready(function () {
$("table").DataTable({
order: [0, "desc"]
});
});
</script>
</body>
</html>
๐ง views/layout/header.ejs
<!DOCTYPE html>
<html lang="en">
<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>
<%= title %>
</title>
<!-- ๋ถํธํธ๋ฉ ์ฌ์ฉ -->
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.0-beta2/css/bootstrap.min.css"
integrity="sha512-aqT9YD5gLuLBr6ipQAS+72o5yHKzgZbGxEh6iY8lW/r6gG14e2kBTAJb8XrxzZrMOgSmDqtLaF76T0Z6YY2IHg=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css"
integrity="sha512-HK5fgLBL+xu6dm/Ii3z4xhlSUyZgTT9tuc/hSrtw6uzJOvgRr2a9jyxxT1ely+B+xFAmJKVSTbpM/CuL7qxO8w=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- ๋ค์ ํ์ด์ง ์ด๋ํ๋ ํ
์ด๋ธ ์ฌ์ฉ -->
<link href="https://cdn.datatables.net/v/bs4/dt-1.13.3/datatables.min.css" rel="stylesheet" />
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a href="/" class="navbar-brand"><i class="fas fa-code me-2"></i>GANI</a>
<button class="navbar-toggler" data-bs-target="#my-nav" data-bs-toggle="collapse" aria-controls="my-nav"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div id="my-nav" class="collapse navbar-collapse">
<ul class="navbar-nav ms-auto">
<li class="nav-item active">
<a class="nav-link" href="/"><i class="fas fa-home me-1"></i>Home</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/add"><i class="fas fa-user-plus me-1"></i>Add user</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="#"><i class="fas fa-globe me-1"></i>About</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="#"><i class="fas fa-envelope me-1"></i>Contact</a>
</li>
</ul>
</div>
</div>
</nav>
๐ง views/addUsers.ejs
<%- include("layout/header") %>
<div class="container">
<div class="row">
<div class="col-lg-6 mx-auto mt-4">
<div class="card shadow">
<div class="card-header bg-primary">
<h3 class="text-light">Add New User</h3>
</div>
<div class="card-body p-4">
<!-- add๋ router ๊ฒฝ๋ก ๊ฐ์์ผ ๋ ๋ฏ? -->
<form action="/add" method="post" id="add-form" enctype="multipart/form-data">
<div class="mb-3">
<label for="name">Name</label>
<input type="text" name="name" class="form-control form-control-lg" placeholder="Enter name" required />
</div>
<div class="mb-3">
<label for="email">Email</label>
<input type="email" name="email" class="form-control form-control-lg" placeholder="Enter email"
required />
</div>
<div class="mb-3">
<label for="phone">Phone</label>
<input type="tel" name="phone" class="form-control form-control-lg" placeholder="Enter phone"
required />
</div>
<div class="mb-3">
<label for="image" class="form-label">Select Image</label>
<input type="file" name="image" class="form-control form-control-lg" required />
</div>
<div class="mb-3 d-grid">
<input type="submit" name="submit" value="Add User" class="btn btn-primary btn-lg" />
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<%- include("layout/footer") %>
๐ง views/editUsers.ejs
<%- include("layout/header") %>
<div class="container">
<div class="row">
<div class="col-lg-6 mx-auto mt-4">
<div class="card shadow">
<div class="card-header bg-success">
<h3 class="text-light">Edit User (<%= user.name %>)</h3>
</div>
<div class="card-body p-4">
<!-- add๋ router ๊ฒฝ๋ก ๊ฐ์์ผ ๋ ๋ฏ? -->
<form action="/update/<%= user._id %>" method="post" id="add-form" enctype="multipart/form-data">
<div class="mb-3">
<label for="name">Name</label>
<input type="text" name="name" class="form-control form-control-lg" placeholder="Enter name"
value="<%= user.name %>" required />
</div>
<div class="mb-3">
<label for="email">Email</label>
<input type="email" name="email" class="form-control form-control-lg" placeholder="Enter email"
value="<%= user.email %>" required />
</div>
<div class="mb-3">
<label for="phone">Phone</label>
<input type="tel" name="phone" class="form-control form-control-lg" placeholder="Enter phone"
value="<%= user.phone %>" required />
</div>
<div class="mb-3">
<label for="image" class="form-label">Select Image</label>
<input type="file" name="image" class="form-control form-control-lg" />
<img src="/<%= user.image %>" width="100" class="img-thumbnail mt-1">
</div>
<input type="hidden" name="old_image" value="<%= user.image %>">
<div class="mb-3 d-grid">
<input type="submit" name="submit" value="Update User" class="btn btn-success btn-lg" />
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<%- include("layout/footer") %>
๐ง views/index.ejs
<%- include("layout/header") %>
<div class="container">
<div class="row my-4">
<div class="col-lg-12">
<!-- router ์ ์๋ locals.message ๊ฐ์ ธ์ด -->
<!--
1. <% %> : EJS ์์ ์์ค๋ด ์์ ์คํ๋์ง๋ง ๋ณด์ด์ง๋ ์๋ ํ๊ทธ
2. <%- %> : <% %> ํ๊ทธ์๋ ์กฐ๊ธ ๋ค๋ฅด๊ฒ HTML ์ฝ๋๋ฅผ ๋ ๊ฒ(Raw)๋ก ๋ณด์
3. <%= %> : ์ด์ค์ผ์ดํ ๋ฌธ์๋ฅผ ํฌํจ, ๋ค๋ฅธ ํ์ด์ง์์ ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ค ๋
-->
<% if (message) { %>
<div class="alert alert-dismissible fade show alert-<%= message.type %>" role="alert">
<button class="btn-close" type="button" data-bs-dismiss="alert" aria-label="Close"></button>
<strong>
<%= message.message %>
</strong>
</div>
<% } %>
<div class="table-responsive">
<% if (users !="" ) { %>
<!-- ํ๋ฉด ํฌ๊ธฐ์ ๋ฐ๋ผ ํจ๊ณผ์ ์ผ๋ก ์ฝํ
์ธ ๊ฐ ํ์๋๋๋ก ํ
์ด๋ธ์ ์กฐ์ -->
<table class="table table-striped">
<!-- ํ
์ด๋ธ์ ์ด์ ๋จธ๋ฆฌ๊ธ์ธ ํ๋ค์ ์งํฉ -->
<thead class="table table-striped text-center my-3">
<tr class="table-dark">
<th>ID</th>
<th>Image</th>
<th>Name</th>
<th>E-mail</th>
<th>Phone</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<% users?.forEach((row, index)=> { %>
<tr class="align-middle">
<td>
<%= index %>
</td>
<td><img src="<%= row.image %>" width="50" class="img-thumbnail"></td>
<td>
<%= row.name %>
</td>
<td>
<%= row.email %>
</td>
<td>
<%= row.phone %>
</td>
<td>
<a href="/edit/<%= row._id %>" class="text-success"><i class="fas fa-edit fa-lg mx-1"></i></a>
<a href="/delete/<%= row._id %>" class="text-danger"><i
class="fas fa-trash fa-lg mx-1"></i></a>
</td>
</tr>
<% }) %>
</tbody>
</table>
<% } else { %>
<h1 class="text-center text-secondary mt-S">No users found in the databases!</h1>
<% } %>
</div>
</div>
</div>
</div>
<%- include("layout/footer") %>
๐ง index.js
// imports
require("dotenv").config();
const express = require("express");
const mongoose = require("mongoose");
const session = require("express-session");
// express-session
// 1. ํ์ผ์ ์ ์ฅ
// 2. DB์ ์ ์ฅ
// 3. Memory ์ ์ ์ฅ
const app = express();
const PORT = process.env.PORT || 8000;
// Database ์ฐ๊ฒฐ
mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true }); // useNewUrlParser : ์๋ฌ ๋ฐฉ์ง
const db = mongoose.connection;
db.on("error", (error) => console.log(error));
db.once("open", () => console.log("๐ Connected to the database!"));
// Middleware ์ฐ๊ฒฐ
app.use(express.urlencoded({ extended: false }));
app.use(express.json()); // JSONํํ์ ๋ฐ์ดํฐ๋ฅผ ํด์
app.use(session({
secret: process.env.SECRET, // ์ํธํ๋ฅผ ์ํ keygen. ๋ณดํต env์ ๋ฃ์ด์ ์ ๋ฌ
saveUninitialized: true, // ์ธ์
์ ์ ์ฅํ ๋ด์ญ์ด ์๋๋ผ๋ ์ฒ์๋ถํฐ ์ธ์
์ ์์ฑํ ์ง ์ค์
resave: false, // ์ธ์
์ ์ธ์ ๋ ์ ์ฅํ ์ง ์ค์ ํจ
cookie: { // ์ฟ ํค์ ์ ํจ์๊ฐ
maxAge: 86400000, // 24 hours (= 24 * 60 * 60 * 1000 ms)
},
})
);
// ๊ณตํต EJS ํ์ผ(header๋ footer)์์ ์ฌ์ฉํ ๋ฐ์ดํฐ๋ฅผ ์ธํ
app.use((req, res, next) => {
// ejs ํ์ผ์์ ์ฌ์ฉํ๋ ค๋ฉด 'res.locals.๋ณ์๋ช
= ๊ฐ' ์ ์ด์ฉ
res.locals.message = req.session.message;
delete req.session.message;
next(); // ์ฑ ๋ด์ ๊ทธ ๋ค์ ๋ฏธ๋ค์จ์ด ํจ์๊ฐ ํธ์ถ
});
// uploads ํด๋์ ์ฌ์ง html์ ๋ณด์ด๊ฒ ํ๊ธฐ
app.use(express.static("uploads"));
// set template engine
app.set("view engine", "ejs");
// router
app.use("", require("./routes/routes"));
app.get("/", (req, res) => {
res.send("Hello World");
});
app.listen(PORT, () => {
console.log(`server started at ๐ http://localhost:${PORT}`);
});
๐ง .env
APPLICATION_NAME=
PORT=3000
MONGODB_URI=mongodb+srv://
SECRET=jVTKzz8w1@k^Lrvm*52w
๐ง package.json
{
"name": "imageupload",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start" : "nodemon index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^16.0.3",
"ejs": "^3.1.8",
"express": "^4.18.2",
"express-session": "^1.17.3",
"mongoose": "^7.0.1",
"multer": "^1.4.5-lts.1"
},
"devDependencies": {
"nodemon": "^2.0.21"
}
}
728x90
๋ฐ์ํ