GraphQL - Node Tutorial - 09. Filtering, Pagination & Sorting

cadenzah·2020년 2월 12일
0

GraphQL - Node Tutorial

목록 보기
9/10
post-thumbnail

알립니다

이 번역 시리즈는 2019년 10월 경에 작성되었습니다. 원본인 GraphQL - Node 튜토리얼은 현재 새로운 버전으로 새롭게 작성되었습니다. 따라서 이 글은 Deprecated된 글임을 알려드립니다.

필터링, 페이지네이션, 정렬

이번 장은 이 튜토리얼의 가장 마지막 부분으로, API 구현의 마무리 작업이 이루어집니다. 클라이언트 측에서 필터링과 페이지네이션을 위한 인자를 제공하여 feed 쿼리로부터 반환되는 Link 요소 목록을 제어할 수 있도록 하는 것이 목표입니다.

필터링

Prisma를 사용하면, 큰 수고를 들이지 않더라도 필터링 기능을 API에 구현할 수 있습니다. 이전 장에서 이루어졌던 것과 유사하게, 쿼리를 해결하는 무거운 작업은 강력한 Prisma 엔진을 통하여 이루어질 겁니다. 그저 들어오는 쿼리를 잘 전달해주기만 하면 됩니다.

첫번째 할일은, API를 통하여 노출시킬 필터에 대하여 생각해보는 것입니다. 지금의 경우, API의 feed 쿼리는 필터링 문자열을 받을 수 있습니다. 이제 이 쿼리는 해당 필터링 문자열 상에 적힌 url 또는 description을 포함하고 있는 Link 요소만을 반환합니다.

어플리케이션의 스키마를 열고 filter 문자열을 feed 쿼리에 추가합니다.
($ .../hackernews-node/src/schema.graphql)

type Query {
  info: String!
  feed(filter: String): [Link!]!
}

다음으로, 클라이언트가 제공하는 새로운 인자를 처리할 수 있도록 feed 리졸버의 구현 내용을 수정해야 합니다.

src/resolvers/Query.js를 열고 feed 리졸버를 아래와 같이 수정합니다.
($ .../hackernews-node/src/resolvers/Query.js)

async function feed(parent, args, context, info) {
  const where = args.filter ? {               // 수정
    OR: [                                     // 수정
      { description_contains: args.filter },  // 수정
      { url_contains: args.filter },          // 수정
    ],                                        // 수정
  } : {}                                      // 수정
                                              // 수정
  const links = await context.prisma.links({  // 수정
    where
  })
  return links
}

filter 문자열이 제공되지 않는다면, where 객체는 단지 빈 객체가 되며 links 쿼리에 대한 응답을 반환할 때에 필터링을 위한 조건이 Prisma 엔진에 적용되지 않게 됩니다.

args를 통하여 filter가 전달되는 경우, 앞서 논의한 바오 같이 2개의 필터링 조건을 표현하는 where 객체를 구성하게 됩니다. 여기서 where 인자는 명시된 조건에 부합하지 않는 Link 요소를 걸러내기 위하여 Prisma가 사용합니다.

그 정도면 필터링 기능을 위하여 충분합니다! 필터링 API를 테스트해보도록 하죠. 아래는 예시로 사용할 수 있는 쿼리입니다.

query {
  feed(filter:"QL") {
    id
  	description
    url
    postedBy {
      id
      name
    }
  }
}

페이지네이션

API 디자인에서 페이지네이션은 까다로운 주제 중 하나입니다. 페이지네이션을 구현하는 데에는 크게 두 가지 접근 방식이 사용됩니다.

  • Limit-Offset: 반환될 항목들에 대한 인덱스를 제공하여 전체 목록 중 특정 부분을 요청(사실 대부분의 경우, 반환될 항목들에 대한 시작 인덱스(offset)와 개수(limit)를 제공하는 것이 보통입니다)
  • Cursor-based: 이 페이지네이션 모델은 발전된 형태입니다. 목록 내의 모든 요소는 고유 ID(지시자)로서 연관됩니다. 클라이언트에서는 시작 위치의 요소에 대한 지시자와 항목 개수를 제공합니다.

Prisma는 두 가지의 페이지네이션 방식을 모두 지원합니다(이 문서에서 관련 내용을 확인할 수 있습니다). 이 튜토리얼에서는 Limit-Offset 방식의 페이지네이션을 구현해보도록 하겠습니다.

참고: 각각의 페이지네이션 방식에 대한 다양한 생각들은 이 문서에서 확인할 수 있습니다.

Limit과 Offset은 Prisma API 상에서 다르게 불립니다.

  • Limitfirst라고 불리며, 이는 제공된 시작 인덱스 이후에 등장하는 첫번째 x개의 요소를 가져온다는 의미입니다. 참고로, last 인자를 사용하면 마지막 x개의 요소를 반환하게 됩니다.
  • start 인덱스skip이라고 불리며, 이는 반환될 항목들을 모으기 전에 전체 목록 상에서 해당 개수 만큼의 요소를 건너뛴다는 의미입니다. skip이 제공되지 않는다면, 기본값으로 0이 사용됩니다. 이 경우 항상 전체 목록의 맨 처음부터 페이지네이션이 이루어집니다. last와 함께 사용할 경우, 맨 끝부터 이루어집니다.

자, 이제 feed 쿼리에 skipfirst 인자를 추가해봅시다.

어플리케이션의 스키마를 열고, skipfirst 인자를 받을 수 있도록 feed 쿼리를 수정합니다.
($ .../hackernews-node/src/schema.graphql)

type Query {
  info: String!
  feed(filter: String, skip: Int, first: Int): [Link!]! // 수정
}

이제, 리졸버 구현으로 넘어가겠습니다.

