1. API Server
1. Resp.java
@AllArgsConstructor
@Data
public class Resp<T> {
private Boolean success;
private String msg;
private T body;
public static <T> Resp<T> ok(T body) {
return new Resp<>(true, "성공", body);
}
public static <T> Resp<T> fail(String msg) {
return new Resp<>(false, msg, null);
}
}
- API 요청에 대한 응답을 JSON 형식으로 변환하여 반환하는 클래스
- 제네릭
<T>
를 사용하여 응답 본문(body
)을 다양한 타입으로 처리할 수 있다.
ok
와fail
메서드를 통해 간단하게 응답 객체를 생성할 수 있다.
2. BoardController.java
@CrossOrigin
@RequiredArgsConstructor
@RestController
public class BoardController {
private final BoardService boardService;
@GetMapping("/api/board")
public Resp<?> list() {
List<BoardResponse.DTO> boardList = boardService.게시글목록보기();
return Resp.ok(boardList);
}
@PostMapping("/api/board")
public Resp<?> save(@Valid @RequestBody BoardRequest.SaveDTO saveDTO, Errors errors) {
boardService.게시글쓰기(saveDTO);
return Resp.ok(null);
}
@PutMapping("/api/board/{id}")
public Resp<?> update(@PathVariable Integer id, @Valid @RequestBody BoardRequest.UpdateDTO updateDTO, Errors errors) {
boardService.게시글수정(id, updateDTO);
return Resp.ok(null);
}
@DeleteMapping("/api/board/{id}")
public Resp<?> delete(@PathVariable("id") Integer id) {
boardService.게시글삭제(id);
return Resp.ok(null);
}
@GetMapping("/api/board/{id}")
public Resp<?> detail(@PathVariable("id") Integer id) {
BoardResponse.DetailDTO boardDetail = boardService.게시글상세보기(id);
return Resp.ok(boardDetail);
}
}
@CrossOrigin
은 CORS를 처리하기 위한 어노테이션
Resp
를 반환하며, 와일드카드(<?>
)를 사용해body
에 다양한 타입의 데이터를 처리한다.
3. MyControllerAdvice.java
@RestControllerAdvice
public class MyControllerAdvice {
@ExceptionHandler(Exception400.class)
public ResponseEntity<?> err400(Exception400 e) {
ResponseEntity responseEntity = new ResponseEntity(Resp.fail(e.getMessage()), HttpStatus.BAD_REQUEST);
return responseEntity;
}
@ExceptionHandler(Exception404.class)
public ResponseEntity<?> err404(Exception404 e) {
ResponseEntity responseEntity = new ResponseEntity(Resp.fail(e.getMessage()), HttpStatus.NOT_FOUND);
return responseEntity;
}
}
- 예외 처리 시 반환 타입을
ResponseEntity
로 설정하고,body
에는Resp
를 사용하여 메시지를 넣으며, 상태 코드를 설정하여 반환한다.
2. SPA(Single Page Application)
1. index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>blog</title>
</head>
<body>
<nav>
<ul>
<li>
<a href="javascript:void(0);" onclick="renderList()">홈</a>
</li>
<li>
<a href="javascript:void(0);" onclick="renderSaveForm()">글쓰기</a>
</li>
</ul>
</nav>
<hr>
<section id="root">
</section>
<script src="js/rendering.js"></script>
<script src="js/api.js"></script>
<script src="js/main.js"></script>
</body>
</html>
- 기존의 Mustache 페이지 대신 단일 HTML 파일만을 사용한다.
- 하이퍼링크는 작동하지 않게 하고,
onclick
이벤트를 사용한다.
- JavaScript 파일을 분리하여 작성한다.
2. rendering.js
// list 디자인
function renderList() {
clear();
let dom = `
<table border="1">
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th></th>
</tr>
</thead>
<tbody id="list-box">
</tbody>
</table>
`;
root.innerHTML = dom;
sendList();
}
function renderListItem(board) {
let dom = `
<td>${board.id}</td>
<td>${board.title}</td>
<td><a href="javascript:void(0);" onclick="renderDetail(${board.id})">상세보기</a></td>
`;
let item = document.createElement("tr");
item.innerHTML = dom;
return item;
}
// detail 디자인
async function renderDetail(id) {
clear();
let board = await sendDetail(id);
state = board;
let dom = `
<form>
<button type="button" onclick="sendDelete(${board.id});">삭제</button>
</form>
<form>
<button type="button" onclick="renderUpdateForm(${board.id});">수정</button>
</form>
<div>
번호: ${board.id}<br>
제목: ${board.title}<br>
내용: ${board.content}<br>
작성일: ${board.createdAt}<br>
</div>
`
root.innerHTML = dom;
}
// saveForm 디자인
function renderSaveForm() {
clear();
let dom = `
<form>
<input type="text" id="title" placeholder="제목"><br>
<input type="text" id="content" placeholder="내용"><br>
<button type="button" onclick="sendSave();">글쓰기</button>
</form>
`;
root.innerHTML = dom;
}
// updateForm 디자인
async function renderUpdateForm(id) {
clear();
let dom = `
<form>
<input type="number" value="${state.id}" readonly><br>
<input type="text" id="title" placeholder="제목" value="${state.title}"><br>
<input type="text" id="content" placeholder="내용" value="${state.content}"><br>
<input type="text" value="${state.createdAt}"" readonly><br>
<button type="button" onclick="sendUpdate(${state.id});">수정</button>
</form>
`
root.innerHTML = dom;
}
// 화면 초기화
function clear() {
root.innerHTML = "";
}
renderList()
- 게시글 목록을 렌더링하는 함수
clear()
함수로 화면을 초기화한 후,dom
변수에 HTML을 구성하여root
에 삽입한다.sendList()
함수에서 게시글 목록을 받아와 화면을 채운다.
renderListItem(board)
- 각 게시글의 내용을 객체 형태로 만들어 반환하는 함수
dom
변수에 HTML을 구성하고,createElement
함수를 사용해tr
태그 안에 해당 객체를 넣는다.
renderDetail(id)
- 선택한 게시글의 상세 정보를 렌더링하는 함수
clear()
함수로 화면을 초기화하고,sendDetail()
함수로 게시글 데이터를 받아온다.- 받은
board
객체는state
에 저장해 다른 함수에서 활용할 수 있도록 한다. dom
변수에 HTML을 구성하여root
에 삽입한다.
renderSaveForm()
- 게시글 작성 화면을 렌더링하는 함수
clear()
함수로 화면을 초기화한 후,dom
변수에 HTML을 구성하여root
에 삽입한다.
renderUpdateForm(id)
- 게시글 수정 화면을 렌더링하는 함수
clear()
함수로 화면을 초기화하고,renderDetail(id)
에서 저장한state
를 활용하여 수정할 게시글 데이터를 채운다.dom
변수에 HTML을 구성하여root
에 삽입한다.
3. api.js
async function sendList() {
// 1. API 요청
let response = await fetch("http://localhost:8080/api/board");
let responseBody = await response.json();
// 2. 응답 처리
let boards = responseBody.body;
let listBox = document.querySelector("#list-box");
boards.forEach(board => {
let item = renderListItem(board);
listBox.append(item);
});
}
async function sendDetail(id) {
// 1. API 요청
let response = await fetch(`http://localhost:8080/api/board/${id}`);
let responseBody = await response.json();
// 2. 응답 처리
return responseBody.body;
}
async function sendSave() {
// 1. 사용자 입력값 받기
let board = {
title: document.querySelector("#title").value,
content: document.querySelector("#content").value,
};
// 2. JSON 변환
let requestBody = JSON.stringify(board);
// 3. API 요청
let response = await fetch("http://localhost:8080/api/board", {
method: "POST",
headers: {
"Content-Type": "application/json; charset=utf-8"
},
body: requestBody
});
let responseBody = await response.json();
// 4. 응답 처리
if (responseBody.success) {
renderList();
} else {
alert(responseBody.msg);
}
}
async function sendDelete(id) {
// 1. API 요청
let response = await fetch(`http://localhost:8080/api/board/${id}`, {
method: "DELETE"
});
let responseBody = await response.json();
// 2. 응답 처리
if (responseBody.success) {
renderList();
} else {
alert(responseBody.msg);
}
}
async function sendUpdate(id) {
// 1. 사용자 입력값 받기
let board = {
title: document.querySelector("#title").value,
content: document.querySelector("#content").value,
};
// 2. JSON 변환
let requestBody = JSON.stringify(board);
// 3. API 요청
let response = await fetch(`http://localhost:8080/api/board/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json; charset=utf-8"
},
body: requestBody
});
let responseBody = await response.json();
// 4. 응답 처리
if (responseBody.success) {
renderDetail(id);
} else {
alert(responseBody.msg);
}
}
- 비동기 함수 앞에
await
를 붙인다.
- 비동기 함수를 사용하는 함수 앞에는
async
를 붙인다.
sendList()
- API GET 요청을
fetch
함수로 보내고, 결과를response
에 저장한다. response
를 JSON 객체로 변환하여responseBody
에 저장한다.json
함수는 비동기 함수이므로 앞에await
를 붙인다.responseBody
의body
를boards
에 저장하고,listBox
를 가져와forEach
문을 사용해renderListItem(board)
를 호출하여 각 항목을listBox
에 추가한다.
sendDetail(id)
- API GET 요청을 보내고, 응답을 처리하여 내용을 반환한다.
sendSave()
- 입력 값을
board
객체로 만든 후JSON.stringify(board)
함수를 사용해 JSON 문자열로 변환한다. - API POST 요청의
body
에 JSON 데이터를 추가하여 전송하고, 응답을 처리한다.
sendDelete(id)
- API DELETE 요청을 보내고, 응답을 처리한다.
sendUpdate(id)
- 입력 값을
board
객체로 만든 후JSON.stringify(board)
함수를 사용해 JSON 문자열로 변환한다. - API PUT 요청의
body
에 JSON 데이터를 추가하여 전송하고, 응답을 처리한다.
4. main.js
// state
let state = {};
// init
let root = document.querySelector("#root");
renderList2();
- 초기화를 하는 스크립트
- 스크립트 중 가장 마지막에 로드되어야 한다.
Share article