( GET: /
, DELETE: '/blog/delete' )
<> index.ejs 코드
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>메인페이지</title> <!-- head include --> // head.ejs 코드 맨 아래에 전체 적용할 CSS or JS 첨부 <%- include('./include/head')%> </head> <body> <!-- header는 (ejs) include 해줄 것임 --> <!-- <header>홈으로 글쓰러가기</header> --> <%- include('./include/header')%> <!-- { user: userID, contentData: tempDB, //[{}, {}] } --> <h2>안녕하세요 <%=user%>의 블로그입니다!</h2> <ul> <!-- 서버로부터 받아온 contentData를 for문으로 글 목록(<li></li> <button>) 자동 생성되게 함 --> <%for(let content of contentData) {%> <li> <a href="/content/<%=content.contentID%>"><%= content.title%></a> <button onclick="deleteContent('<%=content.contentID%>', this)">삭제</button> <!-- deleteContent() 함수 두 번째 인자로 button(this) 태그 넘겨줌--> </li> <%}%> </ul> <script> function deleteContent(contentID, btn) { /* 삭제 기능 : 버튼 눌렀을 때 글의 ID 번호를 매개변수로 해당 글 삭제 */ // console.log(contentID); // 글 ID 번호 // console.log(btn); // this로 전달된 button 태그 if (confirm("정말 삭제하실건가요?😫")) { // 삭제 로직 axios({ method: "delete", url: "/blog/delete?contentID=" + contentID, }) .then((res) => { console.log(res); console.log(res.data); }) .catch((err) => console.error(err)); // console.log(btn.parentNode); // <li> ⋯ </li> btn.parentNode.remove(); } } </script> </body> </html>
<> app.js 코드
// GET 요청 app.get("/", (req, res) => { // res.render("렌더할 view 파일명"[, { 렌더 페이지에 뿌려줄 data가 담긴 객체 }]) res.render("index", { user: userId, contentData: tempDB, //[{}, {}] - 임시 DB }); });
// DELETE 요청 (axios) app.delete("/blog/delete", (req, res) => { console.log(req.query); const { contentID } = req.query; // 객체 구조분해할당 tempDB = tempDB.filter((obj) => obj.contentID !== Number(contentID)); // req.query에 담겨온 contentID와 일치하지 않는 객체만 배열로 다시 return console.log(tempDB); // res.send("ddddd"); res.end(); // send() 랑 동시 사용 X, send()로 데이터 넘겨줄 필요 없어서 end() 사용 });
( /content/:contentID
)
<> content.ejs 코드
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <%- include('./include/head')%> <style> h1, div { text-align: center; } p { white-space: pre-line; } </style> </head> <body> <%- include('./include/header')%> <!-- { contentID: 1, title: "글제목1", content: "글 내용1", img: null, // null or path(string) } --> <h1><%= title%></h1> <div> <p><%= content%></p> <%if(img){%> <img src="/<%= img%>" alt="<%= contentID%>번 글 이미지" width="200" /> <!-- src="/~~" 지정할 때 slash 꼭!! 쓰기 --> <%}%> </div> </body> </html>
<> app.js 코드
app.get("/content/:contentID", (req, res) => { // ':' params 명시 키워드 console.log(req.params); // {contentID:'1'}; const { contentID } = req.params; const isContent = tempDB.filter((obj) => obj.contentID === Number(contentID))[0]; // req.params로 넘어온 contentID와 일치하는 객체만 배열에 담아 return console.log(isContent); // 객체({}) or undefined /* { contentID: 1, title: "글제목1", content: "글 내용1", img: null, // null or path(string) } */ if (isContent) res.render("content", isContent); // isContent가 true면 content.ejs 렌더링 else res.render("404"); // 아니면 404 페이지 렌더링 // res.send("hi~~"); });
< params vs. query >
- params
- 서버에서 url 표기 시
/:params
로 작성- 클라이언트에서 요청 시
/123
로 나타남- req.params 에서 정보 확인 가능 -> {params: '123'}
- 네이버 블로그 처럼 여러 계정의 글을 "조회"할 때 ==> params 사용
- query
- 서버에서 url 표기 시
/sesac
- 클라이언트에서 url 표기 시
/sesac?id=123&content=123...
- req.query에서 정보 확인 가능 -> {id: '123', content: '123'}
- 검색, 필터링 기능 ==> query 사용
( GET: /write
, POST: /blog/post
)
<> wirteContent.ejs 코드
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>글 작성하기(폼 태그 제출)</title> <%- include('./include/head')%> <style> h1 { text-align: center; } form { text-align: center; border: 1px solid #ddd; border-radius: 20px; padding: 1rem 0; margin: 2rem; } form textarea { width: 400px; height: 500px; } form input[type="text"] { width: 400px; } button { border: none; border-radius: 10px; font-weight: 700; padding: 8px; } </style> </head> <body> <%- include('./include/header')%> <h1>글 작성하기</h1> <form action="/blog/post" method="post" enctype="multipart/form-data"> <!-- <form> 태그와 axios 중복 사용 ❌, multer 사용 시 enctype="multipart/form-data" 필수 작성!! --> <input type="text" name="title" placeholder="글 제목을 작성해주세요" /> <br /> <br /> <textarea name="content" placeholder="글 내용을 작성해주세요😀"></textarea> <br /> <input type="file" name="img" /> <br /> <br /> <button type="submit">등록 💌</button> </form> </body> </html>
<> app.js 코드
// 새 글 작성 form 페이지 렌더링 // 이 때, `/content/write`로 경로 지정할 경우 `write`를 params로 착각할 수 있어서 그렇게 하면 안됨! app.get("/write", (req, res) => { res.render("writeContent"); }); // 새 글 작성 form에 입력한 내용 DB에 추가 (POST) app.post("/blog/post", uploadDetail.single("img"), (req, res) => { // multer 사용 시 두 번째 인자로 미들웨어 호출 해줘야 함 // uploadDetail.single("img") 에서 img는 <input type="file" name="img" />의 name이다 // console.log(req.body); // console.log(req.file); // DB에 추가 : tempDB에 객체 형태로 push (keyName 똑같이 쓰기) tempDB.push({ contentID: tempDB.length !== 0 ? tempDB[tempDB.length - 1].contentID + 1 : 1, // ⬆⬆ tempDB가 비워져있지 않으면 ? tempDB의 마지막 객체의 contentID에 +1 : 그렇지 않으면 1번으로 push title: req.body.title, content: req.body.content, img: req.file ? req.file.path : null, // null or path(string) // ⬆⬆ 파일을 업로드하면 ? 파일 경로 넘겨줌 : 그렇지 않으면 null }); //console.log(tempDB); res.redirect("/"); // DB에 추가 후(= POST 요청이 끝나면) Home('/')으로 리다이렉션 });
잘못된 url로 들어왔을 때 보여줄 페이지로, app.js의 라우팅 코드 맨 아래 위치해야 한다.
( == app.listen()
바로 위쪽에 위치)
그래야 모든 라우팅에 해당하지 않을 때 404 페이지를 렌더링 함
html 코드는 하고싶은데로 작성하면 되기 때문에 app.js 코드만 올림!
// 404 페이지 처리 app.get("*", (req, res) => { res.render("404"); });