Search

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 (์†Œ์Šค์ฝ”๋“œ)