๐Ÿ˜Ž ๊ณต๋ถ€ํ•˜๋Š” ์ง•์ง•์•ŒํŒŒ์นด๋Š” ์ฒ˜์Œ์ด์ง€?

[CRUD์„ ์ด์šฉํ•œ File upload Web] (11) ์ตœ์ข… ์ฝ”๋“œ์•ˆ ๋ณธ๋ฌธ

๐Ÿ‘ฉ‍๐Ÿ’ป ๋ฐฑ์—”๋“œ(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
๋ฐ˜์‘ํ˜•
Comments