Vue.js 3 & NodeJS/NodeJS

Node.js 레스트 API 조회 정렬 - Node.js REST API Sorting(Sort By, Order By), Multi-Column Sort

carrotweb 2022. 4. 10. 21:00
728x90
반응형

Sorting(정렬)

Database(데이터베이스)에서 지정된 칼럼을 기준으로 오름차순이나 내림차순으로 정렬(쿼리 구문에서 Order By 절) 하여 데이터를 반환합니다.

레스트 API에서 정렬(Sorting)을 처리하기 위해서 Query String(쿼리 스트링)으로 URL 주소 뒤에 붙여서 사용합니다.

REST API Column Sort(레스트 API 칼럼 정렬)

기준 칼럼(sortby)

기준 칼럼(sortby)은 정렬할 칼럼을 지정합니다.

정렬 방법(orderby)

정렬 방법(orderby)은 기준 칼럼(sortby)을 오름차순이나 내림차순으로 정렬 방법을 설정합니다.

http://localhost:9000/boards?sortby=writer&orderby=desc -> 작성자를 기준으로 내림차순으로 정렬
http://localhost:9000/boards?sortby=writer&orderby=asc -> 작성자를 기준으로 오름차순으로 정렬

 

기준 칼럼(sortby)과 정렬 방법(orderby)을 사용하면 정렬할 수 있지만 여러 개의 칼럼으로 정렬할 수가 없습니다.

콤마(,)를 구분자로 처리할 수 있지만 쌍이 맞지 않으면 오류가 발생할 수 있습니다.

http://localhost:9000/boards?sortby=writedate,writer&orderby=desc,asc (O)
http://localhost:9000/boards?sortby=writedate,writer&orderby=desc (X)

 

그래서 기준 칼럼(sortby)과 정렬 방법(orderby)을 하나로 합쳐서 구문으로 만들어야 합니다.

 

REST API Multi-Column Sort(레스트 API 멀티 칼럼 정렬)

기준 칼럼(sortby)과 정렬 방법(orderby)을 도트(.)로 구분합니다.

http://localhost:9000/boards?sortby=writer.desc -> 작성자 명을 기준으로 내림차순으로 정렬
http://localhost:9000/boards?sortby=writer.asc -> 작성자 명을 기준으로 오름차순으로 정렬

 

그리고 여러 개의 칼럼들은 콤마(,)로 구분하면 여러 개의 칼럼들을 정렬시킬 수 있습니다.

http://localhost:9000/boards?sortby=writedate.desc,writer.desc
-> 작성날짜를 기준으로 내람차순으로 정렬, 작성자 명을 기준으로 내람차순으로 정렬

 

SQL 쿼리문으로 처리하면 다음과 같습니다.

SELECT *
  FROM boards
 ORDER BY write desc, writer asc

 

그럼 게시판 REST API에 멀티 칼럼 정렬을 추가하겠습니다.

 

게시판 REST API에 Multi-Column Sort(멀티 칼럼 정렬) 추가

1. C:\workspaces\nodeserver\testrestapi\boardapi.js 파일을 오픈하여 router.get('/') 라우터에 정렬(sortby)을 추가합니다.

// 정렬
var sortby = req.query.sortby;
// 정렬 배열
var arSortby = null;

if (sortby == undefined || typeof sortby == "undefined" || sortby == null) {
	arSortby = [];
} else {
	arSortby = sortby.split(",");
}

멀티 정렬을 위해 여러 개의 칼럼들을 콤마(,)로 구분하여 정렬 배열로 만듭니다.

정렬 배열을 사용하여 SQL 쿼리문의 Order By 절을 생성하면 됩니다.

현재 DB를 사용하지 않고 있어서 배열로 데이터를 관리하고 있습니다. 그래서 쿼리문이 아닌 Array 객체의 sort() 메서드를 사용하여 처리하겠습니다.

