Vue.js 3 & NodeJS/NodeJS

Node.js 레스트 API 모듈화 (정렬, 필터, 페이지네이션) - Node.js REST API, Board-API Module, export, require

carrotweb 2022. 5. 22. 14:37
728x90
반응형

boardapi에서 사용하는 정렬, 필터, 페이지네이션을 모듈화하여 별도의 js 파일로 분리하고 require() 메서드로 모듈을 불러와 사용하겠습니다.


레스트 API 테스트를 위해 Datebase 없이 배열 기반으로 데이터를 관리하기 때문에 API를 처리하기 위해서 정렬과 필터가 필요합니다. Datebase로 연동하여 사용하시는 분들에게는 필요 없는 부분입니다.

 

Module(모듈) 구조에 대해서 간단하게 설명하겠습니다.

 

 

Module(모듈) 구조

 

module.export로 내보내고 require() 메서드로 모듈을 불러와 사용합니다.

module.export로 함수, 객체, 원시 값들을 묶어서 내보내거나 개별로 내보낼 수 있습니다.

// test.js 파일
// 원의 넓이 구하기 함수
const getCircle = function(radius) {
	// 반지름 * 반지름 * PI
	return radius * radius * PI ;
};
// PI 값
const PI = 3.14;
// 그래픽 객체
var graphic = {
	color: 'white',
	board: '1px solid black',
	draw: function() {
		return "<div>draw Circle</div>";
	}
};

module.exports = {
	// 함수
	getCircle,
	// 원시 값
	PI,
	// 객체
	graphic
};

또는 객체로 만들어서 내보낼 수 있습니다.

// test.js 파일
const test = {
	// PI 값
	PI: 3.14,
	// 원의 넓이 구하기 함수
	getCircle: function(radius) {
		// 반지름 * 반지름 * PI
		return radius * radius * test.PI ;
	},
	// 그래픽 객체
	graphic: {
		color: 'white',
		board: '1px solid black',
		draw: function() {
			return "<div>draw Circle</div>";
		}
	}
};

module.exports = test;

주의할 점은 내부 함수에서 this를 사용하면 안 됩니다. 왜냐하면 객체가 아닌 함수만 불러와서 사용할 경우 this를 인식하지 못합니다. 그래서 this 대신 객체 명을 사용해야 합니다.


모듈들은 require() 메서드로 불러와 사용합니다.
모듈을 객체로 받거나 모듈 중 사용할 함수 명, 객체 명, 원시 값 명을 명시하여 불러와 사용할 수 있습니다.

// 객체로 받아서 사용하는 방법
const test = require('.test');

console.log(test.getCircle(10));
--> 314
console.log(test.PI);
--> 3.14
console.log(test.graphic.board);
--> 1px solid black
console.log(test.graphic.draw());
--> <div>draw Circle</div>

// 사용할 함수나, 객체, 원시 값만 불러와서 사용하는 방법
const { getCircle, PI, graphic } = require('.test');

console.log(getCircle(10));
--> 314
console.log(PI);
--> 3.14
console.log(graphic.board);
--> 1px solid black
console.log(graphic.draw());
--> <div>draw Circle</div>

 

 

그럼 정렬에서 공통으로 사용되는 부분들을 모듈로 만들고 require() 메서드로 불러와 적용하겠습니다.

 


sort(정렬) 모듈 생성 및 적용

 

1. C:\workspaces\nodeserver\testrestapi 폴더에 common 폴더를 생성합니다. 그리고 sort.js 파일을 생성합니다. 모듈을 객체로 리턴하겠습니다.

// 정렬
const sort = {
};

module.exports = sort;

기존 정렬(sort) 코드에서 오름차순 정렬과 내림차순 정렬을 보면 문자열 정렬과 숫자 정렬, 날짜 정렬(날짜는 밀리초를 변화하여 사용하기 때문에 숫자 정렬)로 되어 있는 것을 알 수 있습니다. 즉, 정렬은 문자나, 숫자를 비교하여 오름차순이나 내림차순으로 정렬하는 것입니다.

 

그래서 동일하게 반복적으로 사용되는 함수들을 분리하여 정렬 모듈로 만듭니다.

