Remix-graphql - Utilities for using GraphQL with a Remix app

Overview

remix-graphql

Remix and GraphQL can live together in harmony ❤️ This package contains basic utility functions that can help you with that.

To be more speciic, here's what the latest version of remix-graphql can help you with:

  • Handling loader and action requests using GraphQL queries and mutations
  • Setting up a GraphQL API as a resource route

And here are some cool ideas what it might do as well in the future:

  • Executing operations against a resource route in the same project or against a remote GraphQL API
  • Batching queries from multiple loaders into a single API request

Contents

Installing

You can install remix-graphql with your preferred package manager. It depends on the graphql package, so make sure to also have that installed.

# Using `npm`
npm install graphql remix-graphql
# Or using `yarn`
yarn add graphql remix-graphql

It also lists some of the Remix-packages as peer dependencies. (If you used the Remix CLI to setup your project, you most likely have them installed already.) If you get unexpected errors, double check that the following are installed:

  • @remix-run/dev
  • @remix-run/react
  • @remix-run/serve
  • remix

Defining your schema

remix-graphql keeps it simple and let's you decide on the best way to define your GraphQL schema. In all places where you need to "pass your schema to remix-graphql", the respective function expects a GraphQLSchema object.

That means all of the following approached work to define a schema:

  • Using the GraphQLSchema class from the graphql package (obviously...)
  • Defining the schema using the SDL, defining resolver functions in an object and merging both with makeExecutableSchema (from @graphql-tools/schema)
  • Using nexus and makeSchema

We recommend exporting the schema from a file, e.g. app/graphql/schema.server.ts. By using the .server.ts extension you make sure that none of this code will end up being shipped to the browser. (This is a hint to the Remix compiler that it should ignore this module when building the browser bundle.)

Handle loader and action requests with GraphQL

Both loaders and actions are just simple functions that return a Response given a Request. With remix-graphql you can use GraphQL to process this request! Here's a complete and working example of how it works:

// app/routes/index.tsx
import type { GraphQLError } from "graphql";
import { Form } from "remix";
import type { ActionFunction, LoaderFunction } from "remix";
import { processRequestWithGraphQL } from "remix-graphql/index.server";

// Import your schema from whereever you export it
import { schema } from "~/graphql/schema";

const ALL_POSTS_QUERY = /* GraphQL */ `
  query Posts($limit: Int) {
    posts(limit: $limit) {
      id
      title
      likes
      author {
        name
      }
    }
  }
`;

export const loader: LoaderFunction = (args) =>
  processRequestWithGraphQL({
    // Pass on the arguments that Remix passes to a loader function.
    args,
    // Provide your schema.
    schema,
    // Provide a GraphQL operation that should be executed. This can also be a
    // mutation, it is named `query` to align with the common naming when
    // sending GraphQL requests over HTTP.
    query: ALL_POSTS_QUERY,
    // Optionally provide variables that should be used for executing the
    // operation. If this is not passed, `remix-graphql` will derive variables
    // from...
    // - ...the route params.
    // - ...the submitted `formData` (if it exists).
    variables: { limit: 10 },
    // Optionally pass an object with properties that should be included in the
    // execution context.
    context: {},
    // Optionally pass a function to derive a custom HTTP status code for a
    // successfully executed operation.
    deriveStatusCode(
      // The result of the execution.
      executionResult: ExecutionResult,
      // The status code that would be returned by default, i.e. of the
      // `deriveStatusCode` function is not passed.
      defaultStatusCode: number
    ) {
      return defaultStatusCode;
    },
  });

const LIKE_POST_MUTATION = /* GraphQL */ `
  mutation LikePost($id: ID!) {
    likePost(id: $id) {
      id
      likes
    }
  }
`;

// The `processRequestWithGraphQL` function can be used for both loaders and
// actions!
export const action: ActionFunction = (args) =>
  processRequestWithGraphQL({ args, schema, query: LIKE_POST_MUTATION });

