
블로그 사용자 → 일반 사용자 / 관리자
| 사용자 종류 | 기능 |
|---|---|
| 일반 사용자 | 게시물 목록 조회 / 게시물 보기 / 메뉴 이동 / 로그인 |
| 관리자 | 새 글 작성 / 수정 / 삭제 / 로그아웃 |
새 폴더 생성 후 터미널에서 npm 초기화
npm init -y
주요 모듈 설치
npm install express dotenv
app.js 생성 및 기본 서버 코드 작성
(포트는 .env에 지정하거나 기본값 3000 사용)
아래 폴더들을 만들어 코드 분리
| 폴더 | 역할 |
|---|---|
| config | DB 연결 설정 |
| models | DB 스키마 정의 |
| public | CSS, JS, 이미지 등 정적 파일 |
| routes | 라우팅 파일들 |
| views | EJS 템플릿 저장 (브라우저에 표시될 화면) |
물론입니다! 아래는 "5. EJS 템플릿 엔진 및 레이아웃 구현 B" 파트와 "6. 동적 콘텐츠 표시를 위한 변수 전달"까지를 누락 없이 간결하고 명확하게 정리한 요약입니다:
📌 중복되는 HTML을 줄이기 위해 EJS 템플릿과 레이아웃 방식 사용
<%- body %>를 통해 개별 페이지 내용을 렌더링npm install ejs express-ejs-layouts
const express = require('express');
const expressLayouts = require('express-ejs-layouts');
const app = express();
app.set('view engine', 'ejs');
app.use(expressLayouts);
app.set('layout', 'layouts/main');
<%- body %> 포함)const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.render('index');
});
router.get('/about', (req, res) => {
res.render('about');
});
module.exports = router;
/ 경로 요청 시 → main.ejs 레이아웃 + index.ejs 콘텐츠 출력/about 경로 요청 시 → main.ejs 레이아웃 + about.ejs 콘텐츠 출력<%- body %>에 각 페이지의 내용이 삽입된 것을 HTML 소스 보기로 확인 가능페이지마다 다른 제목을 보여주기 위해 변수 사용
views/layouts/main.ejs
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title><%= title %></title>
</head>
<body>
<%- body %>
</body>
</html>
👉 이때 title 변수는 각 페이지에서 render시 넘겨줘야 함
router.get('/', (req, res) => {
res.render('index', { title: '홈' });
});
router.get('/about', (req, res) => {
res.render('about', { title: '어바웃' });
});
물론입니다! 아래는 요청하신 블로그 앱의 관리자 페이지 및 로그인 기능 구현 내용을 Velog 형식의 학습내용 정리글로 구성한 예시입니다. 내용 누락 없이 간결하고 이해하기 쉽도록 구성했습니다.
📌 블로그에는 2가지 화면이 존재합니다.
| 구분 | 기능 |
|---|---|
| 사용자 | 게시물 조회, 로그인 링크 표시 |
| 관리자 | 게시물 수정/삭제, 새 글 작성, 로그아웃 링크 표시 |
views/layouts/ 폴더에 각각 저장
📌 /admin GET 요청 시 → 로그인 화면 렌더링
📌 app.js에 admin 라우터 연결
관리자 로그인 전에는 로그아웃 링크가 보이면 안되므로,
라우트에서 조건에 따라 해당 레이아웃 사용 지정
▶ models/user.js
MongoDB Mongoose 스키마 정의
const UserSchema = new mongoose.Schema({
username: String,
password: String
})
📦 필요한 모듈
npm install bcrypt
등록 처리 시:
요청 본문 파싱을 위한 미들웨어 필요
app.use(express.urlencoded({ extended: true }))
📦 설치 모듈
npm install jsonwebtoken cookie-parser
app.js 설정:
const cookieParser = require('cookie-parser')
app.use(cookieParser())
.env에 비밀키 추가:
JWT_SECRET=mysecretkey123
로그인 라우트 (/admin POST):
화면 구성:
레이아웃: admin.ejs (로그아웃 포함)
admin.js에 라우트 작성:
router.get('/logout', (req, res) => {
res.clearCookie('token')
res.redirect('/')
})
admin.ejs에서 링크 수정:
<a href="/logout">로그아웃</a>
<a href="/admin">관리자 로그인</a>
요청 시 로그인 화면(index.ejs) 렌더링되도록 구성
| 기능 | 설명 | 모듈/기술 |
|---|---|---|
| 사용자 등록 | username / password 저장 | mongoose |
| 비밀번호 암호화 | bcrypt hash() → DB 저장 | bcrypt |
| 로그인 | 사용자 확인 + 비밀번호 비교 후 JWT 발급 | jsonwebtoken |
| 인증 유지 | JWT 토큰을 쿠키에 저장 | cookie-parser |
| 토큰 검증 | 관리자 페이지 접근 시 토큰 유효성 확인 | middleware 작성 가능 |
| 게시물 출력 | 로그인 후 전체 게시물 조회 가능 | MongoDB, ejs |
| 로그아웃 처리 | 쿠키 삭제 후 첫 화면으로 이동 | res.clearCookie() |
물론입니다! 주어진 CRUD 기능 구현 관련 내용을 빠짐없이 Velog 스타일로 학습 정리로 구성해드릴게요.
| 용어 | 설명 |
|---|---|
| Node.js | 자바스크립트를 서버에서도 실행할 수 있도록 해주는 런타임 환경 |
| CRUD | Create, Read, Update, Delete의 약어로 데이터 처리 기본 기능 |
| API | 요청과 응답을 처리하는 인터페이스 (라우트) |
| Middleware | 요청(req)과 응답(res) 사이에서 작업을 수행하는 함수 |
| JWT | JSON Web Token, 인증 토큰 방식 |
| Method-Override | HTML form에서 PUT, DELETE 요청을 가능하게 함 |
Backend의 핵심인 CRUD API를 아래와 같이 구성했습니다:
| 요청 경로 | 메서드 | 설명 |
|---|---|---|
| /add | GET | 게시물 작성 폼 출력 |
| /add | POST | 게시물 DB에 저장 |
| /edit/:id | GET | 특정 게시물 수정 폼 출력 |
| /edit/:id | PUT | 게시물 내용 업데이트 |
| /delete/:id | DELETE | 게시물 삭제 요청 처리 |
📌 admin.js 예:
router.get('/add', checkLogin, (req, res) => {
res.render('admin/add', { layout: 'layouts/admin' });
});
router.post('/add', checkLogin, async (req, res) => {
await Post.create(req.body);
res.redirect('/allposts');
});
모든 CRUD 작업은 로그인한 관리자만 가능하도록 보안 처리
✅ checkLogin 미들웨어 제작:
🔒 인증이 필요한 라우트마다 checkLogin 미들웨어 적용
router.get('/add', checkLogin, ...);
router.get('/edit/:id', checkLogin, ...);
📦 method-override 사용
npm install method-override
app.js 설정:
const methodOverride = require('method-override');
app.use(methodOverride('_method'));
edit.ejs 폼 내에 method override 적용 예:
<form action="/edit/<%= post._id %>?_method=PUT" method="POST">
/admin.js 라우트:
// 수정 폼 출력
router.get('/edit/:id', checkLogin, async (req, res) => {
const post = await Post.findById(req.params.id);
res.render('admin/edit', { post, layout: 'layouts/admin' });
});
// 수정 반영 요청
router.put('/edit/:id', checkLogin, async (req, res) => {
await Post.findByIdAndUpdate(req.params.id, req.body);
res.redirect('/allposts');
});
admin.js 코드:
router.delete('/delete/:id', checkLogin, async (req, res) => {
await Post.findByIdAndDelete(req.params.id);
res.redirect('/allposts');
});
삭제 버튼 HTML 예:
<form action="/delete/<%= post._id %>?_method=DELETE" method="POST">
<button type="submit" onclick="return confirm('삭제하시겠습니까?')">삭제</button>
</form>
JWT로 로그인 기능을 만들면서, 단순히 로그인만 되는 게 중요한 게 아니라, 로그인한 사용자가 진짜 맞는지 확인하고, 로그인하지 않은 사람은 못 들어오게 막는 것이 더 중요하다는 걸 알게 됐습니다. 이런 보안 처리를 미들웨어로 하는 방법도 배울 수 있었습니다.
또한, HTML 폼에서는 기본적으로 GET과 POST 방식만 지원해서 글을 수정하거나 삭제할 때는 좀 불편했는데, method-override를 이용해 PUT이나 DELETE 요청처럼 처리하는 방법도 알게 돼서 많이 도움이 됐습니다.