Fetch your GraphQL data automagically: Building a movie app with Hasura & gqless
Step 1: Hasura setup
- Create a project on Hasura Cloud. Click on the "Deploy to Hasura" button to signup for a free account and create a new project.
- From the Data tab on the Hasura console, create two tables:
movies
andcharacters
.- Columns for
movies
:id
(uuid),name
(text),image_url
(text) - Columns for
characters
:id
(uuid),name
(text),image_url
(text),movie_id
(uuid) - For the two
id
columns, enter the functiongen_random_uuid()
in the default value field.
- Columns for
- Create a one-to-many relationship between
movies
andcharacters
by:- Adding a foreign key constraint from the
movie_id
field of thecharacters
table to theid
field of themovies
table - Clicking “Track All” in the Data tab
- Adding a foreign key constraint from the
- From the “Insert Row” tab on each table, add your favorite movies and characters as rows! Hint: you can also do this with mutations in the GraphiQL tab.
Step 2: App setup
npx create-react-app movies
npm install gqless @gqless/react typescript
npm install @gqless/cli --save-dev
Step 3: Basic app
import React from "react";
import reactDOM from "react-dom";
import { App } from "./App";
reactDOM.render(<App />, document.getElementById("root"));
import React from "react";
export const App = () => {
return <div>movies!</div>;
}
Step 4: Fun with gqless
import React, { Suspense } from "react";
import { query } from "./movies_api";
import { graphql } from "@gqless/react";
const Movies = graphql(() => {
return (
<div>
{query.movies.map(movie => {
return <div key={movie.id}>{movie.name}</div>;
})}
</div>
);
});
export const App = () => {
return (
<Suspense fallback="loading">
<Movies />
</Suspense>
);
};
Step 5: Listing characters
import { query, movies } from "./movies_api";
const Movies = graphql(() => {
return (
<div>
{query.movies.map(movie => {
return <Movie key={movie.id} movie={movie}></Movie>;
})}
</div>
);
});
const Movie = graphql(({ movie }: { movie: movies }) => {
return (
<div>
<h3>{movie.name}</h3>
{movie.characters.map(character => {
return <div key={character.id}>{character.name}</div>;
})}
</div>
);
});
Step 6: Movie links
npm install react-router react-router-dom @types/react-router @types/react-router-dom @emotion/styled @emotion/core
import { BrowserRouter, Link, Switch, Route } from "react-router-dom";
import styled from '@emotion/styled';
export const App = () => {
return (
<BrowserRouter>
<Suspense fallback="loading">
<Switch>
<Route path="/movie/:id">
{({ match }) => {
const movie = query.movies_by_pk({ id: match?.params.id! });
return movie && <Movie movie={movie}></Movie>;
}}
</Route>
<Movies />
</Switch>
</Suspense>
</BrowserRouter>
);
};
const Movie = graphql(({ movie }: { movie: movies }) => {
return (
<div>
<h3>
<Link to={`/movie/${movie.id}`}>
{movie.name}
</Link>
</h3>
{movie.characters.map(character => {
return <div key={character.id}>{character.name}</div>;
})}
</div>
);
});
Step 7: Extensions!
import { GET_KEY, REDIRECT, RedirectHelpers } from "gqless";
// Keyed values
// object name `movies` must match type name from our schema
export const movies = {
[GET_KEY]: (movie: any) => movie.id
// now the movie should be keyed by its `id` in the cache
};
// Cached redirects
// the names `query_root` and `movies_by_pk` are based on our schema
export const query_root = {
movies_by_pk: {
// we check the cache before fetching from the network
[REDIRECT](args: any, { getByKey }: RedirectHelpers) {
return getByKey(args.id);
}
}
};
Step 8: List characters
const StyledMovies = styled.div`
display: flex;
flex-direction: column;
`;
const Movies = graphql(() => {
return (
<StyledMovies>
{query.movies.map(movie => {
return (
<Link key={movie.id} to={`/movie/${movie.id}`}>
{movie.name}
</Link>
);
})}
</StyledMovies>
);
});
import { query, movies, characters } from "./movies_api";
const StyledCharacter = styled.div`
display: flex;
align-items: center;
`;
const CharacterImg = styled.img`
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 50%;
margin: 10px;
`;
const Characters = graphql(({ movie }: { movie: movies }) => {
return (
<div>
{movie.characters.map(character => {
return <Character key={character.id} character={character} />;
})}
</div>
);
});
const Character = graphql(({ character }: { character: characters }) => {
return (
<StyledCharacter>
<CharacterImg className="charImg" src={character.image_url} />
{character.name}
</StyledCharacter>
);
});
const Movie = graphql(({ movie }: { movie: movies }) => {
return (
<div>
<h3>
<Link to={`/movie/${movie.id}`}>{movie.name}</Link>
</h3>
<Suspense fallback="loading">
<Characters movie={movie} />
</Suspense>
</div>
);
});
Conclusion
- How to set up array relationships with Hasura,
- How to set up gqless using Hasura's GraphQL endpoint,
- Many of gqless's awesome features, including:
- Automagic data fetching,
- Type generation,
- Extensions for caching behavior.
Related reading