// 정렬
const sort = {
	// 문자열 오름차순 정렬
	compareStringIgnoreCaseAsc: function(comp1, comp2) {
		var comp1UC = comp1.toUpperCase();
		var comp2UC = comp2.toUpperCase();
		if (comp1UC < comp2UC) {
			return -1;
		} else if (comp1UC > comp2UC) {
			return 1;
		}
		return 0;
	},
	// 문자열 내림차순 정렬
	compareStringIgnoreCaseDesc: function(comp1, comp2) {
		var comp1UC = comp1.toUpperCase();
		var comp2UC = comp2.toUpperCase();
		if (comp1UC < comp2UC) {
			return 1;
		} else if (comp1UC > comp2UC) {
			return -1;
		}
		return 0;
	},
	// 문자열 오름차순 정렬
	compareStringAsc: function(comp1, comp2) {
		if (comp1 < comp2) {
			return -1;
		} else if (comp1 > comp2) {
			return 1;
		}
		return 0;
	},
	// 문자열 내림차순 정렬
	compareStringDesc: function(comp1, comp2) {
		if (comp1 < comp2) {
			return 1;
		} else if (comp1 > comp2) {
			return -1;
		}
		return 0;
	},
	// 숫자 오름차순 정렬
	compareNumberAsc: function(comp1, comp2) {
		if (comp1 < comp2) {
			return -1;
		} else if (comp1 > comp2) {
			return 1;
		}
		return 0;
	},
	// 숫자 내림차순 정렬
	compareNumberDesc: function (comp1, comp2) {
		if (comp1 < comp2) {
			return 1;
		} else if (comp1 > comp2) {
			return -1;
		}
		return 0;
	},
	// 날짜 오름차순 정렬
	compareDateAsc: function(comp1, comp2) {
		var comp1DT = new Date(comp1).getTime();
		var comp2DT = new Date(comp2).getTime();
		if (comp1DT < comp2DT) {
			return -1;
		} else if (comp1DT > comp2DT) {
			return 1;
		}
		return 0;
	},
	// 날짜 내림차순 정렬
	compareDateDesc: function(comp1, comp2) {
		var comp1DT = new Date(comp1).getTime();
		var comp2DT = new Date(comp2).getTime();
		if (comp1DT < comp2DT) {
			return 1;
		} else if (comp1DT > comp2DT) {
			return -1;
		}
		return 0;
	}
};

module.exports = sort;

 

그리고 정렬(sort)에서 사용하는 해시맵(hashmapSortby)과 정렬 메서드(sortBy)를 복사하여 추가하고 정렬 메서드(sortBy)를 수정합니다.

// 해시맵(HsahMap)
hashmapSortby: [],
// 정렬
sortBy: function(arrayList, sortby) {
	// 정렬 배열
	var arSortby = null;
	
	if (sortby == undefined || typeof sortby == "undefined" || sortby == null) {
		arSortby = [];
	} else {
		arSortby = sortby.split(",");
	}
	
	if (arSortby.length > 0) {
		arrayList.sort(function(comp1, comp2) {
			var result = 0;
			for (var index = 0; index < arSortby.length; index++) {
				if (sort.hashmapSortby[arSortby[index]] != null) {
					result = sort.hashmapSortby[arSortby[index]](comp1, comp2);
					if (result == 0) {
						continue;
					} else {
						break;
					}
				}
			}
			return result;
		});
	}
}

정렬 메서드(sortBy)의 해시맵(hashmapSortby)에 객체 명을 추가해야 합니다.

2. C:\workspaces\nodeserver\testrestapi\boardapi.js 파일을 오픈하여 정렬 모듈을 require() 메서드로 불러옵니다.

const sort = require('./commons/sort');

 

기존 코드를 정렬 모듈을 적용하여 수정합니다.

// 작성자 오름차순 정렬
sort.hashmapSortby["writer.asc"] = function(comp1, comp2) {
	return sort.compareStringIgnoreCaseAsc(comp1.writer, comp2.writer);
};

// 작성자 내림차순 정렬
sort.hashmapSortby["writer.desc"] = function(comp1, comp2) {
	return sort.compareStringIgnoreCaseDesc(comp1.writer, comp2.writer);
};

// 작성날짜 오름차순 정렬
sort.hashmapSortby["writedate.asc"] = function(comp1, comp2) {
	return sort.compareDateAsc(comp1.writedate, comp2.writedate);
};

// 작성날짜 내림차순 정렬
sort.hashmapSortby["writedate.desc"] = function(comp1, comp2) {
	return sort.compareDateDesc(comp1.writedate, comp2.writedate);
};