export default function IndexRoute() {
  const { data } = useLoaderData();
  if (!data) {
    return "Ooops, something went wrong :(";
  }

  return (
    <main>
      <h1>Blog Posts</h1>
      <ul>
        {data.posts.map((post) => (
          <li key={post.id}>
            {post.title} (by {post.author.name})
            <br />
            {post.likes} Likes
            <Form method="post">
              {/* `remix-graphql` will automatically transform all posted 
                  form data into variables of the same name for the GraphQL
                  operation */}
              <input hidden name="id" value={post.id} />
              <button type="submit">Like</button>
            </Form>
          </li>
        ))}
      </ul>
    </main>
  );
}

type LoaderData = {
  data?: {
    posts: {
      id: string;
      title: string;
      likes: number;
      author: { name: string };
    }[];
  };
  errors?: GraphQLError[];
};

Automated type generation

Hidden at the end of the example above you see that the data returned from the loader function had to be typed by hand. Since GraphQL is strongly typed, you can automate this if you want to!

First, you need to generate the introspection data as JSON from your schema and store it in a local file. For that you can create a simple script like this:

// app/graphql/introspection.{js,ts}
import fs from "fs";
import { introspectionFromSchema } from "graphql";
import path from "path";
import { schema } from "./schema";

fs.writeFileSync(
  path.join(__dirname, "introspection.json"),
  JSON.stringify(introspectionFromSchema(schema))
);

Usually you don't want to commit the generated JSON file to version control, so we recommend to add it to your .gitignore file.

To make running this script easier, create a simple NPM script for it in your package.json:

{
  "scripts": {
    // If you created the script with JavaScript
    "introspection": "node app/graphql/introspection.js",
    // If you created the script with TypeScript (make sure to install
    // `esbuild-register` as dev-dependency in this case)
    "introspection": "node --require esbuild-register app/graphql/introspection.ts"
  }
}

To actually generate types from your queries and mutations we recommend using GraphQL Code Generator. For that you need to install a couple of dependencies:

# Using `npm`
npm install --save-dev @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations
# Or using `yarn`
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations

Almost there! Now create a config file named codegen.yml in the root of your project that contains the following:

overwrite: true
# The path where the previously generated introspection data is stored
schema: "app/graphql/introspection.json"
# A glob that matches all files that contain operation definitions
documents: "app/routes/**/*.{ts,tsx}"
generates:
  # This is the path where the generated types will be stored
  app/graphql/types.ts:
    plugins:
      - "typescript"
      - "typescript-operations"
    config:
      skipTypename: true

Now you can finally generate the types! For convenience, add another NPM script:

{
  "scripts": {
    "introspection": "node --require esbuild-register app/graphql/introspection.ts",
    "codegen": "npm run introspection && graphql-codegen --config codegen.yml"
  }
}

