안녕하세요!
Betalabs의 Soora입니다.
개발에서 은탄환은 없지만, 많은 기술을 알고 있으면 선택지가 넓어져서 더 좋은 선택을 할 수 있다고 생각합니다.
오늘은 서버 기술보다는 프론트 영역에 조금 더 가까운 htmx
에 대해서 이야기해보겠습니다.
요새는 많은 회사가 전문적으로 프로젝트를 진행하기 위해 프론트팀과 백엔드 팀을 나눠 업무를 진행합니다. 하지만 제가 처음 개발을 시작했던 회사에는 프론트 팀은 없었고, 퍼블리셔가 틀이 되는 HTML
과 CSS
를 작성하여 백엔드팀에 전달해주면 JSP
나 Thymeleaf
를 사용하여 MPA
로 Web Application
을 개발했습니다. MPA
로 구현된 Web Application
에서는 사용자가 액션을 하면 Page
가 이동되며 약간의 불편함이 있었습니다. 하지만 그때도 SPA
기반의 Web Application
을 제공했습니다. 하지만 조금 더 많은 시간이 흐른 뒤 사용자의 편리함이 중요시됨과 동시에 SPA
이 더 대중화가 되어 가는듯한 느낌을 받았습니다. 요새는 MPA
보다 SPA
을 더 많이 경험하고 있는 것 같습니다.
MPA
는 Multi Page Application
을 이야기합니다. 이름에서 알 수 있듯이 하나의 Page
만을 통해 Web Application
을 구성하는 게 아닌, 여러 Page
를 통해 Web Application
을 구현합니다. 사용자가 Link
나 Form
을 통한 Data
를 전송하면 새로운 Page
로 이동이 됩니다. 이미 우리는 이러한 경험을 많이 해봤고, 최근에도 구글 주 페이지에서 검색을 하면 이런 경험을 할 수 있습니다. 이런 전통적인 MPA
를 사용하면 각 Page
마다 HTML
을 포함한 정적 리소스를 받아오며, Page
전체를 다시 Rendering
하는 과정을 통해 불편한 경험을 느낄 수 있습니다. 대신 MPA
는 백엔드 개발팀만 있어도 개발이 가능할 정도로 손쉽게 접근을 할 수 있습니다.
MPA
를 통해 웹 애플리케이션을 구현한다면 Spring
과 함께 JSP
, Freemarker
, Thymeleaf
를 통해서 구현할 수 있습니다.
SPA
는 Single Page Application
을 이야기합니다. SPA
는 Web Application
전체를 하나의 Page
에서 제공이 되며, Link
나 Form
을 통한 Data
를 전송하면 새로운 Page
로 이동되는 것이 아닌, 최초 Rendering
된 Page
에서 필요한 부분만 새롭게 Rendering
을 합니다. 이러한 특징으로 MPA
와 다르게 최초의 Page
에서 다시 Rendering
을 하므로 사용자의 경험을 향상할 수 있습니다. 이미 SPA
는 많은 Web Application
에서 경험하실 수 있으며, FaceBook이나 Instagram에서 새로운 Feed를 가져오는 기능과 Velog도 SPA
를 통해 Web Application
를 제공하고 있는 것으로 확인이 됩니다.
하지만 SPA
는 전문적인 팀을 구성할 정도로 초기 구성이 복잡하고, 러닝커브가 MPA
보다 높습니다.
SPA
를 통해 Web Application
을 구현한다면 React
, Angular
, Vue.js
등을 통해 구현할 수 있습니다.
이미 팀 빌딩이 된 상태로 프로젝트를 진행하면 백엔드 팀은 프론트 팀에게 API
에 대한 제약 조건과 예외 상황 등의 도메인 지식과 API
스펙 문서를 작성하여 전달 합니다. 처음부터 이 모든 게 완벽했으면 좋겠지만, 실수 또는 요구사항의 변경으로 다시 공유하는 상황이 발생합니다. 또 다른 문제로는 백엔드 팀의 실수나 프론트 팀의 실수로 스펙에 맞지 않는 요청/응답이 발생한 경우 모두가 다 같이 모여서 디버깅을 진행합니다. 결국 문제를 해결하기 위해서는 백엔드 팀과 프론트 팀이 옹기종기 모여서 디버깅하고, 모두의 리소스를 소모하게 됩니다. 팀이 이미 잘 구성된 팀은 그나마 행복해 보일 수 있어 보입니다. 만약 백엔드 팀만으로 상품을 출시해야 하는 스타트업의 경우, 사용자의 경험을 향상하기 위해서 Page
이동이 없는 동적 Rendering
을 구현해야 하는데 SPA
의 높은 러닝 커브를 극복하기는 쉽지 않을 것입니다.
문제가 되는 API
스펙 문서의 경우 asciidoc
과 같은 문서화를 통해 코드와 문서를 일치하게 했고, 문서가 변경되면 CI
를 할 때 이미 Slack을 통해 알림을 주는 방식으로 해결할 수 있습니다.
하지만 다른 문제들은 소프트웨어적으로 해결하기는 쉽지 않아 보이고, 해결하기 위해서는 한 사람(또는 한 팀)이 해결할 수 있도록 응집시키는 방법을 생각해볼 수 있으나, 백엔드 개발자가 Vue.js
나 React
를 공부하기는 쉽지 않고, 프론트 개발자가 Spring
과 DB
를 공부하기는 더더욱 쉽지 않아 보입니다. 또 다른 방법으로는 초심으로 돌아가서 백엔드 팀이 SSR
를 하여 전통적인 MPA
으로 Web Application
을 구현한다면, 사용자 경험 향상은 어렵고 불편하게만 느껴질 것입니다.
만약 Data
를 가지고 Rendering
을 하는 게 아닌, 이미 Rendering
된 Page
를 받아서 필요한 부분에 넣어준다면 MPA
와 SPA
의 장점을 잘 활용할 수 있을 것입니다. 하지만 Ajax
를 사용하여 이런 기능을 하는 Javascript
를 직접 개발, 문서화, 유지보수하는 것은 더 쉽지 않을 수 있습니다.
만약 이런 기능을 해주는 Library
가 있다면 백엔드 팀 혼자서도 어느 정도 수준의 사용자 경험 향상된 웹 애플리케이션을 구현할 수 있지 않을까요?
htmx
(https://htmx.org/)는 HTML
을 사용하여 동적으로 Page
를 Rendering
할 수 있도록하는 Javascript
Library
입니다.htmx
는 HTML
태그에 정의된 속성을 추가하여 서버와의 통신을 할 수 있도록 하며, 서버로부터 HTML
을 받아서 정의된 속성을 기반으로 필요한 부분만 새롭게 Rendering
을 할 수 있도록 도와줍니다. 결국 HTML
태그에 정의된 속성만을 사용하여 Rendering
을 할 수 있으니, Javascript
없이 HTML
만으로도 SPA
의 특징을 쉽고 간단하게 구현할 수 있습니다.
htmx
는 SPA
의 특징을 가지고 있지만, SPA
는 서버로부터 Page
를 받는 게 아닌, Data
를 받아서 최초의 Page
에서 Rendering
을 하는 것을 의미합니다. 하지만 htmx
는 서버에서 Page
를 받고 Rendering
을 하기에 SPA
라고 부르기는 다소 어려운 부분이 있습니다.
htmx
는 Javascript
Library
이므로, 원하는 곳에 다음과 같이 CDN
을 통해서 Javascript
를 받아오는 코드를 넣어주면 됩니다. 필요에 따라서는 다운로드 후 프로젝트에 넣어주시면 됩니다.
<script src="https://unpkg.com/htmx.org@1.8.6"></script>
원하는 태그에 htmx
의 속성을 넣으면 Javascript
없이 서버에 요청 및 Rendering
이 됩니다. 속성은 hx-*
로 시작되며, 다음과 같은 속성을 가지고 있습니다
hx-{httpMethod}
: GET
또는 POST
등의 http method
를 사용할 수 있으며, 요청할 URL
을 작성해주면 됩니다.hx-trigger
: URL
로 요청할 trigger
를 작성합니다. 대표적으로 click
, load
, submit
등이 있습니다.hx-target
: 서버로부터 받은 Page
를 넣을 위치를 정의합니다.hx-swap
: 서버로부터 받은 Page
를 어떻게 교체할 것인지 정의합니다.Rendering
Page
가 load
가 된 후 서버에 GET
요청하여 HTML
를 받아서 Rendering
하는 예제를 Spring Boot
와 Thymeleaf
로 구현해보겠습니다.
아래의 코드는 /load
로 요청이 오면, load.html
을 응답을 주고, /get-load
요청이 오면 Hello, htmx
를 반환해주는 Controller
입니다. 여기서 /get-load
도 .html
파일을 통해 응답을 줄 수 있지만, 간단한 예시이므로 @ResponseBody
를 통해 바로 HTML
을 응답 주도록 구성했습니다.
@Controller
public class LoadController {
@GetMapping("/load")
public String index() {
return "load";
}
@GetMapping("/get-load")
@ResponseBody
public String load() {
return "<h1>Hello, htmx</h1>";
}
}
아래의 코드는 load.html
이며, 최초에 브라우저에 보여줄 Page
입니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LoadTest</title>
</head>
<body>
Betalabs
<div hx-get="/get-load" hx-trigger="load delay:2s">
</div>
<script src="https://unpkg.com/htmx.org@1.8.6"></script>
</body>
</html>
위의 코드를 실행해보면 다음과 같은 결과를 볼 수 있습니다.
Betalabs
라는 글자가 보이고, 2초뒤에 자동으로 Hello, htmx
를 볼 수 있습니다.
여기서 사용된 태그와 값은 다음과 같습니다.
hx-get
: 서버로 /get-load
를 GET
으로 요청합니다.hx-trigger
: hx-get
을 실행시킬 조건을 설정합니다.load
: Page
가 load
가 되면 실행이 될 수 있도록 명시했습니다.delay
: 조건이 충족된 후 몇 초 뒤에 실행이 될 건지 명시했습니다. 예제에서는 Page
load
후 2초뒤에 요청이 됩니다. 만약 delay
를 정의하지 않는다면 즉시 요청됩니다.해당 예제를 통해서 htmx
의 사용법은 매우 간단하고, 강력한것을 알 수 있습니다.
예제1에서는 간단하게 htmx
가 무엇인지 알았다면, 이번 예제에서는 데이터를 쓰고 3초에 한 번씩 폴링하는 예제를 작성해보겠습니다. @PostMapping
을 통해 POST
만 요청을 받아 게시글을 작성할 수 있도록 구현하고, @GetMapping
을 통해 작성된 게시글의 HTML
을 반환하는 예제의 Contoller
입니다.
@Controller
public class BoardController {
private List<String> boards = new ArrayList<>();
@GetMapping("/board")
public String index() {
return "board";
}
@PostMapping("/post-board")
@ResponseBody
public String board(String title) {
boards.add(title);
return "<p style='color:red'>작성을 완료했습니다.</p>";
}
@GetMapping("/get-board")
@ResponseBody
public String board() {
String template = "<div>%s</div>";
StringBuilder result = new StringBuilder();
for(String board: boards) {
result.append(String.format(template, board));
}
return result.toString();
}
}
아래의 코드는 board.html
이며, 최초에 브라우저에 보여줄 Page
입니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>BoardTest</title>
</head>
<body>
<form hx-post="/post-board" hx-trigger="submit">
제목: <input type="text" name="title">
<button type="submit">글 쓰기</button>
</form>
<div hx-get="/get-board" hx-trigger="every 3s">
</div>
<script src="https://unpkg.com/htmx.org@1.8.6"></script>
</body>
</html>
예제를 실행하면 초기에는 Data
가 담긴 어떤 Page
도 보이지 않고, 입력 Page
만 보입니다. 제목을 입력하고 글쓰기 버튼을 클릭하면 서버로 글쓰기 요청이 전달되고, 서버는 List
에 저장합니다. 그 후 응답으로 빨간 글씨로 저장이 되었다는 것을 알려줍니다. 그리고 form
밑에 정의된 div
에서 3초에 한 번씩 /get-board
에 요청하여 저장된 결과를 볼 수 있습니다.
우리는 이번 예제를 통해 더 많은 htmx
사용법을 알게 되었습니다.
hx-post
: POST
요청을 보낼 수 있습니다.submit
: submit
이 되면 post-board
에 요청을 할 수 있도록 설정하였습니다.every
: 폴링을 위해 매번 정의된 시간에 한 번씩 요청할지 정하였습니다.Infinity Scroll
Javascript
없이 많은 기능을 구현해야 htmx
를 사용하는 많은 의미가 있을 것 같습니다. htmx
는 사용자 경험 향상을 위해 많은 기능을 구현할 수 있도록 제공하고 있고, 기능을 활용하여 Infinity Scroll
도 쉽게 구현이 가능합니다.
인피니티 스크롤은 Facebook이나 Instagram에서 마지막의 피드를 보게 되면 새로운 피드를 얻어오는 것처럼 끝없는 스크롤을 의미합니다. 아래의 코드는 /infinity-scroll
요청이 들어오면 infinity-scroll.html
을 응답하고, /get-infinity-scroll
요청으로 들어오면 div
태그를 10개를 생성해서 반환을 주는 Controller
입니다. 여기서 중요하게 봐야 하는 부분은 마지막 태그에는 더 많은 속성을 부여한 것입니다.
@Controller
public class InfinityScrollController {
@RequestMapping("/infinity-scroll")
public String index() {
return "infinity-scroll";
}
@GetMapping("/get-infinity-scroll")
@ResponseBody
public String infinityScroll(@RequestParam(defaultValue = "1") Integer page) {
String generalTemplate = "<div>page: %d index: %d</div>";
String lastTemplate =
"<div hx-get='/get-infinity-scroll?page=%d'" +
" hx-trigger='revealed'" +
" hx-swap='afterend'>" +
" page: %d index: %d" +
"</div>";
StringBuilder result = new StringBuilder();
for (int index = 0; index < 10; index++) {
if (index <= 8) {
result.append(String.format(generalTemplate, page, index));
} else {
int nextPage = page + 1;
result.append(String.format(lastTemplate, nextPage, page, index));
}
}
return result.toString();
}
}
아래의 코드는 infinity-scroll.html
이며, 최초에 브라우저에 보여줄 Page 입니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>InfinityScrollTest</title>
</head>
<body>
<div id="rows" hx-get="/get-infinity-scroll" hx-trigger="load">
</div>
<script src="https://unpkg.com/htmx.org@1.8.6"></script>
</body>
</html>
위의 코드를 실행해보면 초기에 load
가 되었을 때 /get-infinity-scroll
을 요청하여 Rendering
을 하고, 마지막 Data
가 화면에 보였을 때 다음 Page
를 요청하는 것을 확인할 수 있습니다.
여기서 htmx
가 제공하는 기능을 활용하여 Infinity Scroll
을 구현했으며, 다음과 같은 속성과 값을 사용했습니다.
revealed
: 해당 값으로 설정하여 마지막 Data
가 보이면 trigging
을 할수 있습니다.afterend
: HTML
전체를 수정하는 것이 아니라, 결과를 태그 뒤에 붙이는 것으로 정의했습니다.아직 사용하면서 큰 불편함을 느끼지는 못했지만, WebSocket
을 사용할 때는 고민이 필요해 보입니다. htmx
에서 제공하는 WebSocket
은 SockJs
나 STOMP
를 제공하지 않는 것으로 보이고, WebSocket
또한 HTML
을 통해 응답받아야 합니다.
백엔드 팀이 혼자 작업하는 백오피스나 프론트 팀을 따로 구성하기 어려운 환경에 있는 스타트업 또는 토이 프로젝트의 경우 htmx
는 도입해도 괜찮을 것 같습니다.
htmx
를 사용하면 백엔드 팀만으로 SPA
의 특징을 구현할 수 있으며, 쉽게 사용자 경험 향상을 할 수 있습니다. 해당 글은 간단한 예제를 통해 htmx
를 소개했지만, htmx
는 더욱 많은 기능을 소개하고 있으니 문서를 통해 공부를 해보는 것을 추천하며 Spring
에서 Thymeleaf
를 확장한 기능도 오픈소스로 제공하고 있으니, 참고하시면 더 쉽고 유연하게 개발할 수 있습니다.
제가 이해한 바로는 "나 혼자 다 해먹을 htmx"는 혼자서 HTMX라는 기술을 모두 습득하겠다는 의지를 나타내는 말인 것 같습니다.
HTMX는 HTML, CSS, JavaScript를 사용하여 비동기적으로 서버와 클라이언트 간의 상호작용을 구현하는 웹 프론트엔드 기술입니다. HTMX는 AJAX, WebSockets 및 기타 비동기 기술과 같은 전통적인 웹 개발 패턴과는 다르게, 기존 HTML 요소와 이벤트를 사용하여 서버와의 상호작용을 처리합니다.
HTMX를 혼자서 모두 습득하고 응용할 수 있다면, 높은 수준의 웹 개발 기술을 습득할 수 있을 것입니다. HTMX를 공부하기 위해서는 HTML, CSS, JavaScript, 웹 개발의 기본적인 개념에 대한 이해가 필요합니다. 또한 HTMX는 서버와의 상호작용을 처리하기 때문에 백엔드 개발에 대한 이해도 필요합니다.
MyHealthAtVanderbilt Login
HTMX를 공부하는 것은 웹 개발 분야에서 경쟁력 있는 역량을 갖추는 것에 도움이 될 수 있습니다. 하지만 혼자서 모든 것을 습득하는 것은 어렵기 때문에 관심 있는 분야에서 팀원들과 함께 공부하고 협업하는 것이 좋습니다.