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
게시글 수정


















