본문 바로가기

페이팔기반, e커머스 플랫폼 제작하기 [3편. 카테고리 CRUD구현]

by Recstasy 2021. 11. 3.

 

카테고리는 페이지와 설계가 같다. 왼쪽의 폴더구조만 보더라도 사실상 거의 같다.

 

모델 폴더에 카테고리 스키마를 생성한 뒤, 라우트를 만든다. 라우트 형식은 페이지와 같으므로 복사&붙이기를 한다. 뷰 폴더 역시  페이지와 같으며, '추가', '수정', '읽기'와 관련된 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와 같은 모듈을 사용한다. 대규모 프로젝트의 경우에는 라우트 파일을 서버의 껍데기로 사용하며, 내부를 아키텍처로 설계한 뒤 클래스로 채울 수 있다. 방법은 상황에 따라 여러가지로 응용할 수 있다. 다만 현재의 패턴이 기본기이며, 가장 중요하다. 

 

 

 

댓글

최신글 전체

이미지
제목
글쓴이
등록일