Apollo 클라이언트(React) - 웹 SDK
이 페이지의 내용
GraphQL 은 더 이상 사용되지 않습니다. 자세히 알아보기.
개요
Apollo Client 을(를) 사용할 수 있습니다. React 애플리케이션 에서 Realm 앱의 노출된 GraphQL API 에 연결합니다. Apollo Client는 쿼리 및 변형을 실행하고, 클라이언트 사이드 데이터 캐시 를 유지 관리하며, 관용적 인 React 구성 요소 및 후크를 사용하여 앱 에 통합합니다.
참고
워킹 데모 애플리케이션 보기
확인 Realm GraphQL - Apollo(React) 에서 리포지토리를 Github 확인하면 자체 백엔드에 React 연결할 준비가 된 완전히 설정된 & Apollo 애플리케이션을 볼 수 Atlas App Services 있습니다. MongoDB Atlas 샘플 데이터 세트에 포함된 sample_mflix.movies
컬렉션을 사용합니다.
리포지토리 를 복제하지 않으려면 브라우저 내 Realm GraphQL CodeSandbox 에서 데모 애플리케이션 을 사용할 수도 있습니다.
Apollo 클라이언트 설정
설치 종속성
다른 Realm 프로젝트와 마찬가지로 사용자와 요청을 인증하기 위해 Realm 웹 SDK 를 설치해야 합니다.
npm install realm-web
Apollo는 클라이언트를 생성하는 데 필요한 핵심 구성 요소를 @apollo/client라는 패키지에 번들로 제공합니다. 또한 GraphQL 쿼리를 구문 분석하려면 graphql 패키지가 필요합니다.
npm install @apollo/client graphql
Apollo GraphQL 클라이언트 생성
Realm 앱의 GraphQL API 점을 새로운 ApolloClient 객체를 생성합니다. Realm 앱 ID를 기반으로 엔드포인트 URL을 생성하거나 Realm UI의 GraphQL 페이지에서 찾을 수 있습니다.
// Add your App ID const graphqlUri = `https://services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql`; // Local apps should use a local URI! // const graphqlUri = `https://us-east-1.aws.services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql` // const graphqlUri = `https://eu-west-1.aws.services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql` // const graphqlUri = `https://ap-southeast-1.aws.services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql` const client = new ApolloClient({ link: new HttpLink({ uri: graphqlUri, }), cache: new InMemoryCache(), });
사용자 인증 설정
은(는)ApolloClient
앱에 요청을 보내도록 구성됩니다. 그러나 모든 Realm GraphQL 요청에는 요청을 인증하기 위한 유효한 사용자 액세스 토큰이 포함되어야 하므로, 현재로서는 Apollo에서 전송된 모든 작업이 실패합니다. 액세스 토큰은 30분 후 만료되며 새로 고침해야 합니다.
요청을 인증하려면 각 GraphQL 요청에 유효한 리.Realm 사용자 액세스 토큰이 포함된 권한 부여 헤더를 추가해야 합니다.
Realm 웹 SDK를 사용하여 사용자를 인증하고 액세스 토큰을 받을 수 있습니다. Apollo HttpLink
객체를 사용하면 사용자 지정 fetch
함수를 정의하여 모든 요청에 사용자 지정 헤더를 추가할 수 있습니다.
// Connect to your MongoDB Realm app const app = new Realm.App(APP_ID); // Gets a valid Realm user access token to authenticate requests async function getValidAccessToken() { // Guarantee that there's a logged in user with a valid access token if (!app.currentUser) { // If no user is logged in, log in an anonymous user. The logged in user will have a valid // access token. await app.logIn(Realm.Credentials.anonymous()); } else { // An already logged in user's access token might be stale. Tokens must be refreshed after // 30 minutes. To guarantee that the token is valid, we refresh the user's access token. await app.currentUser.refreshAccessToken(); } return app.currentUser.accessToken; } // Configure the ApolloClient to connect to your app's GraphQL endpoint const client = new ApolloClient({ link: new HttpLink({ uri: `https://services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql`, // We define a custom fetch handler for the Apollo client that lets us authenticate GraphQL requests. // The function intercepts every Apollo HTTP request and adds an Authorization header with a valid // access token before sending the request. fetch: async (uri, options) => { const accessToken = await getValidAccessToken(); options.headers.Authorization = `Bearer ${accessToken}`; return fetch(uri, options); }, }), cache: new InMemoryCache(), });
앱에 Apollo 클라이언트 추가
이제 인증된 GraphQL 요청을 App Services 백엔드로 보내도록 Apollo 객체가 구성되었습니다.client
이제 나머지 React 애플리케이션에서 사용할 수 있도록 하는 일만 남았습니다.
패키지는 하위 구성 @apollo/client
요소에서 호출하는 ApolloProvider
client
모든 Apollo 후크에서 을(를) 사용할 수 있도록 하는 구성 요소를 내보냅니다. 앱을 ApolloProvider
(으)로 래핑하고 client
객체를 제공자에게 전달합니다.
// ... code to create the GraphQL client const AppWithApollo = () => ( <ApolloProvider client={client}> <App /> </ApolloProvider> );
쿼리 및 돌연변이 실행
@apollo/client
패키지에는 구성 요소를 GraphQL API에 연결하고 쿼리 및 변형 실행을 처리하는 선언적 React 후크 세트가 포함되어 있습니다.
후크에 전달할 수 있는 쿼리 및 변형을 정의하려면 graphql-tag를 설치하세요.
npm install graphql-tag
관련 후크와 GraphQL 쿼리 생성자를 사용하고 있는 파일 상단에서 이를 가져옵니다.
// import whichever Apollo hooks you're using import { useQuery, useMutation } from "@apollo/client"; import gql from "graphql-tag";
참고
Apollo Hooks에는 ApolloProvider가 있어야 합니다.
쿼리 및 변형 후크를 호출하는 구성 요소는 ApolloProvider 의 하위 항목이어야 합니다. Atlas App Services 백엔드에 대해 구성한 항목입니다. 후크가 쿼리 를 client
호출합니다. 및 돌연변이 제공된 객체에 대한 메서드입니다.
쿼리 실행
Apollo Client에는 쿼리 실행을 위한 후크가 두 개 포함되어 있습니다. 후크는 동일한 매개 변수를 허용하지만, 쿼리를 실행하는 시점이 다릅니다.
useQuery()를 호출합니다. 구성 요소가 마운트될 때 자동으로 실행됩니다. 또한 호출할 때마다 쿼리를 다시 실행하는 콜백을 반환합니다.
useLazyQuery() 는 호출할 때마다 쿼리를 실행하는 콜백 함수를 반환합니다. 구성 요소 마운트에서 쿼리를 실행하지 않습니다.
두 후크 모두 쿼리 정의와 variables
Apollo가 쿼리에 전달하는 을(를) 포함한 추가 옵션을 허용합니다. 또한 두 쿼리 모두 쿼리의 현재 실행 상태와 가장 최근 실행에서 반환된 데이터에 대한 정보를 반환합니다.
const ALL_MOVIES = gql` query AllMovies { movies { _id title year runtime } } `; // Must be rendered inside of an ApolloProvider function Movies() { const { loading, error, data } = useQuery(ALL_MOVIES); if (loading) { return <div>loading</div>; } if (error) { return <div>encountered an error: {error}</div>; } return <MovieList movies={data.movies} />; }
돌연변이 실행
useMutation() hook은 변형 정의와 선택적 구성 객체를 허용합니다. 전달해야 하는 가장 일반적인 옵션은 variables
GraphQL 변수 에 매핑되는 객체입니다. 변형 정의에 포함됩니다.
후크는 여러 개의 객체를 배열로 반환합니다:
변이를 실행하는 콜백 함수
변형의 실행 상태와 가장 최근 실행에서 반환된 데이터에 대한 정보를 포함하는 객체입니다.
const UPDATE_MOVIE_TITLE = gql` mutation UpdateMovieTitle($oldTitle: String!, $newTitle: String!) { updateOneMovie(query: { title: $oldTitle }, set: { title: $newTitle }) { title year } } `; // Must be rendered inside of an ApolloProvider function MovieList({ movies }) { const [updateMovieTitle] = useMutation(UPDATE_MOVIE_TITLE); return ( <ul> {movies.map((movie) => ( <li key={movie._id}> <div>{movie.title}</div> <button onClick={() => { updateMovieTitle({ variables: { oldTitle: movie.title, newTitle: "Some New Title", }, }); }} > Update Title </button> </li> ))} </ul> ); }
Paginate Data
API에서 생성한 GraphQL 스키마에서 제공하는 유형으로 쿼리의 데이터에 페이지를 매길 수 있습니다. useQuery()
및 useLazyQueryHook()
후크를 사용하여 Apollo GraphQL 클라이언트로 데이터에 페이지를 매길 수 있습니다.
Atlas GraphQL API에는 offset
GraphQL 문서에서 페이지 매김에 권장하는 것과 같은 연산자가 없습니다.
다음 예시에서는 useQuery()
후크와 전달한 변수에 따라 데이터를 오름차순 및 내림차순으로 쿼리할 수 있는 GraphQL 쿼리를 사용합니다.
const PAGINATE_MOVIES = gql` query PaginateMovies( $prevTitle: String $nextTitle: String $limit: Int! $sortDirection: MovieSortByInput! ) { movies( # Can add other query filters here if you'd like query: { title_gt: $prevTitle, title_lt: $nextTitle } limit: $limit sortBy: $sortDirection ) { _id title year } } `; const resultsPerPage = 5; function PaginateMovies() { const [variables, setVariables] = useState({ prevTitle: undefined, nextTitle: undefined, limit: resultsPerPage, sortDirection: "TITLE_ASC", }); const [firstTitle, setFirstTitle] = useState(); const { data, error, loading } = useQuery(PAGINATE_MOVIES, { variables, }); const [pagePreviousDisabled, setPagePreviousDisabled] = useState(true); const [pageNextDisabled, setPageNextDisabled] = useState(false); useEffect(() => { if (data?.movies?.length && firstTitle === undefined) { setFirstTitle(data.movies[0].title); } setPagePreviousDisabled(false); if (data?.movies?.length < resultsPerPage) { setPageNextDisabled(true); setPagePreviousDisabled(false); } if ( variables.prevTitle === undefined || data?.movies[0]?.title === firstTitle ) { setPagePreviousDisabled(true); setPageNextDisabled(false); } }, [data, data?.movies?.length, firstTitle, variables.prevTitle]); if (loading) { return <div>loading</div>; } if (error) { return <div>encountered an error: {error.message}</div>; } function goToNextPage() { setVariables({ nextTitle: undefined, prevTitle: data.movies[data.movies.length - 1].title, limit: resultsPerPage, sortDirection: "TITLE_ASC", }); } function goToPrevPage() { setVariables({ nextTitle: data.movies[0].title, prevTitle: undefined, limit: resultsPerPage, sortDirection: "TITLE_DESC", }); } const sorted = data.movies.sort((a, b) => { const titleA = a.title.toUpperCase(); // ignore upper and lowercase const titleB = b.title.toUpperCase(); // ignore upper and lowercase if (titleA < titleB) { return -1; // titleA comes first } if (titleA > titleB) { return 1; // titleB comes first } }); return ( <div> <h1>Movies</h1> {data?.movies?.length ? ( sorted.map((movie) => ( <div key={movie._id}> <h3>{movie.title}</h3> <p>Year Published: {" " + movie.year}</p> <br /> </div> )) ) : ( <p>No movies in system</p> )} <div> <button disabled={pagePreviousDisabled} onClick={goToPrevPage}> ← Previous Page </button> <button disabled={pageNextDisabled} onClick={goToNextPage}> Next Page → </button> </div> </div> ); }
액세스 토큰 새로 고침
Realm GraphQL과 Apollo Client를 사용할 때 액세스 토큰은 부여된 후 30분 후에 만료됩니다. Realm 웹 SDK의 메서드를 사용하여 사용자 액세스 토큰을 새로 고칠 수 refreshAccessToken()
있습니다.
// Connect to your MongoDB Realm app const app = new Realm.App(APP_ID); // Gets a valid Realm user access token to authenticate requests async function getValidAccessToken() { // Guarantee that there's a logged in user with a valid access token if (!app.currentUser) { // If no user is logged in, log in an anonymous user. The logged in user will have a valid // access token. await app.logIn(Realm.Credentials.anonymous()); } else { // An already logged in user's access token might be stale. Tokens must be refreshed after // 30 minutes. To guarantee that the token is valid, we refresh the user's access token. await app.currentUser.refreshAccessToken(); } return app.currentUser.accessToken; } // Configure the ApolloClient to connect to your app's GraphQL endpoint const client = new ApolloClient({ link: new HttpLink({ uri: `https://services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql`, // We define a custom fetch handler for the Apollo client that lets us authenticate GraphQL requests. // The function intercepts every Apollo HTTP request and adds an Authorization header with a valid // access token before sending the request. fetch: async (uri, options) => { const accessToken = await getValidAccessToken(); options.headers.Authorization = `Bearer ${accessToken}`; return fetch(uri, options); }, }), cache: new InMemoryCache(), });