Running npm run codegen (or yarn codegen) will now automatically create types for the returned data for all queries and mutations. (Side-note: It's also a great way to validate if all your operations are valid against your schema!)

One more thing: Noticed the /* GraphQL */ comment we included before the strings that contain queries and mutations in the example above? This is important! It's a hint to @graphql-codegen that this string should be parsed as GraphQL. Without it you won't get any types for the operation defined within the string.

The example above could now be modified like this:

// Add this import...
import type { PostsQuery } from "~/graphql/types";

// ...and change the `LoaderData` type like this:
type LoaderData = { data?: PostsQuery; errors?: GraphQLError[] };

Set up a GraphQL API in a Remix app

You can create a dedicated endpoint for your GraphQL API using resource routes in Remix. All you need to do is create a route (e.g. app/routes/graphql.ts) and paste the following code. By using both a loader and an action your endpoint supports both GET and POST requests!

// app/routes/graphql.ts
import {
  createActionFunction,
  createLoaderFunction,
} from "remix-graphql/index.server";
import type { DeriveStatusCodeFunction } from "remix-graphql/index.server";

// Import your schema from whereever you export it
import { schema } from "~/graphql/schema";

// Handles GET requests
export const loader = createLoaderFunction({
  // Provide your schema.
  schema,
  // Optionally pass an object with properties that should be included in the
  // execution context.
  context: {},
  // Optionally pass a function to derive a custom HTTP status code for a
  // successfully executed operation.
  deriveStatusCode,
});

// Handles POST requests
export const action = createActionFunction({
  // Provide your schema.
  schema,
  // Optionally pass an object with properties that should be included in the
  // execution context.
  context: {},
  // Optionally pass a function to derive a custom HTTP status code for a
  // successfully executed operation.
  deriveStatusCode,
});

// This function equals the default behaviour.
const deriveStatusCode: DeriveStatusCodeFunction = (
  // The result of the execution.
  executionResult,
  // The status code that would be returned by default, i.e. of the
  // `deriveStatusCode` function is not passed.
  defaultStatusCode
) => defaultStatusCode;

Context

When defining a schema and writing resolvers, it's common to provide a context- object. All functions exported by remix-graphql accept an optional property context in the arguments object. When passed, it must be an object. All of its properties will be included in the context object passed to your resolvers.

remix-graphql also exports a Context type that contains all properties that are added to this context objects for execution. This type accepts an optional generic by which you can add any custom properties to your context object.

import type { PrismaClient } from "@prisma/client";
import type { Context } from "remix-graphql/index.server";

type ContextWithDatabase = Context<{ db: PrismaClient }>;

The following subsections highlight all properties that are added to the context object by remix-graphql.

request

This is the Request object that is passed to a loader- or action-function in Remix. It will always be part of the context object.

redirect

When handling loaders or actions in UI routes, a common pattern in Remix is redirection. (Remix even provides a redirect utility function that can be returned from any loader- or action-function.) In remix-graphql you can achieve this by using the redirect function that is provided in the context object.

This function has the following signature:

function redirect(
  // The URL for redirection
  url: string,
  // Optionally header values to include in the HTTP response
  headers?: HeadersInit
): void;

Note that this function is only part of the context object when handling GraphQL requests in UI routes, i.e. when using processRequestWithGraphQL. It is NOT part of the context object when handling GraphQL requests in a resource route, i.e. when using createActionFunction or createLoaderFunction.

Releases(v0.1.2)
  • v0.1.2(Jan 10, 2022)

    New features

    • The new function processRequestWithGraphQL allows you to handle loader and action requests using GraphQL with ease.
    • All exported functions now allow passing a context object which will be included in the execution context.

    Other improvements

    • There is a new section in the README about how to automatically generate types for the result of GraphQL operations
    • The whole jokes-tutorial example was rewritten to include a GraphQL API that handles authentication and the loading, creation, and deletion of jokes, and it now uses processRequestWithGraphQL from remix-graphql in all loader and action functions.
    Source code(tar.gz)
    Source code(zip)
  • v0.1.0(Jan 10, 2022)

    Breaking changes

    • Avoid breaking the browser bundle by importing from remix-graphql/index.server instead of remix-graphql

    Migration guide

    Update all imports from remix-graphql like this:

    - import { ... } from "remix-graphql";
    + import { ... } from "remix-graphql/index.server";
    
    Source code(tar.gz)
    Source code(zip)
  • v0.0.1(Jan 10, 2022)

    Initial release 🥳

    New features

    • Added function createLoaderFunction that handles GraphQL requests for GET requests to a resource route
    • Added function createActionFunction that handles GraphQL requests for POST requests to a resource route
    Source code(tar.gz)
    Source code(zip)
Owner
Thomas Heyenbrock
Thomas Heyenbrock
Project Remix Blog - A Simple blog built with Remix (A Full Stack React Framework) and Prisma DB

Project Remix Blog - A Simple blog built with Remix (A Full Stack React Framework) and Prisma DB

Devanshu Vashishtha 1 Jan 2, 2022
Remix Fundamentals: Get a jumpstart on Remix with this workshop

?? Remix Fundamentals Build Better websites with Remix Remix enables you to buil

Kent C. Dodds 87 Jan 10, 2022
Remix-cloudflare-prisma - An example of prisma working on cloudflare pages with Remix

Remix Cloudflare Prisma Example Developed by Jacob Paris Remix Docs Cloudflare P

Jacob Paris 11 Jan 18, 2022
An app to manage colors, color boards and patterns, built with Remix

Welcome to moodboardr! This is an app to manage colors, color boards and patterns. It is build using Remix as a framework for making React do its thin

Bernhard Häussner 3 Dec 15, 2021
An hexagonal architecture approach for a Remix Todo List Manager app

Todo List Manager This project showcases an hexagonal architecture approach for front-end projects. This is a Remix application, you may want to check

Antoine Chalifour 10 Jan 14, 2022
React Test Shop Using Remix

React Test Shop Using Remix Development Build React Test Shop makes API calls to an external server. First set up this server by cloning the React Tes

Naresh Bhatia 25 Dec 9, 2021
M Haidar Hanif's personal website with React, Remix, Stitches, Radix UI, and deployed to Vercel.

mhaidarhanif-web M Haidar Hanif's personal website with React+Remix. Deployed to Vercel. Current repo is at mhaidarhanif-web on GitHub. Variants The w

M Haidar Hanif 6 Jan 20, 2022
Email Link Strategy With Remix Auth

Email Link Strategy - Remix Auth This strategy is heavily based on kcd strategy

Bhanu Teja Pachipulusu 10 Jan 10, 2022
A collection of social media strategies for remix-auth

Remix Auth Socials A collection of Remix Auth strategies for Oauth2 Social login

Tom Rowe 7 Jan 20, 2022
Blog starter for Remix with React Bricks

React Bricks Remix Blog starter with Tailwind CSS and React Bricks UI Kick-start your project with this boilerplate for a complete Remix blog based on

React Bricks 1 Jan 14, 2022
Sockets-remix-thread-prototype - Prototype of real-time comments and a proposal of how to make it production-ready

Real-time comments prototype Simple demonstration of real-time commenting. Insta

Tiger Abrodi 3 Jan 16, 2022
A chat app made with React and GraphQL

A chat app made with React and GraphQL

Abdou Ouahib 28 Dec 31, 2021
Built with Expo, React Native, and GraphQL, Lexicon is a pre-built mobile discussions app that you can customize for your users.

Lexicon is a customizable, open source mobile app that provides an elegant mobile discussions experience. Built on top of Discourse, a platform for communities.

null 124 Jan 17, 2022
Hacker News clone rewritten with universal JavaScript, using React and GraphQL.

Hacker News Clone React/GraphQL This project is a clone of hacker news rewritten with universal JavaScript, using React and GraphQL. It is intended to

Clinton D'Annolfo 4.2k Jan 18, 2022
A Netflix Clone built using React GraphQL and DataStax, exploring Pagination and Slicing

?? Netflix Clone using DataStax and GraphQL This project was bootstrapped with Create React App. Table of content Prerequisites Create Astra Instance

Ania Kubow 48 Jan 1, 2022
Pinterest Clone Built Using ReactJS And GraphQL

Pinterest Clone About The Project There are many good ideas of projects used with the main purpose of learning a technology. A good side project is al

Pedro Henrique Machado 5 Nov 15, 2021
A simple ReactJS Netflix homepage Clone using Astra DB and GraphQL

?? Netflix Clone using Astra DB and GraphQL 50 minutes, Intermediate, Start Buil

DataStax Developers 554 Jan 12, 2022
FullStack app containing an API (NestJS), a web app (React) and a mobile app (React Native).

Food Order This project is a workspace containing an API (NestJS), a web app (React) and a mobile app (React Native). Content Food Order Content Get S

Germain Michaud 1 Nov 11, 2021
A calculator app built with React.js using React Hooks and Routers. The app contains 3 pages with basic styling. Calculator operations, as well as React components, were tested with Jest and React Testing Library.

Raect Calculator A calculator app built with React.js using React Hooks and Routers. App contains 3 pages with basic styling. Calculator operations, a

SarvarKhalimov 7 Sep 19, 2021