카테고리는 페이지와 설계가 같다. 왼쪽의 폴더구조만 보더라도 사실상 거의 같다.
모델 폴더에 카테고리 스키마를 생성한 뒤, 라우트를 만든다. 라우트 형식은 페이지와 같으므로 복사&붙이기를 한다. 뷰 폴더 역시 페이지와 같으며, '추가', '수정', '읽기'와 관련된 ejs파일을 복사 & 붙이기로 넣어준다. 대략적인 순서는 '모델 스키마 생성 => index.js 라우터 추가 => 라우터 랜더링 => 뷰 파일 추가 => 테스트' 이다. 이 과정에서 핵심적인 역할을 담당하는 부분은 admin_categories.js이다. '몽고DB'부터 라우터, 미들웨어를 추가하고, 프론트 파일 랜더링까지 담당하기 때문에 상대적으로 DB파일을 전반적으로 관리하기 때문이다.
먼저, 모델 스키마부터 추가해보자.
1 스키마 추가 :: categorySchema
아래와 같이 카테고리스키마를 추가한다.
const mongoose = require('mongoose'); // Category Schema let CategorySchema = mongoose.Schema({ title: { type: String, required: true }, slug: { type: String } }); let Category = module.exports = mongoose.model('Category', CategorySchema); |
[ categorySchema.js ]
2 index.js
index.js에서 '/admin/categories'경로와 'admin_categories.js' 라우터를 연결하는 코드를 작성한다.
const express = require('express'); const path = require('path'); const mongoose = require('mongoose'); //중략... //라우팅 let pages = require('./routes/pages.js'); let adminPages = require('./routes/admin_pages.js'); let adminCategories = require('./routes/admin_categories.js'); app.use('/', pages); app.use('/admin/pages', adminPages); app.use('/admin/categories', adminCategories); //https 서버연결 app.listen(3005); |
[ index.js ]
3 라우트 & 뷰 :: admin_categories.js
admin_categories.js 라우터 파일은 DB모델을 받아서 ejs 뷰 파일에 데이터를 전달하는 역할을 담당한다.
3-1 라우트(Get) :: /
url '/admin/categories'의 진입점을 담당하는 ejs뷰 파일을 'admin'폴더 아래 'categories.ejs'라는 이름으로 생성한다. 그리고 해당 경로를 아래와 같이 지정한다. express, route, express-validator, category스키마를 윗부분에 선언해준다. 참고사항으로 express-validator 6.0버전의 경우, 아래와 같이 변경됐다. app.use()를 사용할 필요가 없다. 관련 사항은 express-validator npm 웹사이트에서 확인할 수 있다.(https://express-validator.github.is/docs/custom-validators-sanitizers.html)
let express = require('express'); let router = express.Router(); const {check, validationResult} = require('express-validator'); //Get Category model let Category = require('../models/category'); router.get('/', (req, res)=>{ Category.find((err, categories)=>{ if(err) return console.log(err); res.render('admin/categories', { categories : categories }); }); }); |
[ admin_categories.js ]
' / ' 라우터는 Category DB 데이터를 쿼리한 뒤, categories객체에 담아서 categories.ejs에 전달한다. 해당 파일에는 '수정', '삭제'를 위한 스키마 DB id값이 포함된다.
3-2 뷰 :: categories.ejs
categories.ejs파일은 현재 생성된 카테고리를 전체적으로 볼 수 있는 뷰 페이지이다. 페이지 메인뷰에서 사용했던 부트스트랩 파일을 복사한 뒤, 'page'부분만 'Categories'로 변경한다.
<%- include('../layouts/adminheader') %> <h2 class="page-title"> Categories </h2> <a href="/admin/categories/add-category" class="btn btn-primary">Add a new category</a> <br><br> <table class="table table-striped"> <thead> <tr> <th>Category</th> <th>Edit</th> <th>Delete</th> </tr> </thead> <tbody> <% categories.forEach((cat)=>{ %> <tr> <td><%= cat.title %></td> <td><a href="/admin/categories/edit-category/<%=cat._id %>">Edit</a></td> <td><a class="confirmDeletion" href="/admin/categories/delete-category/<%=cat._id %>">Delete</a></td> </tr> <% }); %> </tbody> </table> <%-include('../layouts/adminfooter') %> |
[ categories.ejs ]
3-3 라우트(Get) :: /add-category
카테고리를 추가하는 뷰 랜더링을 생성한다.
// Get add category router.get('/add-category', (req, res)=>{ let title = ""; res.render('admin/add_category', { title:title }); }); |
[ admin_categories.js ]
3-4 뷰 :: add_category.ejs
카테고리를 추가하는 뷰파일 역시 add_page.ejs파일을 복사한 뒤 page부분만 category로 변경한다.
<%- include('../layouts/adminheader') %> <h2 class="page-title"> Add a Category </h2> <a href="/admin/categories" class="btn btn-primary">Back to all categories</a> <br><br> <form method="post" action="/admin/categories/add-category"> <div class="form-group"> <label for="">Title</label> <input type="text" class="form-control" name="title" value="<%= title %>" placeholder="Title"> </div> <button class="btn btn-default">Submit</button> </form> <%-include('../layouts/adminfooter') %> |
[add_category.ejs]
3-5 라우트(Post) :: /add-category
add_categories.ejs에서 Form을 통해 전달된 데이터는 Post방식의 '/add-category'로 전달된다. post방식에서는 get과 달리 사용자가 작성한 폼의 유효성을 검사가 필요하다. 유효성 검사는 ` express-validator의 check( ).유효검사 메서드.run(req); `부분에서 이뤄진다. express-validator는 6.0버전부터 'async-await'프로미스 구문을 사용한다. 이와 관련 문서는 https://github.com/shwei/express-validator-migrate-5-to-6/blob/main/v5_to_v6.diff 에서 확인할 수 있다.
//Post add category router.post('/add-category', async(req, res)=>{ await check('title', 'title must have a value.').notEmpty().run(req); let title = req.body.title; let slug = title.replace(/\s+/g, '-').toLowerCase(); //validator let validatorResults = await validationResult(req); if(validatorResults.errors[0] !== undefined){ //에러가 있을 경우 res.render('admin/add_category', { errors : validatorResults.errors, title:title }); }else{ Category.findOne({slug : slug}, (err, category)=>{ if(category){ req.flash('danger', 'Category title exists, choose another.'); res.render('admin/add_category',{ title:title }); }else{ var category = new Category({ title : title, slug : slug }); category.save((err)=>{ if(err){ return console.log(err); } req.flash('sucess', 'Category added!'); res.redirect('/admin/categories'); }); } }); } }); |
[ admin_categories.js ]
validatorResults 객체는 express-validator의 체크결과를 저장한다. 만일 validatorResults의 errors값이 존재한다면, 사용자의 폼에 문제가 있음을 의미한다. 이때는 'admin/add_category'뷰 파일에 errors값을 전달한다. 그 외의 경우(else)에는 Category DB를 쿼리한다. 만일 같은 slug명이 존재한다면 categoryDB에 저장되지 않는다.
3-6 라우트(Get) :: /edit-category/:id
edit_category/:id 경로로 접속한 관리자는 클릭한 Doc의 id에 해당하는 데이터를 내려받아서 수정할 수 있다. 따라서 req.params.id값이 필요하며, 해당 id의 DB값을 쿼리한 데이터(title, id값)를 'admin/edit_category.ejs' 뷰 파일로 전달한다.
// Get edit category router.get('/edit-category/:id', (req, res)=>{ Category.findById(req.params.id, (err,category)=>{ if(err) return console.log(err); res.render('admin/edit_category', { title : category.title, id:category._id }); }) }); |
[ admin_categories.js ]
3-7 뷰 :: edit_category.ejs
edit-category/:id 라우트에서 전달한 id값은 edit_category.ejs에서 아래와 같이 사용된다.
<%- include('../layouts/adminheader') %> <h2 class="page-title"> Edit a category </h2> <a href="/admin/categories" class="btn btn-primary">Back to all categories</a> <br><br> <form method="post" action="/admin/categories/edit-category/<%= id %>"> <div class="form-group"> <label for="">Title</label> <input type="text" class="form-control" name="title" value="<%= title %>" placeholder="Title"> </div> <button class="btn btn-default">Submit</button> </form> <%-include('../layouts/adminfooter') %> |
[ edit_category.ejs ]
3-8 라우트(Post) :: /edit-category/:id
edit_category.ejs에서 전달된 폼 데이터는 ' /edit-category/:id'에서 처리된다. 폼 데이터를 추가했던 '/add-category'와 같이 유효성을 체크한 뒤 에러가 없을 경우에는 Category 컬렉션에 저장된다.
//Post edit page router.post('/edit-category/:id', async(req, res)=>{ await check('title', 'title must have a value.').notEmpty().run(req); let title = req.body.title; let slug = title.replace(/\s+/g, '-').toLowerCase(); let id = req.params.id; //validator let validatorResults = await validationResult(req); if(validatorResults.errors[0] !== undefined){ res.render('admin/edit_category', { errors : validatorResults.errors, title:title, id : id }); }else{ Category.findOne({slug : slug, _id:{'$ne':id}}, (err, category)=>{ if(category){ req.flash('danger', 'Category title exists, choose another.'); res.render('admin/edit_category', { title:title, id : id }); }else{ Category.findById(id, (err,category)=>{ if(err) return console.log(err); category.title = title; category.slug = slug; category.save((err)=>{ if(err){ return console.log(err); } req.flash('sucess', 'Category edited!'); res.redirect('/admin/categories/edit-category/'+id); }); }) } }); } }); |
[ admin_categories.js ]
3-9 라우트(Get) :: /delete-category/:id
delete 쿼리는 간단하다. mongoose의 findByIdAndRemove( )메서드를 사용해서 해당id값의 데이터를 다음과 같이 삭제할 수 있다.
//Get delete page router.get('/delete-category/:id', (req, res)=>{ Category.findByIdAndRemove(req.params.id, (err)=>{ if(err) return console.log(err); req.flash('success', 'Category Deleted'); res.redirect('/admin/categories/') }) }); //Exports module.exports = router; |
4 정리
전체적인 패턴은 '라우트(Get) - 뷰 - 라우트(Post) - DB쿼리 - 뷰 - 라우트(Get)' 방식을 반복한다. 이 과정에서 express-validator, express-message와 같은 모듈을 사용한다. 대규모 프로젝트의 경우에는 라우트 파일을 서버의 껍데기로 사용하며, 내부를 아키텍처로 설계한 뒤 클래스로 채울 수 있다. 방법은 상황에 따라 여러가지로 응용할 수 있다. 다만 현재의 패턴이 기본기이며, 가장 중요하다.
'웹개발 자료실 > node & Express 쇼핑몰제작Code' 카테고리의 다른 글
페이팔기반, e커머스 플랫폼 제작하기 [6편. 전역객체 카테고리 구현] (0) | 2021.11.08 |
---|---|
페이팔기반, e커머스 플랫폼 제작 [5편. 전역객체 page구현하기 ] (0) | 2021.11.08 |
페이팔기반, e커머스 플랫폼 제작하기 [4편. 제품페이지 구현하기] (0) | 2021.11.04 |
페이팔기반, e커머스 플랫폼 제작하기 [2편. 페이지 CRUD 구현] (0) | 2021.10.30 |
페이팔기반, e커머스 플랫폼 제작하기 [1편. 설치 & 구조설계] (2) | 2021.10.29 |
댓글