Remix is a superb React framework with a give attention to server-side rendering. Remix permits purposes to have a quick load time, and as soon as the applying is loaded, hydration kicks in and offers it client-side performance.
Since Remix can run on the server, we are able to create API routes in our Remix software that may carry out backend duties, like connecting to a database. Because of applied sciences and instruments like Apollo GraphQL, we are able to make the most of Remix API routes to construct a practical, full-stack GraphQL software.
On this article, we’ll cowl arrange a Remix software with GraphQL performance. We’ll look into how we are able to implement easy CRUD performance in a Remix software by creating GraphQL server routes with Apollo.
What’s Remix?
Remix is a full-stack internet framework that focuses on the consumer interface. It really works again by means of internet fundamentals to ship a quick, modern, and resilient consumer expertise.
Remix is constructed on React and contains React Router, server-side rendering, TypeScript help, manufacturing server, and backend optimization.
In case you are accustomed to React, you’ll know that there are a number of frameworks that supply server-side rendering capabilities constructed on React. A couple of such frameworks embrace Subsequent.js and Astro.
Remix stands out from different server-side React frameworks for a couple of causes. Firstly, in contrast to different frameworks like Subsequent.js, it doesn’t provide static website technology (SSG). Slightly, it builds on the server/shopper mannequin and focuses on SSR, constructing and compiling every little thing on the server and leveraging distributed programs on the edge. The shopper receives a smaller payload and is hydrated on the shopper facet with React.
Remix additionally fully embraces internet requirements just like the Internet Fetch API, permitting builders to leverage the core instruments and options the online has to supply and has developed over time. For instance, Remix leverages HTTP caching and lets the browser cope with any complexity of caching assets.
Lastly, we’ll discover that Remix leverages HTML <type>
in the case of information mutations and CRUD performance, in contrast to different frameworks. Then, it makes use of motion
s and loader
s to deal with requests despatched with the <type>
.
Understanding Remix API routes
With Remix, routes are their very own APIs. Since every little thing is on the server, the part will get the information on the server facet when the consumer requests that route.
Routes in Remix are one other key distinction between Remix and a framework like Subsequent.js, the place the shopper is required to make requests to the API/server routes to carry out CRUD operations with the assistance of loader
s and motion
s.
Check out the code under:
// ./app/routes/index.tsx import { json } from '@remix-run/node'; import { useLoaderData } from '@remix-run/react'; // sort definitions sort Ebook = { title: string; style: string; }; sort Books = Array<Ebook>; sort LoaderData = { books: Books; }; // Loader perform export const loader = async () => { return json<LoaderData>({ books: [ { title: 'Harry Potter and the Deathly Hallows', genre: "Children's Fiction", }, { title: "Harry Potter and the Philosopher's Stone", genre: "Children's Fiction", }, ], }); }; export default perform Index() { // use information from loader const { books } = useLoaderData() as LoaderData; return ( <div model={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.4' }}> <h1>Welcome to Remix</h1> <ul> {books.map(({ title, style }, i) => { return ( <li key={i}> <h3> {title} </h3> <p> {style} </p> </li> ); })} </ul> </div> ); }
Click on right here to view on StackBlitz.
Within the code above, we are able to see we declared a loader
to return an array of books that may be fetched from a distant server or database, but it surely’s hardcoded for now. Since loader
capabilities are the backend “API” for our routes, we are able to simply get the information from the loader
in our Index
part with the assistance of useLoaderData
.
Since that is all occurring on the server, it’s rendered and despatched to the browser. There isn’t any further fetching finished on the shopper facet.
Along with fetching information, we are able to additionally ship information to be processed on the server facet utilizing actions
.
Let’s add a type with technique="put up"
to our Index
part and an motion
that may deal with the submission request:
import { json } from '@remix-run/node'; import { useLoaderData, useActionData, Kind } from '@remix-run/react'; // sort definitions // ... // loader perform export const loader = async () => { // ... }; // motion funtion export const motion = async ({ request }) => { const formData = await request.formData(); const identify = formData.get('identify'); return json({ identify }); }; export default perform Index() { // use information from loader const { books } = useLoaderData() as LoaderData; // get information from motion const information = useActionData(); return ( <div model={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.4' }}> {/* present "Stranger" if no information is offered but */} <h1>Welcome to Remix {information ? information.identify : 'Stranger'} </h1> <ul> {books.map(({ title, style }, i) => { return ( <li key={i}> <h3> {title} </h3> <p> {style} </p> </li> ); })} </ul> {/* Remix type part with "POST" technique */} <Kind technique="put up"> <div className="form-control"> <label htmlFor="identify"> Title <enter id="identify" identify="identify" sort="textual content" /> </label> </div> <button sort="submit">Submit </button> </Kind> </div> ); }
Within the code above, we created an motion
perform with a request
parameter. We acquire the shape values by calling request.formData()
and passing it to the formData
variable. As a way to get the identify
worth, we name the .get()
technique on formData
.
Lastly, we return a JSON object containing the identify
acquired from the request.
In our Index
part, so as to entry the JSON parsed information from our route motion
, we merely use the useActionData
hook. It returns undefined
if there hasn’t been a submission on the present location but.
Nicely, that’s essentially the most primary introduction to Remix and we’ve seen how we are able to get and ship information in our Remix software. Subsequent, we’ll check out GraphQL and the way we are able to use it in Remix.
Why use GraphQL?
The main benefit that GraphQL has over a REST API is that GraphQL reduces the necessity to make extra requests than is important when interacting with an API.
REST APIs, upon a request, are inclined to return extra or generally much less information than we’d like in our software. This would possibly make the response from our request unnecessarily bloated, and even inadequate for an operation. We’ll then have to hold out one other request, which, in flip, impacts the consumer expertise, particularly in unstable community situations).
With GraphQL, we’ve got to skill to explicitly request what we’d like in a response — no extra, no much less.
Combining the effectivity of GraphQL with the effectivity Remix brings in constructing server-side rendered internet purposes, we’ll be one thing actually superior.
Introduction to Apollo GraphQL
As outlined in this put up, “Apollo is a set of instruments to create a GraphQL server, and to devour a GraphQL API.“
One of many instruments we’ll be utilizing is Schema Hyperlink, which permits us to carry out GraphQL operations on a offered schema as an alternative of creating a community name to a GraphQL API.
This device, which we’ll be utilizing alongside others as we transfer forward on this article, is available in fairly helpful for SSR purposes.
Overview of our Remix and GraphQL app
Right here’s a fast rundown of what we’re going to construct on this tutorial.
Within the earlier part, we coated routes in Remix, loader
and motion
capabilities, and to reveal the ideas, we constructed a easy app that renders an inventory of books and features a type that asks and shows our identify upon submission.
Extra nice articles from LogRocket:
As a reminder, you may entry the code on StackBlitz and on this GitHub department.
Within the following part, we’ll be constructing a easy app that shows an inventory of books and supplies a type to add new books in a nested route, all utilizing GraphQL queries and mutations.
You possibly can entry the ultimate code on the schema-links department of the repository on GitHub.
Conditions
To comply with together with this text, we’ll want:
- A textual content editor (VSCode for instance)
- Fundamental data of Remix
- Fundamental data of GraphQL
- A latest Node.js model put in
Establishing a Remix mission
To create a brand new Remix mission, run the next within the terminal:
npx [email protected]
Then comply with the prompts:
? The place would you prefer to create your app? remix-graphql ? What sort of app do you wish to create? Simply the fundamentals ? The place do you wish to deploy? Select Remix App Server should you're not sure; it is simple to vary deployment targets. Remix App Server ? TypeScript or JavaScript? TypeScript ? Would you like me to run `npm set up`? Sure
As soon as the mission has been created and put in, we are able to proceed.
Apollo GraphQL in Remix
As a way to use Apollo GraphQL in our mission, we’ve got to put in a couple of packages:
npm set up @apollo/shopper @graphql-tools/schema
As soon as the packages are put in, let’s arrange our GraphQL shopper. As aforementioned, we’ll be utilizing Schema Hyperlink. In a brand new ./app/lib/apollo/index.ts
file, we’ll configure our schema and resolvers:
// ./app/lib/apollo/index.ts import { ApolloClient, gql, InMemoryCache } from "@apollo/shopper"; import { SchemaLink } from "@apollo/shopper/hyperlink/schema"; import { makeExecutableSchema } from "@graphql-tools/schema"; import { learn, write } from "../../utils/readWrite"; // a schema is a group of sort definitions (therefore "typeDefs") // that collectively outline the "form" of queries which are executed in opposition to // your information. export const typeDefs = gql` # Feedback in GraphQL strings (reminiscent of this one) begin with the hash (#) image. # This "Ebook" sort defines the queryable fields for each e-book in our information supply. sort Ebook { title: String writer: String } # the "Question" sort is particular: it lists all the accessible queries that # purchasers can execute, together with the return sort for every. on this # case, the "books" question returns an array of zero or extra Books (outlined above). sort Question { books: [Book] } `; // resolvers outline the approach for fetching the kinds outlined within the // schema. this resolver retrieves books from the "books" array above. export const resolvers = { Question: { books: () => { const books = learn(); return books; }, } }; const schema = makeExecutableSchema({ typeDefs, resolvers }); export const graphQLClient = new ApolloClient({ cache: new InMemoryCache(), ssrMode: true, hyperlink: new SchemaLink({ schema }), });
Within the code above, we outlined our Ebook
and Question
sorts. For our Question
, the books
question returns an inventory of Ebook
.
We additionally outlined our Question
resolver in resolvers
, which merely returns an inventory of books offered by the learn
perform. This could possibly be a perform that fetches an inventory of books from an exterior API or database. In our case, we’ll merely be getting our books from a JSON file.
Then, we create an executable schema utilizing makeExecutableSchema
and go within the schema (typeDefs
) and resolvers
.
Lastly, we outline a brand new ApolloClient
occasion as graphQLClient
and export it, prepared for use in our Remix loaders.
Earlier than that, let’s arrange our utility capabilities learn
and write
to allow us to learn from and modify the .json
file containing our listing of books.
Establishing utility capabilities to learn and write to JSON information
Create a JSON file at ./app/information/books.json
:
// ./app/information/books.json [ { "title": "The Awakening", "author": "Kate Chopin" }, { "title": "City of Glass", "author": "Paul Auster" }, { "title": "Harry Potter and the Deathly Hallows", "author": "JK Rowling" } ]
Create a brand new file ./app/utils/readWrite.ts
and enter the next code:
// ./app/utils/readWrite.ts import fs from "fs" // JSON file containing books array const dataPath = `${course of.cwd()}/app/information/books.json`; // perform to learn file contents export const learn = ( returnJSON = false, path = dataPath, encoding = "utf-8" ) => { strive { let information = readFileSync(path, encoding); return returnJSON ? information : JSON.parse(information); } catch (error) { console.log({ error }); return null; } }; // perform to write down content material to file export const write = (information: object, path = dataPath) => { let initialData = learn(); let modifiedData = [...initialData, data]; strive { writeFileSync(path, JSON.stringify(modifiedData, null, 2)); let consequence = learn(); return consequence; } catch (error) { console.log({ error }); return null; } };
Nice! Now that our learn and write capabilities for our resolvers have been created, let’s create a /books
path to run a question that lists out all our books utilizing the Apollo Consumer Schema hyperlinks.
Working our queries
Create a brand new file ./app/routes/books.tsx
with the next code:
// ./app/routes/books.tsx import { LoaderFunction, json } from "@remix-run/node"; import { gql } from "@apollo/shopper"; import { graphQLClient } from "~/lib/apollo"; import { useLoaderData } from "@remix-run/react"; const question = gql` question GetBooks { books { title writer } } `; export const loader: LoaderFunction = async ({ request, params }) => { const { information } = await graphQLClient.question({ question, }); return json({ books: information.books }); }; export default perform Books() { const { books } = useLoaderData(); return ( <primary> <part> <h1>All books</h1> <ul> {books.map(({ title, writer }: { title: string; writer: string }, index:quantity) => ( <li key={index}> <h3>{title}</h3> <p>{writer}</p> </li> ))} </ul> </part> </primary> ); }
Right here, we outline our question
and in our loader
perform, we execute that question utilizing graphQLClient.question()
and return the json
response.
In our Books
part, we get the books listing utilizing useLoaderData()
and render it:
Superior! Our books/
route with the question works. Subsequent, we’ll see how we are able to arrange mutations.
Establishing mutations
First, we outline our Mutation
and BookInput
sorts in typeDefs
in ./app/lib/apollo/index.ts
:
// ./app/lib/apollo/index.ts // ... export const typeDefs = gql` # right here, we outline an enter enter BookInput { title: String writer: String } # right here, we outline our mutations sort Mutation { addBook(e-book: BookInput): [Book]! } `;
Right here, we outlined an enter
sort: BookInput
with title
and writer
. They’re each strings.
We additionally outlined an addBook
mutation which accepts an argument, e-book
, of the enter sort we created earlier, BookInput
.
The addBook
mutation then returns an inventory of books known as [Book]
Subsequent, we outline our Mutation
resolver:
// ./app/lib/apollo/index.ts // ... export const resolvers = { Question: { // ... }, Mutation: { addBook: (mum or dad: any, { e-book }: any) => { console.log({ e-book }); let books = write(e-book); return books; }, }, }; // ...
Right here, we create a brand new addBook
resolver that takes in e-book
as an argument and passes it to the write()
perform. This provides the brand new e-book to the listing and returns the up to date listing of books.
Create a brand new /books/addbook
route. That is going to be a nested route, which signifies that we’ll need to create a books
listing like ./app/routes/books/addbook.tsx
:
// ./app/routes/books/addbook.tsx import { gql } from "@apollo/shopper"; import { ActionFunction, json } from "@remix-run/node"; import { Kind } from "@remix-run/react"; import { graphQLClient } from "~/lib/apollo"; // motion perform export const motion: ActionFunction = async ({ request }) => { const formData = await request.formData(); const title = formData.get("title"); const writer = formData.get("writer"); let e-book = { title, writer, }; // mutation so as to add e-book const mutation = gql` mutation ($e-book: BookInput) { addBook(e-book: $e-book) { title } } `; const { information } = await graphQLClient.mutate({ mutation, variables: { e-book }, }); return json({ books: information.books }); }; export default perform AddBook() { return ( <part model={{ border: "1px stable #333", padding: "1rem" }}> <h2>Add new e-book</h2> <Kind technique="put up"> <div className="form-control"> <label htmlFor="title">Title</label> <enter id="title" identify="title" sort="textual content" /> </div> <div className="form-control"> <label htmlFor="writer">Writer</label> <enter id="writer" identify="writer" sort="textual content" /> </div> <button sort="submit">Submit</button> </Kind> </part> ); }
Right here, we are able to see that we’ve got an motion
perform the place we get the title
and writer
from the request
.
Then, we create a mutation
and go the e-book
object containing the title
and writer
as a variable. After that, we execute this question utilizing graphQLClient.mutate
.
In our AddBook
part, we use the Kind
part offered by Remix to ship our information with technique = "put up"
.
Now, so as to render our nested route, we’ve got so as to add <Outlet/>
in ./app/routes/books.tsx
:
// ... export default perform Books() { const { books } = useLoaderData(); return ( <primary> <part> {/* ... */} </part> <Outlet /> </primary> ); }
Now, after we go to http://localhost:3000/books/addbook
we must always see this:
Good!
Conclusion
Thus far, we’ve been capable of set Remix with Apollo GraphQL to make requests in opposition to our GraphQL schema.
We will now make queries and mutations and all of it occurs server-side, so there aren’t any pointless community calls on our shopper.
That is simply the tip of the iceberg of what we are able to obtain with Remix and GraphQL. We will additionally create a useful resource route in Remix that we are able to use to offer a GraphQL endpoint utilizing Apollo Server. It will permit us to make requests in opposition to and in addition present a GraphQL playground.
You possibly can learn extra about useful resource routes within the Remix docs and in addition about Apollo Server within the Apollo documentation.
Monitor failed and gradual GraphQL requests in manufacturing
Whereas GraphQL has some options for debugging requests and responses, ensuring GraphQL reliably serves assets to your manufacturing app is the place issues get harder. If you happen to’re considering guaranteeing community requests to the backend or third social gathering providers are profitable, strive LogRocket.https://logrocket.com/signup/
LogRocket is sort of a DVR for internet and cell apps, recording actually every little thing that occurs in your website. As an alternative of guessing why issues occur, you may combination and report on problematic GraphQL requests to rapidly perceive the basis trigger. As well as, you may monitor Apollo shopper state and examine GraphQL queries’ key-value pairs.
LogRocket devices your app to file baseline efficiency timings reminiscent of web page load time, time to first byte, gradual community requests, and in addition logs Redux, NgRx, and Vuex actions/state. Begin monitoring totally free.