Array 객체의 sort() 메서드에 대한 자세한 내용은 (https://carrotweb.tistory.com/185)를 참조하시기 바랍니다.

 

Array 객체의 sort() 메서드에서 사용할 compareFunction 함수들을 생성하고 해시맵(HashMap)에 추가하겠습니다. 해시맵(HashMap)은 Array 객체를 사용하여 만들 수 있습니다.

2. router.get('/') 라우터 위에 정렬 해시맵(HashMap)을 생성하고 compareFunction 함수들을 추가합니다.

// 정렬 해시맵(HashMap)
var hashmapSortby = []; 

// 작성자 오름차순 정렬
hashmapSortby["writer.asc"] = function(comp1, comp2) {
	var comp1UC = comp1.writer.toUpperCase();
	var comp2UC = comp2.writer.toUpperCase();
	if (comp1UC < comp2UC) {
		return -1;
	} else if (comp1UC > comp2UC) {
		return 1;
	}
	return 0;
};

// 작성자 내림차순 정렬
hashmapSortby["writer.desc"] = function(comp1, comp2) {
	var comp1UC = comp1.writer.toUpperCase();
	var comp2UC = comp2.writer.toUpperCase();
	if (comp1UC < comp2UC) {
		return 1;
	} else if (comp1UC > comp2UC) {
		return -1;
	}
	return 0;
};

// 작성날짜 오름차순 정렬
hashmapSortby["writedate.asc"] = function(comp1, comp2) {
	var comp1UC = new Date(comp1.writedate).getTime();
	var comp2UC = new Date(comp2.writedate).getTime();
	if (comp1UC < comp2UC) {
		return -1;
	} else if (comp1UC > comp2UC) {
		return 1;
	}
	return 0;
};
	
// 작성날짜 내림차순 정렬
hashmapSortby["writedate.desc"] = function(comp1, comp2) {
	var comp1UC = new Date(comp1.writedate).getTime();
	var comp2UC = new Date(comp2.writedate).getTime();
	if (comp1UC < comp2UC) {
		return 1;
	} else if (comp1UC > comp2UC) {
		return -1;
	}
	return 0;
};

 

3. 정렬 배열이 있으면 정렬 배열 순서로 해시맵(HashMap)에 있는 compareFunction 함수를 가져와서 실행합니다.

compareFunction 함수의 리턴 값이 0 아니면 리턴하고 0이면 다음 compareFunction 함수를 처리합니다.

여러 개의 칼럼 정렬이 전달될 경우 첫 번째 정렬 값이 동일하면 그다음 번째 정렬 기준이 적용되고 또 동일하면 그다음 번째 정렬 기준이 적용됩니다. 정렬 배열의 순서대로 비교됩니다.

if (arSortby.length > 0) {
	boardList.sort(function(comp1, comp2) {
		var result = 0;
		for (var index = 0; index < arSortby.length; index++) {
			if (hashmapSortby[arSortby[index]] != null) {
				result = hashmapSortby[arSortby[index]](comp1, comp2);
				if (result == 0) {
					continue;
				} else {
					break;
				}
			}
		}
		return result;
	});
}

 

compareFunction(comp1, comp2)에서

리턴 값이 -1(0보다 작은 수)이면 comp1을 comp2보다 낮은 요소로 정렬됩니다.

리턴 값이 0이면 동일하게 정렬됩니다.

리턴 값이 1(0보다 큰 수)이면 comp1을 comp2보다 큰 요소로 정렬됩니다.

전체 소스입니다.

router.get('/', function(req, res, next) {
	console.log("REST API Get Method - Read All");
	
	// 정렬
	var sortby = req.query.sortby;
	// 정렬 배열
	var arSortby = null;
	
	if (sortby == undefined || typeof sortby == "undefined" || sortby == null) {
		arSortby = [];
	} else {
		arSortby = sortby.split(",");
	}
	
	if (arSortby.length > 0) {
		boardList.sort(function(comp1, comp2) {
			var result = 0;
			for (var index = 0; index < arSortby.length; index++) {
				if (hashmapSortby[arSortby[index]] != null) {
					result = hashmapSortby[arSortby[index]](comp1, comp2);
					if (result == 0) {
						continue;
					} else {
						break;
					}
				}
			}
			return result;
		});
	}

	// 페이지 크기
	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 = 0;
	} else {
		pageNo = parseInt(pageNo);
	}
	
	if (pageNo > 0) {
		// 전체 크기
		var totalCount = boardList.length;
		// 마지막 페이지 번호(전체 페이지 크기)
		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;
		}
		// 시작 번호
		var startItemNo = ((pageNo - 1) * countPerPage);
		// 종료 번호
		var endItemNo = (pageNo * countPerPage) - 1;
		// 종료 번호가 전체 크기보다 크면 전체 크기로 변경
		if (endItemNo > (totalCount - 1)) {
			endItemNo = totalCount - 1;
		}
		var boardPageList = [];
		if (startItemNo < totalCount) {
			for (var index = startItemNo; index <= endItemNo; index++) {
				boardPageList.push(boardList[index]);
			}
		}
		// 페이지네이션 정보
		var paginationInfo = {};
		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;
		res.json({success:true, data:boardPageList, pagination:paginationInfo});
	} else {
		res.json({success:true, data:boardList});
	}
});

 

 