src/resolvers/Query.js를 열고 feed 리졸버의 구현을 아래와 같이 수정합니다.
($ .../hackernews-node/src/resolvers/Query.js)

async function feed(parent, args, context, info) {
  const where = args.filter ? {
    OR: [
      { description_contains: args.filter },
      { url_contains: args.filter },
    ],
  } : {}

  const links = await context.prisma.links({
    where,
    skip: args.skip,   // 수정
    first: args.first  // 수정
  })
  return links
}

달라진 것이라곤 단지 이제 links 쿼리를 호출할 때, args 객체를 통하여 전달된 두 개의 추가적인 인자를 받을 수 있다는 점입니다. 이번에도 역시, Prisma가 우리를 대신하여 수고해줄 겁니다 🙏.

아래의 쿼리를 사용하여 페이지네이션 API를 테스트해보세요. 이 쿼리는 전체 목록에서 두 번째 Link를 반환합니다.

query {
  feed(
    first: 1
    skip: 1
  ) {
    id
    description
    url
  }
}

정렬

Prisma를 사용하면 요소들의 목록을 특정 기준에 따라 정렬하여 반환할 수 있습니다. 예를 들어, Link의 목록을 url 또는 description의 가나다 순 기준으로 정렬할 수 있습니다. Hacker News API의 경우, GraphQL 서버 상의 Prisma API가 어떤 정렬 방식을 사용할 것인지 등을 클라이언트 측에서 결정할 수 있도록 해줄 겁니다. 정렬 방식을 가리키는 열거자(enum)을 사용하면 됩니다.

schema.graphql에 아래의 열거자 정의를 추가합니다.
($ .../hackernews-node/src/schema.graphql)

enum LinkOrderByInput {
  description_ASC
  description_DESC
  url_ASC
  url_DESC
  createdAt_ASC
  createdAt_DESC
}

Link 요소가 정렬될 수 있는 다양한 방식들을 나타냅니다.

orderBy 인자를 포함하도록 feed 쿼리를 수정합니다.
($ .../hackernews-node/src/schema.graphql)

type Query {
  info: String!
  feed(filter: String, skip: Int, first: Int, orderBy: LinkOrderByInput): [Link!]! // 수정
}

리졸버 구현은 앞서 페이지네이션 API 구현과 유사합니다.

src/resolvers/Query.jsfeed 리졸버 구현을 아래와 같이 수정하고, Prisma를 통하여 orderBy 인자를 전달합니다.
($ .../hackernews-node/src/resolvers/Query.js)

async function feed(parent, args, context, info) {
  const where = args.filter ? {
    OR: [
      { description_contains: args.filter },
      { url_contains: args.filter },
    ],
  } : {}

  const links = await context.prisma.links({
    where,
    skip: args.skip,
    first: args.first,
    orderBy: args.orderBy // 수정
  })
  return links
}

아주 좋습니다! 아래의 쿼리는 반환된 링크들을 각각의 생성 일자를 기준으로 정렬합니다.

query {
  feed(orderBy: createdAt_ASC) {
    id
    description
    url
  }
}

Hacker News API에서 마지막으로 구현할 것은 바로 Link 요소가 현재 데이터베이스 상에 몇 개가 저장되어있는지 알 수 있는 API입니다. 이를 위해서는 feed 쿼리를 약간 다듬고, API에서 반환할 새로운 타입 Feed를 만들어야 합니다.

GraphQL 스키마에 새로운 타입 Feed를 추가합니다. 이에 맞추어 feed 쿼리의 반환 타입도 수정합니다.
($ .../hackernews-node/src/schema.graphql)

type Query {
  info: String!
  feed(filter: String, skip: Int, first: Int, orderBy: LinkOrderByInput): Feed! // 수정
}

type Feed {        // 수정
  links: [Link!]!  // 수정
  count: Int!      // 수정
}                  // 수정

이제, feed 리졸버를 수정합니다.
($ .../hackernews-node/src/resolvers/Query.js)

async function feed(parent, args, context) {
  // 1
  const where = args.filter
    ? {
        OR: [
          { description_contains: args.filter },
          { url_contains: args.filter },
        ],
      }
    : {}

  const links = await context.prisma.links({
    where,
    skip: args.skip,
    first: args.first,
    orderBy: args.orderBy,
  })
  const count = await context.prisma // 수정
    // 2
    .linksConnection({               // 수정
      where,                         // 수정
    })                               // 수정
    .aggregate()                     // 수정
    .count()                         // 수정
  // 3
  return {                           // 수정
    links,                           // 수정
    count,                           // 수정
  }                                  // 수정
}
  1. 가장 첫번째로, 필터링, 정렬, 페이지네이션을 위하여 제공된 인자를 사용하여 Link 요소의 개수를 반환할 때에 사용할 객체를 만들고 있습니다.

  2. 다음으로, Prisma 클라이언트 API의 linksConnection 쿼리를 사용합니다. 이를 통하여 현재 데이터베이스 상에 저장된 Link 요소의 총 개수를 가져올 수 있습니다.

  3. linkscount는 GraphQL 스키마 상에 추가된 Feed 타입의 명세와 부합하도록 하나의 객체로 감싸집니다.

이제 마지막으로, GraphQLServer를 인스턴스화 할 때 새로운 리졸버를 포함시키면 됩니다.

새로 고친 feed 쿼리를 아래와 같이 테스트해보세요.

query {
  feed {
    count
    links {
      id
      description
      url
    }
  }
}

Quiz

Limit-Offet 페이지네이션을 사용할 때 Prisma API에서 주로 사용되는 인자는 무엇인가?

  • skip & last
  • skip & first
  • first & last
  • where & orderBy

0개의 댓글