Search
Duplicate
moon
sun

Express 게시판 프로젝트

Express 게시판 프로젝트

1.
프로젝트 폴더 생성 - board
2.
모듈 설치
3.
config.json (데이터 소스 설정)
4.
model 설정
5.
라우터 작성
6.
뷰 작성
7.
app 설정

프로젝트 폴더 생성 - board

package.json 생성

npm init
Shell
복사

모듈 설치

# npm 초기화 npm init # 모듈 설치 npm install express morgan pug sequelize sequelize-cli mysql2 dotenv nodemon cookie-parser express-session # 개별 설치 npm install express npm install morgan npm install pug npm install sequelize npm install sequelize-cli npm install mysql2 npm install dotenv npm install nodemon npm install cookie-parser npm install express-session --- # sequelize 초기화 (sequelize-cli 모듈 이용) npx sequelize init ### sequelize 설정 파일들이 생성됨 ### 자동 생성된 models/index.js 에 에러 발생이슈 등으로 코드 수정 ``` const Sequelize = require('sequelize'); // ✅ 모델 import const Board = require('./board'); const env = process.env.NODE_ENV || 'development'; const config = require('../config/config')[env]; const db = {}; const sequelize = new Sequelize(config.database, config.username, config.password, config); db.sequelize = sequelize; // 모델 등록 및 설정 db.Board = Board; // 모델 초기화 메소드 호출 Board.initiate(sequelize); // 연관 관계 설정 메소드 호출 (필요에 따라 사용) Board.associate(db); module.exports = db; ```
Markdown
복사

모듈 설치

npm install express morgan pug sequelize sequelize-cli mysql2 dotenv nodemon cookie-parser express-session
Shell
복사

시퀄라이즈 초기화

npx sequelize init
Shell
복사

config.json (데이터 소스 설정)

{ "development": { "username": "joeun", "password": "123456", "database": "test", "host": "127.0.0.1", "dialect": "mysql" }, "test": { "username": "joeun", "password": "123456", "database": "test", "host": "127.0.0.1", "dialect": "mysql" }, "production": { "username": "joeun", "password": "123456", "database": "test", "host": "127.0.0.1", "dialect": "mysql" } }
JSON
복사

model 설정

index.js

const Sequelize = require('sequelize'); // ✅ 모델 import const Board = require('./board'); const env = process.env.NODE_ENV || 'development'; const config = require('../config/config')[env]; const db = {}; const sequelize = new Sequelize(config.database, config.username, config.password, config); db.sequelize = sequelize; // 모델 등록 및 설정 db.Board = Board; // 모델 초기화 메소드 호출 Board.initiate(sequelize); // 연관 관계 설정 메소드 호출 (필요에 따라 사용) Board.associate(db); module.exports = db;
JavaScript
복사

board.js

