ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Express.js] Query String, Semantic URL
    Back-End/Express.js 2021. 2. 7. 04:56
    반응형

    😲 서론

    이전에, 동적 라우팅에 대해서 공부한 적이 있다. 그 때, fetch() 를 하는 URL에 추가적으로 내용을 첨부했던 것 같았다.

    또, Pagination 을 2차원 배열로 받으려고 했는데, 이때도 URL에 파라미터를 추가함으로서 필요한 길이만 가져올 수 있었다. 

     

    이번 내 프로젝트에서도, Pagination부터 Searching, Filtering 구현을 위해 URL에 갖가지 조건을 추가해야 한다.

    이를, 좀 더 개발자스러운 용어들로 정립하고, Express에서 어떻게 이 구문들을 인식할 수 있는지 알아보았다.


    📗 Query String

    0. URL의 구성요소

    쿼리스트링 공부에 앞서, 우리가 작성할 URL의 기본적인 구성에 대해 알 필요가 있다. (출처: victorydntmd.tistory.com/287)

    <스킴>://<호스트>:<포트>/<경로>?<질의>#<프레그먼트>
    • 스킴(scheme) : 사용할 프로토콜(리소스에 어떻게 요청, 접근하는지)을 명시한다. 주로 HTTP, HTTPS(+보안) 이 통용
    • 도메인(domain) : 서비스하는 컴퓨터(호스트)의 주소이다. 여러 개의 프로그램이 각 소켓(포트)으로 통신하며, 기본값은 80이다.
    • 경로(path) : 호스트에서 제공하는 자원의 경로를 의미한다.
    • 질의(query string) : 리소스를 GET 요청시, 필요한 데이터만 넘겨받기 위한 조건문 첨부에 사용된다.
    • 프래그먼트(fragment) : 해당 페이지에서 이동할 스크롤 속성을 설정한다. (ex) http://abangpa1ace.tistory.com/24#bottom)

     

    1. Query String 이란?

    사용자가 path로 접근한 뒤에, 해당 경로에서 제공하는 리소스의 조건을 부여하기 위해 사용되는 구문이다.

     

    2. Query String 적용(Front-End)

    - 문법

    http://localhost:5000/rests?type=hotel&location=gangnam

    ? 연산자는 해당 path에서의 쿼리 스트링이 시작됨을 알려준다. 그리고, [조건]=[값] 으로 각 조건들을 넘기면 된다.

    위 쿼리 스트링은, 내 서버의 rests(숙소) 중, type 이 hotel, location 이 강남인 정보만 불러오기 위한 쿼리 스트링이다.

    위처럼, 조건간의 연결은 & 연산자를 사용하면 된다.

     

    - 적용예시

    const fetchRests = async () => {
      const response = await fetch(
        `${RestsAPI}?filter[type]=${type}&page=${page}&limit=${LIMIT}`,
      );
      const result = await response.json();
      setRestList(result.restsList);
    }

    위 fetchRests() 함수는, 내가 서버에서 숙소 정보를 받아오기 위해 Context API에서 처리하는 함수이다.

    다른 부분은 무시하고, fetch() 로직 내의 URL 링크를 보도록 하자.

     

    RestsAPI는 서버링크다.(http://localhost:5000/rests) 여기서는, 기본적으로 내가 DB파일에 넣은 20개의 숙소 리소스를 제공한다.

     

    여기서, 위에 추가한 조건들(filter[type], page, limit) 쿼리스트링이 반영되면, 해당 값들을 서버링크에서 인식할 수 있다.

    서버에서는, 내가 Request에 보낸 위 값들을 활용해서, 데이터를 가공해서 필요한(혹은 적합한) 데이터만 반환시키는 로직을 짤 수 있다.

     

    - 응용 : 조건부 쿼리 스트링 첨가

    let RestsAPI_Query = `${RestsAPI}?page=${page}&limit=${LIMIT}`;
    if (location) {
      RestsAPI_Query += `&search[location]=${location}`;
    }
    if (type.length !== 0) {
      RestsAPI_Query += `&filter[type]=${type}`
    }

    내 fetch() 의 쿼리 스트링이 길어짐에 따라, 가독성을 높이고 서버의 불필요한 필터링을 줄이고자 조건부 쿼리 스트링으로 바꾼 것이다.

    기본적인 서버주소(RestsAPI)와 pagination만 가지고 있고, 각 검색/필터 값들이 존재하면 쿼리 스트링을 이어 붙이는 것이다.

     

     

    3. Query String 적용(Back-End)

    - req.query

    express에서 쿼리 스트링을 인식하기 위한 Request 메서드이다. 쿼리스트링을 객체 형태로 변환시켜준다.

    // from(Front)
    
    fetch('http://localhost:5000/rests?filter[type]=hotel&page=1&limit=15'
    
    
    // to(Back)
    
    console.log(req.query)
    {
      filter: { type: "hotel" },
      page: 1,
      limit: 15,
    }

    그러면, 쿼리 스트링의 대괄호에 대한 의문까지 풀렸으리라! 해당 조건의 키벨류를 다시 객체화 할 수 있는 유용한 문법이다.

     

    - 적용예시

    router.get('/', function(req, res, next) {
      let restsList = restsData;
      const { page, limit } = req.query;
      const Page = Number(page);
      const Limit = Number(limit)
      const startIdx = (Page - 1) * Limit;
      
      const pagedRestsList = restsList.slice(startIdx, startIdx + Limit);
      res.json({
        restsList: pagedRestsList,
      });
    });

    위 예시는, 내 서버에서 usersRouter(/users) 경로에서 'GET' Request 요청과 함께 쿼리스트링을 받았을 때,

    해당 값에 따라 숙소 리스트를 잘라서 보내주는 로직이다.

    req.query 객체의 page, limit 값을 숫자로 바꾼 다음, 해당 값으로 startIdx와 길이만큼 잘라낸 리스트를 JSON Response로 보낸다.

     

    - 참고

    1. 위처럼, req.query 의 키벨류는 String 타입임을 알 수 있다. 숫자의 경우는 Number()를 통해 number 타입으로 변환이 필요.
    2. 배열의 경우, ?arr=['a', 'b', 'c'] 쿼리는, arr: 'a,b,c' 로 들어올 것이다. split(',') 함수를 사용하여 arr를 다시 배열로 만들었다.
    3. 객체의 경우, [object Object] 라고 들어온다. 이는, 차라리 프론트 사이드에서 객체 for - in 순회로 `&${key}=${Object[key]}` 쿼리스트링을 추가하는 것이 낫다고 생각된다.

     

    4. Pagination

    위에서 볼 수 있듯이, 쿼리 스트링이 가장 유용하게 쓰이는 부분 중 하나가 바로 Pagination 이다.

    1차 프로젝트 초반엔, 모든 데이터를 state에 저장하고(서버에서 미리 2차원으로 가공해서 보내준 뒤), Index 스테이트로 순회하려고 했다.

     

    하지만, 위처럼 fetch() URL에 쿼리 스트링을 첨가해주면 필요한 데이터만 가져올 수 있는 것이었다! (프론트 정보량이 가벼워지겠지?)

    프론트에서 보낼 인자는 2가지이다. 시작점(offset)길이(limit)!

     

    const [index, setIndex] = useState(1);
    const LIMIT = 15;
    
    fetch(`http://localhost:5000/items?offset=${index}&limit=${LIMIT}`)
      .then(...)

    서버로 fetch() 하는 로직의 Pagination URL을 간단하게 작성한 것이다.

    • offset의 경우엔 변할 경우가 많기 때문에 state로 관리한다.(페이지 버튼)
    • limit는 한 페이지에서 불러올 개수가 정해져있기 때문에 고정값을 쓴다. (변수명도 불변 컨벤션인 UpperCase로 적음)

    📗 Semantic URL

    1. Path Parameter 과 Semantic URL

    둘 다, /를 사용하지만, 차이를 명확하게 할 필요가 있다. 아래 예시는, 내 서버의 디테일 페이지 Router 미들웨어이다.

    router.get('/detail/:id', function(req, res, next) {
      const restDetail = restsData.find((rest) => rest.id === Number(req.params.id));
      res.json(restDetail);
    });
    • Path Parameter : Router가 할당된 URL 경로이다. (위 예시에선, /detail 이 해당)
    • Semantic URL : 해당 경로에서 변수로 사용할 URL 위치와 변수명으로 이루어져있다. (위 예시에선, :id 이 해당)

     

    2. Semantic URL 적용(Front-End)

    프론트는 큰 어려움 없다. fetch()의 경로를, `http://localhost:5000/rests/detail/${id}` 와 같이 설정하면 된다.

     

     

    3. Semantic URL 적용(Back-End)

    app.get('/topic/:id',function(req,res){
      res.send(req.params.id);
    })

    - Semantic URL 문법

    위처럼, 해당경로 하위('/')에서, ':' 로 Semantic URL 시작을, 그 뒤에는 변수명을 적는다. (id 말고 다른 변수명도 설정 가능)

     

    - req.params.[변수명]

    Semantic URL 경로의 변수값을 가져올 수 있는 Request 메서드이다. 마찬가지로, 변수값을 string 형태로 반환한다.

     

     

    4. Query String vs Semantic URL

    // Query String
    http://localhost:5000/details?id=100&content=location
    
    // Semantic URL
    http://localhost:5000/details/100/location

    이처럼, 어떤 변수에 대해선 Semantic URL이 직관적이고 간결할 수 있다.(상세 페이지의 id(index) 라던지, 추가정보 등)

    반대로, 다양한 정보를 연계해야하는 검색, 필터, Pagination의 경우엔 Query String을 활용하는 것이 더 적절할 것이다.


    URL 파라미터들에 대해 간단히 포스팅해볼까 했는데, 생각보다 시간이 오래 걸렸다.

    아무래도, 프론트의 패치로직과, 백의 라우터 로직을 같이 다뤄야하다 보니 예시를 적절이 고르는게 참 난감했다.

     

    나도, URL 구성을 다시 공부하다 보니 프래그먼트라는 새로운 성분을 알게 되었다. 상세 페이지 제작 등에 한 번 활용해봐야 겠다.

     

    [출처]

    - Express 공식문서(기본 라우팅, 라우팅, API참조) : expressjs.com/ko/guide/routing.html

    - Hula_Hula 님의 블로그 : twoearth.tistory.com/38  

    - victorydntmd 님의 블로그 : victorydntmd.tistory.com/287

    반응형
Designed by Tistory.