Express는 Node.js에서 서버 사이드 웹 애플리케이션을 구축하기 위한 간단하면서도 유연한 웹 애플리케이션 프레임워크입니다. Express는 Node.js 환경에서 서버를 구축하고 HTTP 요청을 처리하는 과정을 쉽게 만들어줍니다.
express()
)app
객체는 Express 애플리케이션의 기본적인 컨테이너입니다. 이를 통해 서버의 주요 동작을 설정할 수 있습니다. 서버 설정, 미들웨어 추가, 라우팅 정의 등을 모두 이 객체를 통해 처리합니다.
app.get(path, callback)
: 특정 경로에 대한 GET 요청을 처리합니다.app.post(path, callback)
: 특정 경로에 대한 POST 요청을 처리합니다.app.put(path, callback)
: 특정 경로에 대한 PUT 요청을 처리합니다.app.patch(path, callback)
: 특정 경로에 대한 PATCH 요청을 처리합니다.app.delete(path, callback)
: 특정 경로에 대한 DELETE 요청을 처리합니다.app.use(path, callback)
: 애플리케이션에 미들웨어를 추가합니다. 미들웨어는 요청과 응답 사이의 중간 처리 기능을 수행합니다.app.set(key, value)
: 웹 서버의 환경을 설정합니다.app.listen(path, callback)
: 서버를 특정 포트에서 실행합니다.req
)req
객체는 클라이언트로부터 전달된 HTTP 요청에 대한 정보를 담고 있습니다. 이 객체를 통해 요청 URL, HTTP 메서드, 헤더, 요청 본문 등의 정보를 얻을 수 있습니다.
req.params
: URL 경로의 동적 파라미터를 접근할 수 있습니다.req.query
: URL의 쿼리 스트링 파라미터를 가져옵니다.req.body
: 요청 body의 데이터를 가져옵니다.req.headers
: 요청의 헤더 정보를 담고 있습니다. 특정 헤더 값을 가져올 때 사용합니다.req.app
: app 객체에 접근하여, req.app.get(’port’)와 같은 식으로 사용할 수 있습니다.req.cookies
: cookie-parser 미들웨어가 만드는 요청의 쿠키를 해석한 객체입니다.req.ip
: 요청의 ip 주소가 담겨 있습니다.req,get(headerName)
: 특정 헤더의 값을 가져올 때 사용합니다.res
)res
객체는 서버가 클라이언트로 HTTP 응답을 보낼 때 사용됩니다. 이 객체를 통해 상태 코드 설정, 본문 전송, 파일 전송 등 다양한 응답 작업을 할 수 있습니다.
res.send(body)
: 클라이언트에게 응답을 보냅니다. 문자열, 객체, HTML 등을 보낼 수 있습니다.res.json(JSON)
: JSON 형식으로 데이터를 응답합니다.res.status(code)
: 응답의 상태 코드를 설정합니다. 일반적으로 다른 응답 메서드와 체이닝하여 사용합니다.res.sendFile(path)
: 지정된 경로의 파일을 클라이언트에 전송합니다.res.render(view, data, callback)
: 뷰 템플릿을 렌더링하고, 그 결과를 클라이언트에게 전달합니다.view
: 렌더링할 템플릿 파일의 이름입니다. Express는 기본적으로 views
디렉토리에서 이 파일을 찾습니다.locals
: 템플릿에 전달할 데이터입니다. 이 데이터는 템플릿 안에서 변수로 접근할 수 있습니다.callback
: 렌더링 후 실행되는 콜백 함수로, 선택 사항입니다. 에러나 결과를 처리할 때 사용됩니다.res.end()
: 데이터 없이 응답을 보냅니다.res.setHeader(hedaer, value)
: 응답의 헤더를 설정합니다.res.redirect(url)
: 특정 URL로 리다이렉트시킵니다.Router
)Express의 Router 객체는 여러 개의 경로를 모듈화하고 독립적으로 관리하는 데 유용합니다. 애플리케이션의 특정 기능에 관련된 라우트를 한 곳에 모아 모듈화할 수 있어 큰 프로젝트의 유지보수가 수월해집니다.
router.get(path, callback)
: app.get()
과 비슷하게 특정 경로에 대한 GET 요청을 처리하지만, app
객체 대신 라우터에서 사용됩니다.router.post(path, callback)
: POST 요청을 처리하는 라우터 메서드입니다.router.put(path, callback)
: PUT요청을 처리하는 라우터 메서드입니다.router.patch(path, callback)
: PATCH요청을 처리하는 라우터 메서드입니다.router.delete(path, callback)
: DELETE요청을 처리하는 라우터 메서드입니다.router.use(middleware)
: 해당 라우터에 대한 미들웨어를 추가할 수 있습니다.라우팅 처리는 Express의 가장 큰 장점 중 하나입니다. 다양한 HTTP 메소드(GET, POST, PUT, DELETE 등)에 대해 특정 경로를 간단하게 처리할 수 있습니다. 경로에 따라 분리된 라우터를 사용하면 더욱 구조적으로 코드를 관리할 수 있습니다.
// 필요한 모듈을 가져옵니다.
const express = require('express'); // Express 모듈 가져오기
const app = express(); // Express 애플리케이션 생성
const port = 3000; // 서버가 실행될 포트 번호
// 요청 본문을 JSON 형식으로 파싱하기 위한 미들웨어를 사용합니다.
app.use(express.json());
// 기본 경로 ('/')에 대한 GET 요청을 처리합니다.
app.get('/', (req, res) => {
res.send('Hello, World! Express 서버에 오신 것을 환영합니다!');
});
// '/about' 경로에 대한 GET 요청을 처리합니다.
app.get('/about', (req, res) => {
res.send('이 서버는 Express를 사용해 구축된 간단한 웹 서버입니다.');
});
// '/user' 경로에 대한 POST 요청을 처리합니다.
app.post('/user', (req, res) => {
const userName = req.body.name;
res.send(`사용자 ${userName}님을 추가했습니다!`);
});
// '/user/:id' 경로에 대한 GET 요청을 처리하여 특정 사용자의 정보를 가져옵니다.
// ':id'는 URL의 동적 파라미터로, 여러 사용자 ID를 처리할 수 있습니다.
app.get('/user/:id', (req, res) => {
const userId = req.params.id; // URL에서 사용자 ID를 가져옴
res.send(`ID가 ${userId}인 사용자의 정보를 반환합니다.`);
});
// '/user/:id' 경로에 대한 PUT 요청을 처리하여 특정 사용자의 정보를 업데이트합니다.
app.put('/user/:id', (req, res) => {
const userId = req.params.id;
const updatedName = req.body.name;
res.send(`ID가 ${userId}인 사용자의 이름을 ${updatedName}으로 업데이트했습니다.`);
});
// '/user/:id' 경로에 대한 DELETE 요청을 처리하여 특정 사용자를 삭제합니다.
app.delete('/user/:id', (req, res) => {
const userId = req.params.id;
res.send(`ID가 ${userId}인 사용자를 삭제했습니다.`);
});
// 서버를 지정된 포트에서 실행합니다.
app.listen(port, () => {
console.log(`서버가 http://localhost:${port} 에서 실행 중입니다.`);
});
또한, Router
객체를 사용하면 각 경로(라우트)를 별도의 모듈로 분리하여 코드의 가독성을 높이고 유지보수를 용이하게 할 수 있습니다.
아래 코드는 위 예시 코드를 Router
객체를 이용하여 별도의 모듈로 분리한 예시 코드입니다.
// ./routes/index.js
const express = require('express');
const router = express().Router;
// '/' 경로에 대해 정의된 라우터를 사용합니다.
router.get('/',(req, res) => {
res.send("Hello, World! Express 서버에 오신 것을 환영합니다!")
});
module.exports = router;
// ./routes/about.js
const express = require('express');
const router = express().Router;
// '/about' 경로에 대해 정의된 라우터를 사용합니다.
router.get('/',(req, res) => {
res.send("이 서버는 Express를 사용해 구축된 간단한 웹 서버입니다.")
});
module.exports = router;
// ./routes/user.js
const express = require('express');
const router = express().Router;
// '/user' 경로에 대한 POST 요청을 처리합니다.
app.post('/', (req, res) => {
const userName = req.body.name;
res.send(`사용자 ${userName}님을 추가했습니다!`);
});
// '/user/:id' 경로에 대한 GET 요청을 처리하여 특정 사용자의 정보를 가져옵니다.
// ':id'는 URL의 동적 파라미터로, 여러 사용자 ID를 처리할 수 있습니다.
app.get('/:id', (req, res) => {
const userId = req.params.id; // URL에서 사용자 ID를 가져옴
res.send(`ID가 ${userId}인 사용자의 정보를 반환합니다.`);
});
// '/user/:id' 경로에 대한 PUT 요청을 처리하여 특정 사용자의 정보를 업데이트합니다.
app.put('/:id', (req, res) => {
const userId = req.params.id;
const updatedName = req.body.name;
res.send(`ID가 ${userId}인 사용자의 이름을 ${updatedName}으로 업데이트했습니다.`);
});
// '/user/:id' 경로에 대한 DELETE 요청을 처리하여 특정 사용자를 삭제합니다.
app.delete('/:id', (req, res) => {
const userId = req.params.id;
res.send(`ID가 ${userId}인 사용자를 삭제했습니다.`);
});
module.exports = router;
// app.js
const express = require('express');
const app = express();
const indexRouter = require('./routes');
const aboutRouter = require('./routes/about');
const userRouter = require('./routes/user')
// index router 연결
app.use('/', indexRouter);
// about router 연결
app.use('/about', aboutRouter);
// user router 연결
app.use('/user/', userRouter);
Express의 미들웨어(Middleware)는 서버의 요청과 응답 사이에서 특정 작업을 수행하는 함수입니다. 미들웨어는 요청을 처리하거나 응답을 생성하기 전에 여러 단계에서 다양한 작업을 수행할 수 있도록 도와줍니다. 이러한 작업에는 로깅, 인증, 오류 처리, 요청 본문 파싱 등이 포함됩니다.
미들웨어는 Express의 매우 중요한 개념으로, 다음과 같은 역할을 합니다:
미들웨어의 구조
req
: 클라이언트의 요청 객체.res
: 서버의 응답 객체.next
: 다음 미들웨어로 넘어가게 하는 함수. 미들웨어가 요청 처리를 끝내지 않고, 다음 미들웨어로 넘기고자 할 때 호출합니다. 만약 next()
를 호출하지 않으면 요청은 해당 미들웨어에서 멈추게 됩니다. next에 인수로 값을 넣으면 에러 핸들러로 넘어갑니다.('route'인 경우 다음 라우터로 넘어갑니다.)function middleware(req, res, next) {
// 처리 로직
next(); // 다음 미들웨어로 이동
}
미들웨어간 데이터 전달하기
req, res 객체 안에 값을 넣어 데이터를 전달할 수 있습니다.
app.set() 메소드와 차이점 : app.set() 메소드는 서버 내 유지 되지만 req, res는 요청 하나 동안만 유지됩니다.
req.body나 req.cookies 같은 미들웨어의 데이터와 겹치지 않게 주의해야합니다.
app.use((req, res, next) => {
req.data = 'data';
next();
}, (req, res, next) => {
console.log(req.data);
next();
})
미들웨어 확장
미들웨어에 새로운 미들웨어를 추가하여 확장할 수 있습니다.
morgan
를 개발 또는 프로덕션 환경에 따라 다른 로그 형식을 적용합니다.
app.use((req, res, next) => {
if(process.env.NODE_ENV === 'production') {
morgan('combined')(req, res, next);
} else {
morgan('dev')(req, res, next);
}
})
미들웨어의 종류
애플리케이션 레벨 미들웨어
const express = require('express');
const app = express();
// 모든 경로에 적용되는 미들웨어
app.use((req, res, next) => {
console.log(`Request Method: ${req.method}, Request URL: ${req.url}`);
next(); // 다음 미들웨어로 이동
});
// 특정 경로에서만 적용되는 미들웨어
app.use('/about', (req, res, next) => {
console.log('About 페이지 요청이 들어왔습니다.');
next();
});
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
라우터 레벨 미들웨어
const router = express.Router();
// 특정 라우터에만 적용되는 미들웨어
router.use((req, res, next) => {
console.log('라우터 레벨 미들웨어 실행 중');
next();
});
router.get('/user/:id', (req, res) => {
res.send(`User ID: ${req.params.id}`);
});
app.use('/user', router);
빌트인 미들웨어
express.static()
: 정적 파일을 제공하는 미들웨어로, HTML, CSS, 이미지 등과 같은 정적 리소스를 제공하는 데 사용됩니다.app.use(express.static('public'));
express.json()
: 요청 본문을 JSON 형식으로 파싱하는 미들웨어입니다. 이 미들웨어를 사용하면 POST 요청에서 전달된 JSON 데이터를 쉽게 req.body
를 통해 접근할 수 있습니다.app.use(express.json());
서드 파티 미들웨어
주요 서드 파티 미들웨어
morgan
: 로그 기록을 남기기 위한 미들웨어입니다.
const express = require('express');
const app = express();
const morgan = require('morgan');
app.use(morgan('dev')); // 요청 로그를 콘솔에 출력
cors
: CORS (Cross-Origin Resource Sharing)를 설정하기 위한 미들웨어로, 다른 도메인에서의 요청을 허용하는 경우에 사용됩니다.
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors());
cookie-parser
: 쿠키를 쉽게 처리할 수 있도록 도와주는 미들웨어입니다.
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
// 쿠키 설정하기
app.get('/set-cookie', (req, res) => {
res.cookie('username', 'gptonline', { maxAge: 900000, httpOnly: true });
res.send('쿠키가 설정되었습니다.');
});
// 쿠키 읽기
app.get('/get-cookie', (req, res) => {
let username = req.cookies.username;
res.send(`저장된 쿠키: ${username}`);
});
express-session
: 세션 관리를 위한 미들웨어입니다.
const session = require('express-session');
const express = require('express');
const app = express();
app.use(session({
secret: 'your-secret-key', // 세션 암호화를 위한 비밀 키
resave: false, // 세션 데이터가 변경되지 않더라도 세션을 저장할지 여부
saveUninitialized: true, // 초기화되지 않은 세션을 저장할지 여부
cookie: { secure: false } // true면 HTTPS에서만 쿠키 전달 (개발 환경에서는 false)
}));
app.get('/', (req, res) => {
// 세션 데이터 저장
req.session.user = 'John Doe';
res.send('세션이 설정되었습니다!');
});
app.get('/session', (req, res) => {
// 세션 데이터 접근
if (req.session.user) {
res.send(`세션에 저장된 사용자: ${req.session.user}`);
} else {
res.send('세션이 없습니다.');
}
});
app.listen(8080, () => {
console.log('Server is running on port 8080');
});
에러 처리 미들웨어
err, req, res, next
)를 사용하는 점이 특징입니다. 이는 일반적인 미들웨어와 다르게 오류 정보를 첫 번째 인자로 전달받아 처리할 수 있게 해줍니다.app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something wrong!');
});
Express는 정적 파일(HTML, CSS, JS 등)을 쉽게 제공할 수 있는 기능을 제공합니다. express.static()
미들웨어를 이용하여 정적 파일이 있는 디렉터리를 지정하면 서버에서 이 디렉터리에 있는 파일을 직접 제공할 수 있습니다.
app.use(express.static('public'));
위와 같은 코드를 추가하면 public
폴더의 모든 정적 파일을 제공하게 됩니다. 예를 들어, public/index.html
은 /index.html
로 접근할 수 있습니다.
Express는 템플릿 엔진을 통합하여 서버 사이드에서 HTML을 동적으로 생성할 수 있도록 돕습니다. 대표적으로 Pug, EJS와 같은 템플릿 엔진을 사용할 수 있습니다.
app.set('view engine', 'pug');
app.get('/', (req, res) => {
res.render('index', { title: 'Express', message: 'Hello there!' });
});
위 코드는 pug 템플릿 엔진을 사용하여 템플릿 파일에서 데이터를 바인딩하여 동적으로 HTML을 생성하고 클라이언트에게 전달할 수 있습니다.
express 프레임워크를 이용하여 서버를 구축하는 예시 코드입니다.
ejs 템플릿 엔진을 사용하였으며, Router
객체를 이용하여 라우터를 모듈로 분리하였습니다.
express.static()
를 통해 정적 파일(js, css)을 제공하였습니다.
// ./src/views/home.ejs
<!DOCTYPE html>
<html lang="ko-KR">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
<link href="/css/home.css" rel="stylesheet" />
</head>
<body>
<header>
<h1>My Website</h1>
<nav>
<ul>
<li><a href="/">HOME</a></li>
<li><a href="/posts/create">CREATE</a></li>
<li><a href="/posts">POSTS</a></li>
</ul>
</nav>
</header>
<main>
<section>
<h2>환영합니다!</h2>
<p>여기는 메인 페이지입니다. 이곳에서 다양한 게시물을 관리하고 새로운 게시물을 생성할 수 있습니다.</p>
<button class="primary-btn" onclick="location.href='/posts'">게시물 목록 보기</button>
</section>
</main>
</body>
</html>
// ./src/views/posts.ejs
<!DOCTYPE html>
<html lang="ko-KR">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>게시글 목록</title>
<link href="/css/posts.css" rel="stylesheet" />
<script src="/js/posts.js" defer></script>
</head>
<body>
<div class="container">
<h1>게시글 목록</h1>
<div class="posts">
<% posts.forEach(post => { %>
<div class="post" data-id="<%= post.id %>">
<img src="<%= post.image %>" alt="post image" class="post-image" loading="lazy" width="300" height="300"/>
<h2><%= post.title %></h2>
<p><%= post.content %></p>
<div class="btn-group">
<button type="button" class="edit-btn">수정</button>
<button type="button" class="delete-btn">삭제</button>
</div>
</div>
<% }) %>
</div>
</div>
</body>
</html>
// ./src/views/create.ejs
<!DOCTYPE html>
<html lang="ko-KR">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>게시물 생성</title>
<script src="/js/posts.js" defer></script>
</head>
<body>
<h1>게시물 생성</h1>
<form id="post-form">
<label for="title-input">제목:</label>
<input id="title-input" type="text" placeholder="제목 입력" required/><br><br>
<label for="content-input">내용:</label>
<input id="content-input" type="text" placeholder="내용 입력" required/><br><br>
<button id="submit-btn" type="submit">생성</button>
</form>
</body>
</html>
// ./src/views/edit.ejs
<!DOCTYPE html>
<html lang="ko-KR">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>게시물 수정</title>
<script src="/js/posts.js" defer></script>
</head>
<body>
<h1>게시물 수정</h1>
<form id="post-form">
<label for="title-input">제목:</label>
<input id="title-input" type="text" value="<%= post.title %>" placeholder="제목 입력" required/><br><br>
<label for="content-input">내용:</label>
<input id="content-input" type="text" value="<%= post.content %>" placeholder="내용 입력" required/><br><br>
<label for="content-input">이미지 URL:</label>
<input id="image-input" type="text" value="<%= post.image %>" placeholder="이미지 URL 입력" required/><br><br>
<button id="submit-btn" type="submit">수정</button>
</form>
</body>
</html>
// ./src/public/js
"use strict";
const pathname = location.pathname;
// 폼 제출 이벤트를 처리하는 함수
if (pathname.includes("create")) {
document
.getElementById("post-form")
.addEventListener("submit", function (event) {
event.preventDefault(); // 기본 폼 제출을 막음
// 입력된 제목과 내용을 가져옴
const id = new Date().getTime();
const title = document.getElementById("title-input").value;
const content = document.getElementById("content-input").value;
// 서버로 보낼 데이터
const postData = {
id,
title,
content,
image: "https://picsum.photos/300",
};
// Fetch API를 이용해 POST 요청 전송
fetch("/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ postData }), // JSON 형태로 변환 후 전송
})
.then((response) => {
if (response.ok) {
location.href = "/posts";
alert("게시물이 성공적으로 생성되었습니다.");
}
})
.catch((error) => {
console.error("오류:", error);
alert("게시물 생성에 실패했습니다.");
});
});
}
if (pathname.includes("posts")) {
document.querySelectorAll(".delete-btn").forEach((button) => {
button.addEventListener("click", async function () {
const postElement = this.closest(".post"); // 해당 게시글 요소
const postId = postElement.getAttribute("data-id"); // 게시글 id
// 서버로 삭제 요청 보내기
const response = await fetch("/posts", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ postId }), // 객체 형태로 전달
});
if (response.ok) {
// 삭제 성공 시, 해당 게시글을 DOM에서 제거
postElement.remove();
alert("게시글이 삭제되었습니다.");
} else {
alert("삭제 중 오류가 발생했습니다.");
}
});
});
document.querySelectorAll(".edit-btn").forEach((button) => {
button.addEventListener("click", () => {
const postElement = button.closest(".post"); // 해당 게시글 요소를 button 기준으로 찾음
const postId = postElement.getAttribute("data-id"); // 게시글 id
location.href = `/posts/edit?id=${postId}`;
});
});
}
if (pathname.includes("edit")) {
document
.getElementById("post-form")
.addEventListener("submit", function (event) {
event.preventDefault(); // 기본 폼 제출을 막음
// 입력된 제목과 내용을 가져옴
const searchParams = new URL(location.href).searchParams;
const id = searchParams.get("id");
const title = document.getElementById("title-input").value;
const content = document.getElementById("content-input").value;
const image = document.getElementById("image-input").value;
// 서버로 보낼 데이터
const postData = {
id,
title,
content,
image,
};
// Fetch API를 이용해 POST 요청 전송
fetch("/posts", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ postData }), // JSON 형태로 변환 후 전송
})
.then((response) => {
if (response.ok) {
location.href = "/posts";
alert("게시물이 성공적으로 수정되었습니다.");
}
})
.catch((error) => {
console.error("오류:", error);
alert("게시물 수정에 실패했습니다.");
});
});
}
/*./public/css/home.css */
/* 전체 스타일 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background-color: #f4f4f4;
color: #333;
line-height: 1.6;
}
/* 헤더 및 네비게이션 */
header {
background-color: #333;
color: white;
padding: 10px 20px;
text-align: center;
}
header h1 {
font-size: 2rem;
margin-bottom: 10px;
}
nav ul {
list-style: none;
padding: 0;
display: flex;
justify-content: center;
}
nav ul li {
margin: 0 15px;
}
nav ul li a {
color: white;
text-decoration: none;
font-size: 1.1rem;
}
nav ul li a:hover {
text-decoration: underline;
}
/* 메인 컨텐츠 */
main {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
section {
margin-bottom: 40px;
}
section h2 {
font-size: 1.8rem;
margin-bottom: 20px;
}
p {
font-size: 1.1rem;
}
/* 버튼 스타일 */
.primary-btn {
background-color: #28a745;
color: white;
border: none;
padding: 10px 20px;
font-size: 1.2rem;
cursor: pointer;
}
.primary-btn:hover {
background-color: #218838;
}
/* 게시물 미리보기 */
.featured {
display: flex;
gap: 20px;
}
.post-preview {
background-color: white;
padding: 20px;
border: 1px solid #ddd;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 300px;
text-align: center;
}
.post-preview img {
max-width: 100%;
height: auto;
margin-bottom: 15px;
}
.post-preview h3 {
font-size: 1.5rem;
margin-bottom: 10px;
}
.post-preview p {
font-size: 1rem;
color: #555;
}
/* 푸터 */
footer {
background-color: #333;
color: white;
text-align: center;
padding: 20px 0;
position: fixed;
bottom: 0;
width: 100%;
}
/* ./public/css/posts.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
}
.container {
width: 80%;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 20px;
}
.posts {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
}
.post {
background-color: white;
border: 1px solid #ddd;
padding: 20px;
width: 300px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.post-image {
width: 100%;
height: auto;
margin-bottom: 15px;
}
h2 {
font-size: 1.5rem;
margin-bottom: 10px;
}
p {
font-size: 1rem;
color: #555;
}
.btn-group{
margin-top: 2rem;
display: flex;
justify-content: end;
gap: 8px;
}
.delete-btn,
.edit-btn {
padding: 6px 12px;
background-color: rgb(252, 168, 157);
border-radius: 6px;
border: none;
}
.edit-btn {
background-color: rgb(157, 150, 253);
}
// ./src/routes/index.js
const express = require("express");
const router = express.Router();
router.get("/", (req, res) => {
res.render("home");
});
module.exports = router;
// ./src/routes/posts.js
const express = require("express");
const router = express.Router();
const fs = require("fs").promises; // fs.promises 사용
const HomeController = require("../controllers/home.controller");
router.get("/", async (req, res) => {
const postsData = await HomeController.getPosts();
const posts = JSON.parse(postsData).posts;
res.render("posts", { posts });
});
router.get("/create", (req, res) => {
res.render("create");
});
router.get("/edit", async (req, res) => {
const postId = req.query.id;
const postsData = await HomeController.getPosts();
const posts = JSON.parse(postsData).posts;
const post = posts.find((post) => post.id === parseInt(postId, 10));
res.render("edit", { post });
});
router.post("/", async (req, res) => {
const postData = req.body.postData;
const postsData = await HomeController.getPosts(); // 기존 게시물 데이터 불러오기
const posts = JSON.parse(postsData).posts;
// 새로운 게시물 추가
posts.push(postData);
// 업데이트된 데이터를 파일에 저장 (fs.promises.writeFile 사용)
await fs.writeFile("./src/db/posts.json", JSON.stringify({ posts }, null, 2));
return res.status(201).json({ message: "게시물 생성 성공", postData });
});
router.patch("/", async (req, res) => {
const postData = req.body.postData;
const postsData = await HomeController.getPosts(); // 기존 게시물 데이터 불러오기
const posts = JSON.parse(postsData).posts;
const updatedPosts = posts.map((post) => {
if (post.id === parseInt(postData.id, 10)) {
return { ...post, ...postData, id: parseInt(postData.id, 10) };
} else {
return post;
}
});
// 업데이트된 데이터를 파일에 저장 (fs.promises.writeFile 사용)
await fs.writeFile(
"./src/db/posts.json",
JSON.stringify({ posts: updatedPosts }, null, 2)
);
// 클라이언트에 응답
return res.status(200).json({ message: "게시물 수정 성공", postData });
});
router.delete("/", async (req, res) => {
const postId = req.body.postId; // 객체에서 postId 가져오기
const postsData = await HomeController.getPosts(); // 기존 게시물 데이터 불러오기
const posts = JSON.parse(postsData).posts;
const updatedPosts = posts.filter((post) => post.id !== parseInt(postId, 10));
// 업데이트된 데이터를 파일에 저장 (fs.promises.writeFile 사용)
await fs.writeFile(
"./src/db/posts.json",
JSON.stringify({ posts: updatedPosts }, null, 2)
);
return res.status(200).json({ message: "게시물 삭제 성공" });
});
module.exports = router;
// ./src/controllers/home.controller.js
const fs = require("fs").promises;
const path = require("path");
class HomeController {
static async getPosts() {
const filePath = path.join(__dirname, "../db/posts.json");
const posts = await fs.readFile(filePath, "utf-8");
return posts;
}
}
module.exports = HomeController;
const express = require("express");
const app = express();
const path = require("path");
const homeRoutes = require("./src/routes/home"); // 홈 라우터
const postsRoutes = require("./src/routes/posts"); // 게시물 라우터
app.set("port", 8080); // 서버 포트 설정
app.set("view engine", "ejs"); // 템플릿 엔진 설정
app.set("views", "./src/views"); // 뷰 폴더 설정
app.use(express.static(path.join(__dirname, "public"))); // 정적 파일 제공
app.use(express.json()); // JSON 파싱 미들웨어
app.use(express.urlencoded({ extended: true })); // URL-encoded 파싱 미들웨어
app.use("/", homeRoutes); // 홈 라우터 사용
app.use("/posts", postsRoutes); // 게시물 라우터 사용
// 에러 처리 라우트
app.use((err, req, res, next) => {
console.error(err);
res.send(`에러: ${err}`);
});
// 서버 실행
app.listen(app.get("port"), () => {
console.log("8080 포트에서 서버 실행 중");
console.log("http://localhost:8080/");
});