Overview
Puedes utilizar Cliente ApolloPara conectarte a la API GraphQL expuesta de tu aplicación Realm desde una aplicación React. Apollo Client ejecuta consultas y mutaciones, mantiene una caché de datos del lado del cliente y se integra en tu aplicación con componentes y ganchos React idiomáticos.
Nota
Consulta una aplicación de demostración funcional
Consulta el repositorio Realm GraphQL - Apollo (React) en GitHub para ver una aplicación React y Apollo completamente configurada y lista para conectarse a tu backend de Atlas App Services. Utiliza sample_mflix.movies colección que está incluida en los conjuntos de datos de muestra de MongoDB Atlas.
Si no desea clonar el repositorio, la aplicación de demostración también está disponible en el navegador en Realm GraphQL CodeSandbox.
Configurar el cliente Apollo
Instalar dependencias
Como en cualquier proyecto de Realm, necesitarás instalar el SDK web de Realm para autenticar usuarios y solicitudes.
npm install realm-web
Apollo incluye los componentes principales necesarios para crear un cliente en un paquete llamado @apollo/client. También requiere el paquete graphql para analizar consultas GraphQL.
npm install @apollo/client graphql
Crear un cliente GraphQL de Apollo
Crea un nuevo objeto ApolloClient que apunte al punto final de la API GraphQL de tu aplicación Realm. Genera la URL del punto final según el ID de tu aplicación Realm o búscala en la página GraphQL de la interfaz de usuario de Realm.
// 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(), });
Configurar la autenticación de usuario
El ApolloClient está configurado para enviar solicitudes a tu aplicación. Sin embargo, todas las solicitudes GraphQL de Realm deben incluir un token de acceso de usuario válido para autenticarlas, por lo que, por el momento, cualquier operación enviada desde Apollo fallará. Los tokens de acceso caducan después de 30 minutos y deben actualizarse.
Para autenticar solicitudes, debe agregar un encabezado de autorización con un token de acceso de usuario de Realm válido a cada solicitud GraphQL.
Puedes autenticar a un usuario y obtener su token de acceso con el SDK web de Realm. El HttpLink objeto Apollo te permite añadir encabezados personalizados a cada solicitud mediante la definición de una fetch función personalizada.
// 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(), });
Agregue el cliente Apollo a su aplicación
El objeto Apollo client ya está configurado para enviar solicitudes GraphQL autenticadas al backend de App Services. Solo queda ponerlo a disposición del resto de la aplicación React.
El paquete @apollo/client exporta un componente ApolloProvider que permite que el client esté disponible para cualquier gancho de Apollo que se llame desde componentes secundarios. Envuelva su aplicación en un ApolloProvider y pase el objeto client al proveedor.
// ... code to create the GraphQL client const AppWithApollo = () => ( <ApolloProvider client={client}> <App /> </ApolloProvider> );
Ejecutar consultas y mutaciones
El paquete @apollo/client incluye un conjunto de ganchos declarativos de React que conectan sus componentes a la API GraphQL y manejan la ejecución de consultas y mutaciones.
Para definir consultas y mutaciones que puedas pasar a los hooks, instala graphql-tag:
npm install graphql-tag
Importa los hooks relevantes y el constructor de la consulta GraphQL en la parte superior del archivo donde los vayas a utilizar.
// import whichever Apollo hooks you're using import { useQuery, useMutation } from "@apollo/client"; import gql from "graphql-tag";
Nota
Los hooks de Apollo deben tener un ApolloProvider
Los componentes que invocan los ganchos de consulta y mutación deben ser descendientes del ApolloProvider configurado para el backend de App Services. Los ganchos invocan los métodos de consulta y mutación en el client objeto proporcionado.
Ejecutar una consulta
El Cliente Apollo incluye dos ganchos para ejecutar consultas. Los ganchos aceptan parámetros idénticos, pero difieren en el momento en que ejecutan la consulta:
useQuery() se ejecuta automáticamente al montar su componente. También devuelve una llamada que vuelve a ejecutar la consulta cada vez que la llamas.
useLazyQuery() devuelve una función de devolución de llamada que ejecuta la consulta cada vez que se la llama. No la ejecuta al montar el componente.
Ambos ganchos aceptan una definición de consulta y opciones adicionales, incluyendo variables, que Apollo pasa a la consulta. Además, ambos devuelven información sobre el estado de ejecución actual de la consulta y los datos devueltos de la ejecución más reciente.
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} />; }
Ejecutar una mutación
El gancho useMutation() acepta una definición de mutación y un objeto de configuración opcional. La opción más común que deberá pasar es un variables objeto que mapee las variables GraphQL en la definición de mutación.
El gancho devuelve varios objetos en una matriz:
una función de devolución de llamada que ejecuta la mutación
un objeto que incluye información sobre el estado de ejecución de la mutación y datos devueltos de la ejecución más reciente.
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> ); }
Paginar datos
Puede paginar datos en sus consultas con los tipos proporcionados por el esquema GraphQL generado por su API. Puede paginar datos con el cliente GraphQL de Apollo mediante los ganchos useQuery() y useLazyQueryHook().
La API Atlas GraphQL no tiene un offset operador, como recomienda la documentación de GraphQL para la paginación.
El siguiente ejemplo utiliza el gancho useQuery() y una consulta GraphQL que puede consultar datos en orden ascendente y descendente, dependiendo de las variables que le pase.
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> ); }
Actualizar tokens de acceso
Al usar Realm GraphQL y un Apollo Client, los tokens de acceso expiran 30 minutos después de ser otorgados. Puedes refrescar los tokens de acceso de usuario con el método refreshAccessToken() del Realm Web SDK.
// 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(), });