A quick field guide to using Remix with Apollo GraphQL

A quick field guide to using Remix with Apollo GraphQL

Remix is the newest and hottest full-stack React-based framework on the block. Newly minted and open-sourced in November, the framework closed off 2021 with +10k stars on GitHub.

We took Remix for a test drive and found it fun and concise. It cuts out a lot of the bloat that comes with wiring a typical React app and backend together.

What makes Remix a different kind of React framework is that it’s primarily a server-side rendered framework. On the first page load the page is pre-rendered on the server, which ensures a super-fast load time. Only once the HTML page is rendered, is the JS loaded and the app is hydrated. Any navigation from this point on is done using JS and without a full page reload. Remix gives you the best of both worlds out of the box. The starter docs also provides a comprehensive guide on how quickly you can start up a functioning Remix app.

In Remix you use the loader function to specify code that should be run on the server. This means you can make direct calls to your database or other API to fetch data. But if you already make use of Apollo and have a schema, it is also possible to use Apollo client in the Remix backend. Even better, if you use Apollo SchemaLink you will avoid the additional network hop to a separate GraphQL backend.

We’re going to show you how easy it is to plug Apollo into Remix. But before we do, let’s quickly dig into how Remix data loaders work.

What are loaders and how loaders work

loaders in Remix is a function that gets called on the server before rendering to provide the route with the required data.

To use loaders, you'd need to import useLoaderData from Remix. The loader function runs on the server-side, meaning that sensitive data can remain protected if needed.

Here is an example of how to import a loader and use it in a function:

import { useLoaderData } from "remix"; ​ export const loader = async () => { // some code here return <some async here>; } ​ export default function Thing() { const data = useLoaderData(); return ( <article> {data.map(thing => ( <h2 key={thing.id}>{thing.title}</h2> ))} </article> ); }

That's basically how to import and use Remix loaders in a nutshell. Here is an example of some mock data being returned by the loader and used by the route.

import { Link, useLoaderData } from "remix"; ​ export const loader = () => { return [ { title: "Mutation-like immutability in JavaScript using immer.js", sourceLink: "https://layercode.com/community/immerjs-immutability", highlightHeading: false, createdAt: "20 Nov", }, { title: "How to set up Firebase Authentication in React", sourceLink: "https://layercode.com/community/firebase-auth-with-react", highlightHeading: false, createdAt: "21 Nov", }, ]; }; ​ export default function Index() { const data = useLoaderData(); return ( <div className="container"> {data.map((post) => ( <article key={post.id}> <p className="date">{post.createdAt}</p> ​ <a href={post.sourceLink} className={`title highlight ${post.highlightHeading}`} > {post.title} </a> </article> ))} </div> ); }

But how do we incorporate a real database with it?

Using Apollo GraphQL with Remix loaders

Apollo GraphQL's client library has a schema link functionality that lets you create mock and server-side renders. This works hand-in-hand with Remix's loader, which also supports async.

Here is an example of a loader with async:

import { useLoaderData } from "remix"; ​ export const loader = async () => { // some code here return <some async here>; }

To create a schema link with Apollo GraphQL, first, you need to install @apollo/client to your Remix project.

npm install @apollo/client --save

In addition to this, you will also need some GraphQL tools and utilities. Here is the complete npm list for everything that you need to load GraphQL data to your Remix app.

npm install @graphql-tools/utils graphql graphql-tag graphql-tools

To get started, set up your schema. A schema sets up the metadata for what your data should look like. It defines the types and fields that are expected by the data. Here is an example of a GraphQL type:

type Thing { attributeOfThing: String anotherAttribute: String }

A schema file is typically called schema.ts for quick identification. If you are trialing this out on a Remix app, you can put the following code inside a grapql folder under the app folder. Here is an example of a GraphQL schema:

import gql from 'graphql-tag'; import { makeExecutableSchema } from 'graphql-tools'; ​ const typeDefs = gql` type Post { title: String sourceLink: String highlightHeading: Boolean createdAt: String } type Query { posts: [Post] } `;

Now, we can make a GraphQL resolver with mock data, accessible via IResolvers from @graphql-tools/utils. This resolver will pass the processing over to our route when called. Here is an example of the GraphQL resolver:

// ~/graphql/resolvers.ts ​ import { IResolvers } from "@graphql-tools/utils"; ​ export type Post = { title: string; sourceLink: string; highlightHeading: boolean; createdAt: string; }; ​ const resolvers: IResolvers = { Query: { posts: async (source, args, context, info) => { const posts: Post[] = [ { title: "Mutation-like immutability in JavaScript using immer.js", sourceLink: "https://layercode.com/community/immerjs-immutability", highlightHeading: false, createdAt: "20 Nov", }, { title: "How to set up Firebase Authentication in React", sourceLink: "https://layercode.com/community/firebase-auth-with-react", highlightHeading: false, createdAt: "21 Nov", } ]; return posts; }, }, }; export default resolvers;

To connect the above resolver with the schema, go back to your schema file and invoke makeExecutableSchema(). This function lets you create GraphQLSchema instance. Here is the function in action (and complete schema example code):

// ~/graphql/schema.ts import gql from "graphql-tag"; import { makeExecutableSchema } from "graphql-tools"; import resolvers from "./resolvers"; const typeDefs = gql` type Post { title: String sourceLink: String highlightHeading: Boolean createdAt: String } type Query { posts: [Post] } `; const schema = makeExecutableSchema({ typeDefs, resolvers, }); export default schema;

To create server-side rending using Apollo, create a client that will use the loader above.

// ~/graphql/client.ts import { ApolloClient, InMemoryCache } from '@apollo/client'; import { SchemaLink } from '@apollo/client/link/schema'; import schema from './schema'; const graphqlClient = new ApolloClient({ cache: new InMemoryCache(), ssrMode: true, link: new SchemaLink({ schema }) }); export default graphqlClient;

To call our GraphQL client, navigate back to your Remix code and use the loader function. This will allow you to load your mock data on the server side. Here is an example what it looks like:

import { Link, useLoaderData } from "remix"; import graphqlClient from "~/graphql/client"; import gql from "graphql-tag"; export const loader = async () => { const res = await graphqlClient.query({ query: gql` query getPosts { posts { title sourceLink highlightHeading createdAt } } `, }); return res; }; export default function Posts() { const queryResults = useLoaderData<ApolloQueryResult<{ posts: Post[] }>>(); const data = queryResults.data?.posts; return ( <div className="container"> {data.map((post) => ( <article key={post.id}> <p className="date">{post.createdAt}</p> <a href={post.sourceLink} className={`title highlight ${post.highlightHeading}`} > {post.title} </a> </article> ))} </div> ); }

And that's basically it!

Wrapping up

Here is a link to our GitHub repo with a working Remix app that uses the above implementation.

Overall, using a schema link with mock data instead of using a network call can help slim down the development process. Remix, by design, is highly efficient for creating rapid prototypes. At a minimum, incorporating Apollo GraphQL only requires a schema, a resolver, and a client.

Share this post