4. 콘솔을 실행하고 Node.js 레스트 API 서버 프로젝트가 있는 C:\workspaces\nodeserver\testrestapi 폴더로 이동합니다. 그리고 npm run 명령어를 실행합니다.

npm run start

 

5. Postman(포스트맨)를 실행하여 테스트합니다.

Postman(포스트맨)를 이용하여 REST API Sort 테스트

작성 날짜를 내림차순으로 정렬해 보겠습니다.

Get Method - sort 테스트

http://localhost:9000/boards?countperpage=3&pageno=1&sortby=writedate.desc

작성 날짜를 기준으로 내림차순으로 정렬되는 것을 확인할 수 있습니다.

{
    "success": true,
    "data": [
        {
            "no": 13,
            "subject": "테스트 제목13",
            "content": "테스트 내용13",
            "writer": "testid1",
            "writedate": "2022-03-27 00:30:00"
        },
        {
            "no": 12,
            "subject": "테스트 제목12",
            "content": "테스트 내용12",
            "writer": "testid3",
            "writedate": "2022-03-26 23:20:00"
        },
        {
            "no": 11,
            "subject": "테스트 제목11",
            "content": "테스트 내용11",
            "writer": "testid2",
            "writedate": "2022-03-26 22:10:00"
        }
    ],
    "pagination": {
        "totalCount": 13,
        "countPerPage": 3,
        "pageSize": 10,
        "startPageNo": 1,
        "endPageNo": 5,
        "lastPageNo": 5,
        "pageNo": 1,
        "enablePrevPageNo": false,
        "enableNextPageNo": true,
        "prevPageSizeNo": 0,
        "enablePrevPageSizeNO": false,
        "nextPageSizeNo": 6,
        "enableNextPageSizeNO": false
    }
}

 

반대로 작성 날짜를 오름차순으로 정렬해 보겠습니다.

http://localhost:9000/boards?countperpage=3&pageno=1&sortby=writedate.asc

작성 날짜를 기준으로 오름차순으로 정렬되는 것을 확인할 수 있습니다.

{
    "success": true,
    "data": [
        {
            "no": 1,
            "subject": "테스트 제목1",
            "content": "테스트 내용1",
            "writer": "testid1",
            "writedate": "2021-08-09 13:00:00"
        },
        {
            "no": 2,
            "subject": "테스트 제목2",
            "content": "테스트 내용2",
            "writer": "testid2",
            "writedate": "2021-08-09 13:10:00"
        },
        {
            "no": 3,
            "subject": "테스트 제목3",
            "content": "테스트 내용3",
            "writer": "testid3",
            "writedate": "2021-08-09 13:20:00"
        }
    ],
    "pagination": {
        "totalCount": 13,
        "countPerPage": 3,
        "pageSize": 10,
        "startPageNo": 1,
        "endPageNo": 5,
        "lastPageNo": 5,
        "pageNo": 1,
        "enablePrevPageNo": false,
        "enableNextPageNo": true,
        "prevPageSizeNo": 0,
        "enablePrevPageSizeNO": false,
        "nextPageSizeNo": 6,
        "enableNextPageSizeNO": false
    }
}

 

 

 

Postman(포스트맨)를 이용하여 REST API Multi-Column Sort 테스트

