관계형 데이터베이스에서는 Menu나 Category 같은 계층형 데이터를 트리 구조로 표현하기 위해 자기 참조 테이블을 사용하는 경우가 많습니다.
이 데이터를 웹 UI에서 시각화하고 조작하려면 트리 형태로 직접 프론트를 구현하거나 렌더링해주는 도구가 필요합니다.
jsTree는 jQuery 기반 라이브러리로, JSON 데이터를 트리 구조로 표현하고 드래그 앤 드롭 같은 기능을 제공합니다.
이 글에서는 jsTree를 사용해 메뉴 데이터를 트리로 표현하고, 노드를 이동하거나 순서를 바꾸는 방법을 정리합니다.
jsTree는 JSON 데이터를 기반으로 트리 UI를 렌더링하는 jQuery 라이브러리 입니다.
기본적인 트리 뷰 외에도 드래그 앤 드롭, 체크박스, 아이콘 커스터마이징 등 다양한 플러그인을 지원합니다.
트리 UI를 직접 구현하지 않아도 jsTree가 제공하는 기본 템플릿을 사용할 수 있습니다.
UI 커스터마이징은 다소 제한적이지만, 메뉴나 카테고리 같은 계층형 데이터를 관리하는 관리자 페이지에서는 간단하게 적용하기에 적합 합니다.
백엔드에서는 JSON 형태로 트리 데이터를 제공하고, 노드 이동 등의 이벤트에 맞춰 구조를 저장하는 API를 구현하면 됩니다.
jsTree는 트리 노드를 표현하기 위해 특정한 JSON 구조를 사용합니다.
주로 두 가지 포맷을 사용할 수 있습니다.
//1. 기본 Json 포멧
{
id : "string" // 노드 ID (생략하면 자동 생성)
text : "string" // 노드 텍스트
icon : "string" // 커스텀 아이콘 (이미지 경로나 클래스명 지정)
state : {
opened : boolean // 노드가 열려 있는지 여부
disabled : boolean // 노드가 비활성화 상태인지 여부
selected : boolean // 노드가 선택되어 있는지 여부
},
children : [] // 문자열 또는 객체 배열로 자식 노드 정의
li_attr : {} // 생성된 LI 태그에 대한 속성
a_attr : {} // 생성된 A 태그에 대한 속성
}
//2. 대안 Json 포멧
{
id : "string" // 노드 ID (생략하면 자동 생성)
parent : "string" // 부모 노드 ID ("#"은 루트)
text : "string" // 노드 텍스트
icon : "string" // 커스텀 아이콘 (이미지 경로나 클래스명 지정)
state : {
opened : boolean // 노드가 열려 있는지 여부
disabled : boolean // 노드가 비활성화 상태인지 여부
selected : boolean // 노드가 선택되어 있는지 여부
},
li_attr : {} // 생성된 LI 태그에 대한 속성
a_attr : {} // 생성된 A 태그에 대한 속성
}
이 글에서는 트리 구조를 다루는 예시로 메뉴 엔티티를 사용합니다.
메뉴는 계층형 구조로 구성되며, Menu 엔티티는 부모-자식 관계를 갖는 자기 참조 형태로 정의합니다.
@Entity
public class Menu {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Menu parent;
private String name;
private Integer menuOrder;
@OneToMany(mappedBy = "parent")
private List<Menu> subMenus = new ArrayList<>();
}
트리 구조에서 노드 간의 순서를 유지하려면 menuOrder 와 같은 정렬용 필드를 별도로 두고 관리해야 합니다.
jsTree에서 노드를 시각적으로 정렬하려면 이 값을 기준으로 정렬된 데이터를 제공해야 하며,
백엔드에서는 이 필드를 활용해 정렬된 결과를 반환하도록 처리해야 합니다.
이 엔티티를 기반으로 jsTree에서 요구하는 JSON 포맷에 맞게 DTO를 생성하면 됩니다.
DTO는 children 필드를 재귀적으로 구성해 중첩된 트리 구조를 표현 합니다.
트리의 정렬이 필요한 경우, 백엔드에서 특정 필드 (menuOrder) 기준으로 정렬 로직을 구현 합니다.
기본 필드 외에도 icon, li_attr, a_attr 등의 속성을 추가해 커스터마이징할 수 있습니다.
public class JsTreeMenuResponse {
private final String id;
private final String text;
private final List<JsTreeMenuResponse> children;
public JsTreeMenuResponse(String id, String text, List<JsTreeMenuResponse> children) {
this.id = id;
this.text = text;
this.children = children;
}
public static JsTreeMenuResponse from(Menu menu) {
List<JsTreeMenuResponse> childNodes = menu.getSubMenus().stream()
.sorted(Comparator.comparing(Menu::getMenuOrder))
.map(JsTreeMenuResponse::from)
.toList();
return new JsTreeMenuResponse(
String.valueOf(menu.getId()),
menu.getName(),
childNodes
);
}
}
jsTree는 트리 구조를 JSON 형태로 받아 트리를 렌더링 합니다.
이를 위해 메뉴 데이터를 jsTree의 포맷에 맞춰 반환하는 API를 구성 합니다.
아래는 /api/menu-tree 엔드포인트를 통해 전체 메뉴를 정렬된 순서로 조회하고, jsTree에서 사용할 수 있는 형태로 변환해주는 예제 입니다.
// MenuRestController.java
@RequiredArgsConstructor
@RestController
public class MenuRestController {
private final MenuService menuService;
@GetMapping("/api/menu-tree")
public ApiResponse<List<JsTreeMenuResponse>> getMenuTree() {
List<JsTreeMenuResponse> menus = menuService.findAllByOrder().stream()
.map(JsTreeMenuResponse::from)
.toList();
return ApiResponse.ok(menus);
}
}
---
// MenuService.java
@RequiredArgsConstructor
@Service
public class MenuService {
private final MenuRepository menuRepository;
public List<Menu> findAllByOrder() {
return menuRepository.findAllByOrderByMenuOrder();
}
}
---
// MenuRepository.java
public interface MenuRepository extends JpaRepository<Menu, Integer> {
List<Menu> findAllByOrderByMenuOrder();
}
<!-- 트리 UI를 그릴 DOM 요소 -->
<div id="menuTree"></div>
<script src="/js/jquery-3.7.1.min.js"></script>
<script src="/dist/jstree.js"></script>
<script src="/js/menus.js"></script>
const initMenuTree = () => {
$.ajax({
url: '/api/menu-tree',
method: 'GET',
success: (response) => {
drawMenuTree(response.data);
},
error: (error) => {
console.error("Failed to load menu tree:", error);
}
});
};
const drawMenuTree = (data) => {
$('#menuTree').jstree({
core: {
animation: 0, // 노드 열고 닫을 때 애니메이션 효과 제거
themes: {
variant: 'large' // 트리 테마 크기 설정 (기본, large, small 등)
},
data: data, // 서버에서 받은 트리 데이터 입력
check_callback: true // 노드 이동, 수정 등 변경 허용 (필수 옵션)
},
plugins: [
'dnd', // Drag & Drop 기능
'types', // 노드별 유형 설정 가능 (icon, 제한 등)
'state' // 트리 상태(열림/닫힘 등) 저장 및 복원
],
types: {
default: {
icon: false // 기본 아이콘 비활성화
}
}
});
};
$(document).ready(() => {
initMenuTree();
});
jsTree의 설정 옵션은 위 코드에 포함된 주요 항목 외에도 다양하게 제공 됩니다.
필요한 추가 설정은 공식 문서를 참고. → https://www.jstree.com/docs/config