const { DataTypes, Model, Sequelize } = require('sequelize'); class Board extends Model { static initiate(sequelize) { Board.init( { board_no: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true, }, title: { type: DataTypes.STRING, allowNull: false, }, writer: { type: DataTypes.STRING, allowNull: false, }, content: { type: DataTypes.TEXT, allowNull: false, }, reg_date: { type: 'TIMESTAMP', // defaultValue: 'now()', }, upd_date: { type: 'TIMESTAMP', // defaultValue: 'now()', }, }, { sequelize, modelName: 'Board', tableName: 'board', timestamps: false, // true ➡ createdAt, updatedAt 컬럼 자동생성 } ); } static associate(db) { // Add associations if needed } } module.exports = Board;
JavaScript
복사

라우터 작성

board.js

const express = require('express') const Sequelize = require('sequelize'); // ✅ Sequelize 추가 const Board = require('../models/board') // ✅ Board 모델 import const router = express.Router() // 👩‍💻 게시글 목록 router.get('/', async (req, res) => { console.log('게시글 목록...'); let boardList = [] try { boardList = await Board.findAll() // ✅ 전체 데이터 조회 } catch (error) { console.log(error); } // console.log(boardList); res.render('board/list', {boardList} ) }) // 👩‍💻 게시글 등록 router.get('/insert', (req, res) => { console.log('게시글 등록 화면...'); res.render('board/insert') }) // 👩‍💻 게시글 등록 router.post('/', async (req, res) => { console.log('게시글 등록...'); // 구조분해할당 const { title, writer, content } = req.body; const newBoard = { title, writer, content }; let result = 0 try { result = await Board.create(newBoard) // ✅ 데이터 등록 } catch (error) { console.log(error); } console.log(`등록 result : ${result}`); res.redirect('/board'); }); // 👩‍💻 게시글 수정 페이지 router.get('/update/:id', async (req, res) => { console.log('게시글 수정 화면...'); console.log(`id : ${req.params.id}`); let id = req.params.id let board = await Board.findByPk(id) res.render('board/update', { board, id }); }); // 👩‍💻 게시글 수정 router.post('/update', async (req, res) => { console.log('게시글 수정...'); const { id, title, writer, content } = req.body; let result = 0 try { result = await Board.update({ title: title, writer: writer, content: content, upd_date: Sequelize.literal('now()') }, { where: {board_no: id} }) } catch (error) { console.log(error); } console.log(`수정 result : ${result}`); res.redirect(`/board/${id}`); }); // 👩‍💻 게시글 삭제 router.post('/delete', async (req, res) => { console.log('게시글 삭제...'); const id = req.body.id; let result = 0 try { result = await Board.destroy({ where: { board_no : id } }) } catch (error) { console.log(error); } console.log(`삭제 result : ${result}`); res.redirect('/board'); }); // 👩‍💻 게시글 읽기 // 요청 경로에 파라미터 매핑 방법 ➡ '/:파라미터명' router.get('/:id', async (req, res) => { console.log('게시글 읽기 화면...'); console.log(`id : ${req.params.id}`); let id = req.params.id let board = await Board.findByPk(id) console.log(board); res.render('board/read', {board, id}) }) module.exports = router;
JavaScript
복사

index.js

const express = require('express'); const router = express.Router(); // GET / 라우터 router.get('/', (req, res) => { res.render('index', { title: 'Main' }); }); // 로그인 router.get('/login', (req, res) => { res.render('login', { title: 'Login' }); }); // 회원가입 router.get('/join', (req, res) => { res.render('join', { title: 'Join' }); }); module.exports = router;
JavaScript
복사

뷰 작성

1.
./fragment/header.pug
2.
./fragment/footer.pug
3.
error.pug
4.
layout.pug
5.
index.pug
6.
login.pug
7.
join.pug
8.
./board/list.pug
9.
./board/insert.pug
10.
./board/read.pug
11.
./board/update.pug

./fragment/header.pug

header h1 Pug nav ul li a(href="/") Home li a(href="/login") Login li a(href="/board") Board
HTML
복사

./fragment/footer.pug

footer p Copyright © 2023 Pug
HTML
복사

error.pug

extends layout block content h1= message h2= error.status pre #{error.stack}
HTML
복사

layout.pug

doctype html html head title= title link(rel='stylesheet', href='/css/style.css') body include fragment/header.pug block content include fragment/footer.pug
HTML
복사

index.pug

extends layout block content div.container-lg h1= title h3 #{title} 화면입니다
HTML
복사

login.pug

extends layout block content div.container h2 Login form div.form-group label(for="username") Username: input(type="text", id="username", name="username", required) div.form-group label(for="password") Password: input(type="password", id="password", name="password", required) div.form-group button.btn(type="submit") Login a.btn(href="/join") Sign Up
HTML
복사

join.pug

extends layout block content div.container h2 Sign Up form div.form-group label(for="username") Username: input(type="text", id="username", name="username", required) div.form-group label(for="email") Email: input(type="email", id="email", name="email", required) div.form-group label(for="password") Password: input(type="password", id="password", name="password", required) div.form-group button.btn(type="submit") Sign Up
HTML
복사

./board/list.pug

extends ../layout block content div.container-lg div.btn-box h1 게시글 목록 a.btn(href="/board/insert") 글쓰기 table.table(border=1) thead tr th No th Title th Writer tbody each board, index in boardList tr td(width=80)= board.board_no td a(href=`/board/${board.board_no}`) #{board.title} td(width=200)= board.writer
HTML
복사

./board/insert.pug

extends ../layout block content div.container-lg h1 게시글 등록 form(action="/board", method="post") table.table.form-group(border=1) tr th(width="200") Title td input(type="text", name="title") tr th(width="200") Writer td input(type="text", name="writer") tr th(width="200") Content td input(type="text", name="content") div.btn-box a.btn(href="/board") 목록 button.btn(type="submit") 등록
HTML
복사

./board/read.pug

extends ../layout block content div.container-lg h1 #{board.title} table.table(border=1) tr th(width="200") No td #{id} tr th(width="200") Writer td #{board.writer} tr th(width="200") Content td #{board.content} div.btn-box a.btn(href="/board") 목록 a.btn(href=`/board/update/${id}`) 수정
HTML
복사

./board/update.pug

extends ../layout block content div.container-lg h1 게시글 수정 form(action="/board/update", method="post" id="form") input(type="hidden", name="id" value=`${id}`) table.table.form-group(border=1) tr th(width="200") No td #{id} tr th(width="200") Title td input(type="text", name="title", value=`${board.title}`) tr th(width="200") Writer td input(type="text", name="writer", value=`${board.writer}`) tr th(width="200") Content td input(type="text", name="content", value=`${board.content}`) div.btn-box div.item a.btn(href="/board") 목록 div.item button.btn(type="submit") 수정 a.btn(href="javascript:;" id="btn-delete") 삭제 script. document.getElementById('btn-delete').addEventListener('click', function() { let form = document.getElementById('form') form.action = '/board/delete'; form.submit(); });
HTML
복사

app 설정

.env

# 모드 (development, test, production) NODE_ENV=development # PORT 설정 PORT=3000 # 쿠키 COOKIE_SECRET=cookiesecret
Plain Text
복사

app.js

const express = require('express'); const morgan = require('morgan'); const cookieParser = require('cookie-parser'); const session = require('express-session'); const dotenv = require('dotenv'); const path = require('path'); const { sequelize } = require('./models'); // ✅ 시퀄라이즈 // dotenv 라이브러리를 사용하여 환경 변수를 로드하는 부분 // 이 메소드를 호출하면 프로젝트 루트에 위치한 .env 파일의 환경 변수가 process.env 객체에 추가됩니다. dotenv.config(); // 👩‍💻 라우터 모듈 import const indexRouter = require('./routes/index'); const boardRouter = require('./routes/board'); const app = express(); // 포트 설정: 환경 변수에서 PORT를 가져오고, 없을 경우 기본값으로 3000 사용 app.set('port', process.env.PORT || 3000); // ✅ 시퀄라이즈 싱크 // - 데이터베이스 테이블 생성 또는 동기화 sequelize.sync({ force: false }) .then(() => { console.log('데이터베이스 연결 성공'); }) .catch((err) => { console.error(err); }); // 뷰 엔진 설정: Pug를 사용하도록 설정 app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'pug'); // 로깅 미들웨어 설정: 개발 환경에서는 dev 모드로 로그를 출력 app.use(morgan('dev')); // 정적 파일 제공 미들웨어 설정: public 폴더를 정적 파일 제공 디렉토리로 설정 app.use('/', express.static(path.join(__dirname, 'public'))); // JSON 파싱 미들웨어 설정 // - JSON 형식의 요청 본문을 파싱 // - URL 인코딩된 요청 본문을 파싱 app.use(express.json()); app.use(express.urlencoded({ extended: false })); // cookie-parser 미들웨어를 사용하여 쿠키를 파싱하는 부분 // process.env.COOKIE_SECRET는 .env 파일이나 환경 변수에서 설정한 비밀 키를 사용 app.use(cookieParser(process.env.COOKIE_SECRET)); // Express 애플리케이션에 세션 미들웨어 설정 app.use(session({ resave: false, // 세션 데이터가 변경되지 않으면 서버에 다시 저장하지 않음 saveUninitialized: false, // 초기화되지 않은 세션을 저장소에 저장하지 않음 secret: process.env.COOKIE_SECRET, // 세션 식별을 위한 비밀 키 // 세션 쿠키 설정 cookie: { httpOnly: true, // 브라우저에서 쿠키에 접근할 때만 가능하도록 httpOnly 속성 사용 secure: false, // HTTPS가 아닌 환경에서도 쿠키 전송 허용 }, name: 'session-cookie', // 세션 쿠키의 이름 설정 })); // 👩‍💻 라우터 설정 app.use('/', indexRouter); app.use('/board', boardRouter); // 404 오류 처리 미들웨어 app.use((req, res, next) => { // 요청된 경로가 존재하지 않을 때 404 상태 코드와 'Not Found' 메시지를 응답으로 전송 res.status(404).send('Not Found'); }); // 에러 처리 미들웨어 app.use((err, req, res, next) => { // 에러가 발생했을 때, 에러 메시지를 콘솔에 출력하고 500 상태 코드와 에러 메시지를 응답으로 전송 console.error(err); res.status(500).send(err.message); }); // Express 애플리케이션을 특정 포트에서 실행하는 부분 app.listen(app.get('port'), () => { // 애플리케이션이 실행되면 콘솔에 해당 포트에서 대기 중임을 출력 console.log(app.get('port'), '번 포트에서 대기 중'); });
JavaScript
복사

실행 화면

/

메인화면

/login

로그인 화면

/join

회원가입 화면

/board

게시글 목록

/board/insert

게시글 등록

/board/2

게시글 읽기

/board/update/2

게시글 수정

Github (소스코드)