// 조회 수 오름차순 정렬
sort.hashmapSortby["viewcount.asc"] = function(comp1, comp2) {
	return sort.compareNumberAsc(comp1.viewcount, comp2.viewcount);
};

// 조회 수 내림차순 정렬
sort.hashmapSortby["viewcount.desc"] = function(comp1, comp2) {
	return sort.compareNumberDesc(comp1.viewcount, comp2.viewcount);
};

// 추천(좋아요) 수 오름차순 정렬
sort.hashmapSortby["likecount.asc"] = function(comp1, comp2) {
	return sort.compareNumberAsc(comp1.likecount, comp2.likecount);
};

// 추천(좋아요) 수 내림차순 정렬
sort.hashmapSortby["likecount.desc"] = function(comp1, comp2) {
	return sort.compareNumberDesc(comp1.likecount, comp2.likecount);
};

그리고 코드에서 hashmapSortby와 sortBy() 메서드는 삭제합니다.

참고로 배열 기반으로 객체의 값을 비교해서 정렬 처리를 하기 때문에 해시맵(HsahMap)에 정렬 메서드를 추가하는 부분은 모듈화 하기에는 적합하지 않습니다. 반복적으로 사용되는 공통부분만 모듈화 하였습니다.

3. router.get('/') 라우터에서 정렬 코드를 수정합니다.

// 정렬
var sortby = req.query.sortby;
sortBy(filteredBoardList, sortby);

수정된 정렬 코드입니다.

// 정렬
var sortby = req.query.sortby;
sort.sortBy(filteredBoardList, sortby);

 

반응형

 

 

filter(필터) 모듈 생성 및 적용

 

1. C:\workspaces\nodeserver\testrestapi 폴더에 common 폴더를 생성합니다. 그리고 filter.js 파일을 생성합니다. 모듈을 객체로 리턴하겠습니다.

// 필터
const filter = {
};

module.exports = filter;

 

기존 필터(filter) 코드에서 filter() 메서드를  compareFilter() 메서드로 변경하여 필터 모듈로 만듭니다.

// 필터
const filter = {
	// 비교 필터
	compareFilter: function(element, columnName, compareCondition, compareValue) {
		var result = false;
		if (typeof element[columnName] == "string") {
			// 문자열 비교
			if (compareCondition == "eq") {
				result = (element[columnName] == compareValue);
			} else if (compareCondition == "ei") {
				result = (element[columnName].toUpperCase() == compareValue.toUpperCase());
			} else if (compareCondition == "ne") {
				result = (element[columnName] != compareValue);
			}
		} else if (typeof element[columnName] == "number") {
			// 숫자 비교
			if (compareCondition == "eq") {
				result = (element[columnName] == compareValue);
			} else if (compareCondition == "lt") {
				result = (element[columnName] < compareValue);
			} else if (compareCondition == "le") {
				result = (element[columnName] <= compareValue);
			} else if (compareCondition == "gt") {
				result = (element[columnName] > compareValue);
			} else if (compareCondition == "ge") {
				result = (element[columnName] >= compareValue);
			} else if (compareCondition == "ne") {
				result = (element[columnName] != compareValue);
			}
		}
		return result;
	},
	// 필터링
	filtering: function(condition, arrayList) {
		// 필터된 배열
		var filtered = [];
		// 반복 카운트
		var loopCount = 0;
		
		// 필터 제외
		const excludeFilter = ["countperpage", "pageno", "pagesize", "sortby"];
		// 필터 (Key=value)
		for (conditionName in condition) {
			if (!excludeFilter.includes(conditionName.toLowerCase())) {
				// 대상 칼럼 명
				var columnName = conditionName;
				// 비교문
				var compareCondition = "eq";
				// 비교값
				var compareValue = condition[columnName];
				if (typeof compareValue == "string") {
					var pos = compareValue.indexOf(":");
					if (pos > 0) {
						compareCondition = compareValue.substr(0, pos);
						compareValue = compareValue.substring(pos+1);
					}
					if (loopCount == 0) {
						filtered = arrayList.filter(function(element) {
							return filter.compareFilter(element, columnName, compareCondition, compareValue);
						});
					} else {
						filtered = filtered.filter(function(element) {
							return filter.compareFilter(element, columnName, compareCondition, compareValue);
						});
					}
					loopCount++;
				} else if (Array.isArray(compareValue)) {
					for (var index = 0; index < compareValue.length; index++) {
						// 비교문
						var arCompareCondition = "eq";
						// 비교값
						var arCompareValue = compareValue[index];
						var pos = arCompareValue.indexOf(":");
						if (pos > 0) {
							arCompareCondition = arCompareValue.substr(0, pos);
							arCompareValue = arCompareValue.substring(pos+1);
						}
						if (loopCount == 0) {
							filtered = arrayList.filter(function(element) {
								return filter.compareFilter(element, columnName, arCompareCondition, arCompareValue);
							});
						} else {
							filtered = filtered.filter(function(element) {
								return filter.compareFilter(element, columnName, arCompareCondition, arCompareValue);
							});
						}
						loopCount++;
					}
				}
				if (filtered.length == 0) {
					break;
				}
			}
		}
		
		if (loopCount == 0) {
			filtered = arrayList;
		}
		
		return filtered;
	}
};