작성 날짜를 내림차순으로 정렬하고 작성자 명을 내림차순으로 정렬해 보겠습니다.

게시판 배열(boardList)에 동일한 작성 날짜가 없습니다. 그래서 11번, 12번 게시물의 작성 날짜를 13번 게시물과 동일하게 수정합니다.

let boardList = [
{no:1, subject:"테스트 제목1", content:"테스트 내용1", writer:"testid1", writedate:"2021-08-09 13:00:00"},
{no:2, subject:"테스트 제목2", content:"테스트 내용2", writer:"testid2", writedate:"2021-08-09 13:10:00"},
{no:3, subject:"테스트 제목3", content:"테스트 내용3", writer:"testid3", writedate:"2021-08-09 13:20:00"},
{no:4, subject:"테스트 제목4", content:"테스트 내용4", writer:"testid1", writedate:"2022-03-26 14:00:00"},
{no:5, subject:"테스트 제목5", content:"테스트 내용5", writer:"testid2", writedate:"2022-03-26 15:10:00"},
{no:6, subject:"테스트 제목6", content:"테스트 내용6", writer:"testid3", writedate:"2022-03-26 16:20:00"},
{no:7, subject:"테스트 제목7", content:"테스트 내용7", writer:"testid1", writedate:"2022-03-26 17:30:00"},
{no:8, subject:"테스트 제목8", content:"테스트 내용8", writer:"testid2", writedate:"2022-03-26 18:40:00"},
{no:9, subject:"테스트 제목9", content:"테스트 내용9", writer:"testid3", writedate:"2022-03-26 19:50:00"},
{no:10, subject:"테스트 제목10", content:"테스트 내용10", writer:"testid1", writedate:"2022-03-26 21:00:00"},
{no:11, subject:"테스트 제목11", content:"테스트 내용11", writer:"testid2", writedate:"2022-03-27 00:30:00"},
{no:12, subject:"테스트 제목12", content:"테스트 내용12", writer:"testid3", writedate:"2022-03-27 00:30:00"},
{no:13, subject:"테스트 제목13", content:"테스트 내용13", writer:"testid1", writedate:"2022-03-27 00:30:00"}
];

 

Get Method - multi-column sort 테스트

http://localhost:9000/boards?countperpage=3&pageno=1&sortby=writedate.desc,writer.desc

작성 날짜를 기준으로 내림차순으로 정렬되고 동일한 작성 날짜에 대해서는 작성자 명을 내림차순으로 정렬되는 것을 확인할 수 있습니다.

{
    "success": true,
    "data": [
        {
            "no": 12,
            "subject": "테스트 제목12",
            "content": "테스트 내용12",
            "writer": "testid3",
            "writedate": "2022-03-27 00:30:00"
        },
        {
            "no": 11,
            "subject": "테스트 제목11",
            "content": "테스트 내용11",
            "writer": "testid2",
            "writedate": "2022-03-27 00:30:00"
        },
        {
            "no": 13,
            "subject": "테스트 제목13",
            "content": "테스트 내용13",
            "writer": "testid1",
            "writedate": "2022-03-27 00:30:00"
        }
    ],
    "pagination": {
        "totalCount": 13,
        "countPerPage": 3,
        "pageSize": 10,
        "startPageNo": 1,
        "endPageNo": 5,
        "lastPageNo": 5,
        "pageNo": 1,
        "enablePrevPageNo": false,
        "enableNextPageNo": true,
        "prevPageSizeNo": 0,
        "enablePrevPageSizeNO": false,
        "nextPageSizeNo": 6,
        "enableNextPageSizeNO": false
    }
}

 

 

보안 이슈 - SQL Injection(인젝션)

REST API에서 정렬 처리 시 데이터베이스의 필드 명과 동일하게 사용하시지 않는 것이 좋습니다. 그리고 SQL 쿼리문에 직접 전달된 파라미터를 사용하시면 안 됩니다. 반드시 조건문 처리하고 SQL 쿼리문 안에서 조건에 따라 Order By 절이 동작되도록 처리하시기 바랍니다.

향후 Database 연동 시 설명하도록 하겠습니다.

728x90
반응형