jsTree는 드래그 앤 드롭 기능을 기본 플러그인으로 제공하며,
트리 노드를 이동할 경우 move_node.jstree 이벤트가 발생 합니다.
이 이벤트를 활용해 서버에 순서 변경 요청을 보내는 API를 구현하면 됩니다.
아래 예시는 하나의 노드를 다른 위치로 이동했을 때,
그에 따라 다른 노드들의 순서(order)가 어떻게 재조정되는지를 보여 줍니다.

jsTree의 드래그 앤 드롭 기능은 dnd 플러그인과 check_callback: true 설정을 통해 활성화할 수 있습니다.
아래 코드는 트리를 초기화할 때 필요한 설정 입니다.
const drawMenuTree = (data) => {
$('#menuTree').jstree({
core: {
animation: 0,
themes: { variant: "large" },
data: data,
check_callback: true // 노드 이동, 생성, 삭제 등 허용
},
types: {
default: { icon: false }
},
plugins: ['dnd', 'types', 'state'] // 드래그 앤 드롭 플러그인 포함
});
};
트리에서 노드가 이동되면 move_node.jstree 이벤트가 발생 합니다.
이 이벤트를 통해 서버로 변경된 구조를 전달할 수 있습니다.
$('#menuTree').on('move_node.jstree', (e, data) => {
dragAndDrop(data);
});
const dragAndDrop = (data) => {
const request = JSON.stringify({
id: data.node.id,
oldParent: data.old_parent,
newParent: data.parent,
oldOrder: data.old_position,
newOrder: data.position,
});
$.ajax({
url: `/api/menus/${data.node.id}`,
method: 'PATCH',
contentType: 'application/json',
data: request,
success: () => {
window.location.href = "/admin/menus";
},
error: (error) => {
console.error("Update failed", error);
},
});
};
드래그 앤 드롭으로 메뉴를 옮기면, 이동한 노드뿐만 아니라 같은 계층에 있는 다른 노드들의 순서(order)도 함께 변경되어야 합니다.
예를 들어, 노드를 아래쪽으로 이동하면 사이에 있던 노드들의 순서는 하나씩 줄어들고,
위쪽으로 이동하면 순서가 하나씩 증가해야 합니다.
또한, 다른 계층(부모)으로 옮기는 경우에는 이전 위치와 새 위치 모두에서 정렬 보정이 필요 합니다.
아래 예시는 노드 이동 시 순서가 어떻게 조정되는지 보여주는 예시 입니다.
뒤로 이동할 때

앞으로 이동할 때


order 의 상태를 업데이트하는 로직은 서버에서 직접 구현해야 합니다.
정렬 보정과 노드 위치 저장을 위한 API는 아래 예제에서 확인할 수 있습니다.
Github 예제
https://github.com/hingjae/admin-menu-management
jsTree는 UI에서 트리 구조를 손쉽게 조작할 수 있게 도와줍니다.
서버 측 정렬 처리와 함께 연동하면, 관리자 메뉴 구성 등 다양한 실무에 바로 적용할 수 있습니다.