module.exports = filter;

 

2. C:\workspaces\nodeserver\testrestapi\boardapi.js 파일을 오픈하여 필터 모듈을 require() 메서드로 불러옵니다.

const filter = require('./commons/filter');

그리고 코드에서 filter() 메서드와 filtering() 메서드를 삭제합니다.

router.get('/') 라우터에서 필터 코드를 수정합니다.

// 필터된 게시판
var filteredBoardList = filtering(req, boardList);

수정된 필터 코드입니다.

// 필터된 게시판
var filteredBoardList = filter.filtering(req.query, boardList);

 

 

 

pagination(페이지네이션) 모듈 생성 및 적용

 

1. C:\workspaces\nodeserver\testrestapi 폴더에 common 폴더를 생성합니다. 그리고 pagination.js 파일을 생성합니다. 모듈을 함수로 리턴하겠습니다.

// 페이지네이션
const pagination = function() {
};

module.exports = pagination;

 

기존 페이지네이션 코드에서 router.get('/') 라우터에서 Query String(쿼리 스트링)으로 받은 페이지 크기, 페이지 사이즈, 페이지 번호 등의 처리까지 페이지네이션 모듈에 포함해서 만듭니다.

// 페이지네이션
const pagination = function(totalCount, countPerPage, pageSize, pageNo) {
	// 페이지 크기
	if (countPerPage == undefined || typeof countPerPage == "undefined" || countPerPage == null) {
		countPerPage = 10;
	} else {
		countPerPage = parseInt(countPerPage);
		if (isNaN(countPerPage) || countPerPage <= 0 || countPerPage > 1000) {
			countPerPage = 10;
		}
	}
	// 페이지 사이즈
	if (pageSize == undefined || typeof pageSize == "undefined" || pageSize == null) {
		pageSize = 10;
	} else {
		pageSize = parseInt(pageSize);
		if (isNaN(pageSize) || pageSize <= 0 || pageSize > 100) {
			pageSize = 10;
		}
	}
	// 페이지 번호
	if (pageNo == undefined || typeof pageNo == "undefined" || pageNo == null) {
		pageNo = 1;
	} else {
		pageNo = parseInt(pageNo);
		if (isNaN(pageNo) || pageNo <= 0) {
			pageNo = 1;
		}
	}
	
	// 페이지네이션 정보
	var paginationInfo = {};
	
	// 마지막 페이지 번호(전체 페이지 크기)
	var lastPageNo = Math.floor(totalCount / countPerPage) + (totalCount % countPerPage== 0 ? 0 : 1);
	// 시작 페이지 번호
	var startPageNo = 1;
	// 페이지 사이즈로 페이지 번호를 나눈 몫만큼 페이지 시작 번호 변경
	var start = Math.floor(pageNo / pageSize);
	if (start >= 1) {
		// 그렇지만 나머지가 없으면 현재 페이지 번호가 마지막 페이지 번호와 같아 감소
		if (pageNo % pageSize == 0){
			start--;
		}
		startPageNo = (start * pageSize) + 1;
	}
	// 종료 페이지 번호
	var endPageNo = (startPageNo - 1) + pageSize;
	// 그렇지만 종료 페이지 번호가 마지막 페이지 번호보다 크면 마지막 페이지 번호로 변경
	if (endPageNo > lastPageNo) {
		endPageNo = lastPageNo;
	}
	// 이전 페이지 번호 활성화 여부
	var enablePrevPageNo = true;
	if ((pageNo - 1) == 0) {
		enablePrevPageNo = false;
	}
	// 다음 페이지 번호 활성화 여부
	var enableNextPageNo = true;
	if ((pageNo + 1) > lastPageNo) {
		enableNextPageNo = false;
	}
	// 이전 페이지 사이즈 번호
	var prevPageSizeNo = startPageNo - 1;
	// 이전 페이지 사이즈 번호 활성화 여부
	var enablePrevPageSizeNO = true;
	if (prevPageSizeNo == 0) {
		enablePrevPageSizeNO = false;
	}
	// 다음 페이지 사이즈 번호
	var nextPageSizeNo = endPageNo + 1;
	// 다음 페이지 사이즈 번호 활성화 여부
	var enableNextPageSizeNO = true;
	if (nextPageSizeNo > lastPageNo) {
		enableNextPageSizeNO = false;
	}
	
	// 페이지네이션 정보
	paginationInfo.totalCount = totalCount;
	paginationInfo.countPerPage = countPerPage;
	paginationInfo.pageSize = pageSize;
	paginationInfo.startPageNo = startPageNo;
	paginationInfo.endPageNo = endPageNo;
	paginationInfo.lastPageNo = lastPageNo;
	paginationInfo.pageNo = pageNo;
	paginationInfo.enablePrevPageNo = enablePrevPageNo;
	paginationInfo.enableNextPageNo = enableNextPageNo;
	paginationInfo.prevPageSizeNo = prevPageSizeNo;
	paginationInfo.enablePrevPageSizeNO = enablePrevPageSizeNO;
	paginationInfo.nextPageSizeNo = nextPageSizeNo;
	paginationInfo.enableNextPageSizeNO = enableNextPageSizeNO;
	
	return paginationInfo;
}

module.exports = pagination;

페이지 크기가 숫자가 아니거나 0보다 작거나 1000보다 크면 10으로 보정한다. 페이지 사이즈가 숫자가 아니거나 0보다 작거나 100보다 크면 10으로 보정합니다. 페이지 번호가 숫자가 아니거나 0보다 작으면 1로 보정합니다.

2. C:\workspaces\nodeserver\testrestapi\boardapi.js 파일을 오픈하여 페이지네이션 모듈을 require() 메서드로 불러옵니다.

const pagination = require('./commons/pagination');

그리고 코드에서 pagination() 메서드를 삭제합니다.

router.get('/') 라우터에서 페이지네이션 코드를 수정합니다.

// 페이지 크기
var countPerPage = req.query.countperpage;
// 페이지 번호
var pageNo = req.query.pageno;
// 페이지 사이즈
var pageSize = req.query.pagesize;

if (countPerPage == undefined || typeof countPerPage == "undefined" || countPerPage == null) {
	countPerPage = 10;
} else {
	countPerPage = parseInt(countPerPage);
}
if (pageSize == undefined || typeof pageSize == "undefined" || pageSize == null) {
	pageSize = 10;
} else {
	pageSize = parseInt(pageSize);
}
if (pageNo == undefined || typeof pageNo == "undefined" || pageNo == null) {
	pageNo = 1;
} else {
	pageNo = parseInt(pageNo);
}

// 전체 크기
var totalCount = filteredBoardList.length;

// 페이지네이션 정보
var paginationInfo = pagination(totalCount, countPerPage, pageSize, pageNo);

// 시작 번호
var startItemNo = ((pageNo - 1) * countPerPage);
// 종료 번호
var endItemNo = (pageNo * countPerPage) - 1;
// 종료 번호가 전체 크기보다 크면 전체 크기로 변경
if (endItemNo > (totalCount - 1)) {
	endItemNo = totalCount - 1;
}

수정된 페이지네이션 코드입니다.

// 전체 크기
var totalCount = filteredBoardList.length;

// 페이지네이션 정보
var paginationInfo = pagination(totalCount, req.query.countperpage, req.query.pagesize, req.query.pageno);

// 시작 번호
var startItemNo = ((paginationInfo.pageNo - 1) * paginationInfo.countPerPage);
// 종료 번호
var endItemNo = (paginationInfo.pageNo * paginationInfo.countPerPage) - 1;
// 종료 번호가 전체 크기보다 크면 전체 크기로 변경
if (endItemNo > (totalCount - 1)) {
	endItemNo = totalCount - 1;
}
728x90
반응형