Home

Awesome

Overview

graphql-codegen-hasura is a collection of code generator plugins for graphql-code-generator. These plugins are designed to automate coding tasks around the development of a strongly typed client for a Hasura backend. The majority of the code generated is strongly-typed wrappers for Apollo GraphQL, in addition to a number of convenience features, including:

Status: *BETA*

Important Note: Despite the version number, this project is at "Beta" status, with an evolving codebase. However, any breaking changes will be released with new minor or major version bumps in order to minimize disruption to any consumers, and detailed in Release Notes

Approach

These plugins follow a Fragment First approach to Hasura client code generation:

  1. The Developer defines one or multiple gql Fragments in source code
  2. The plugins scans the target source code for gql Fragments
  3. The plugins generate CRUD and config gql and TypeScript code based on the located fragments

Example

You Define A Fragment Like So:

export const DogFragmentDoc = gql`
  fragment Dog on dogs {
    breed
    id
  }
`;

The graphql-codegen-hasura plugins can then generate a 'GQLHooks' and 'GQLHelper' object which you can then use to make CRUD calls to your API. All the generated methods are strongly typed counterparts for methods on the ApolloGraphQL client (with some augmentations).

GQLHelper Generated Methods

// GQLHelper Object - Available Methods

// Query
GQLHelper.Fragments.Dog.queryById(/* params */)               // Query By id
GQLHelper.Fragments.Dog.queryObjects(/* params */)            // Query

// Subscribe
GQLHelper.Fragments.Dog.subscribeToById(/* params */)         // Subscribe by id
GQLHelper.Fragments.Dog.subscribeToObjects(/* params */)      // Subscribe

// Insert
GQLHelper.Fragments.Dog.insert(/* params */)                  // Insert single entity
GQLHelper.Fragments.Dog.insertWithOnConflict(/* params */)    // Insert with strongly types onconflict options
GQLHelper.Fragments.Dog.insertObjects(/* params */)           // Insert one or multiple entities

// Update
GQLHelper.Fragments.Dog.updateById(/* params */)              // Update by id
GQLHelper.Fragments.Dog.updateObjects(/* params */)           // Update one or multiple entities

// Delete
GQLHelper.Models.Dog.removeById(/* params */)                 // Delete entity by id
GQLHelper.Models.Dog.removeObjects(/* params */)              // Delete one or multiple entities

// Watch
GQLHelper.Fragments.Dog.watchQueryById(/* params */)          // Subscribe to observable by id
GQLHelper.Fragments.Dog.watchQueryObjects(/* params */)       // Subscribe to observable

// Cache Read/Write
GQLHelper.Fragments.Dog.cacheReadFragmentById(/* params */)   // Read fragment from cache
GQLHelper.Fragments.Dog.cacheReadQueryById(/* params */)      // Read query from cache
GQLHelper.Fragments.Dog.cacheWriteFragmentById(/* params */)  // Write fragment to cache
GQLHelper.Fragments.Dog.cacheWriteQueryById(/* params */)     // Write query to cache
GQLHelper.Fragments.Dog.cacheWriteQueryObjects(/* params */)  // Write query to cache
GQLHelper.Fragments.Dog.cacheWriteQueryInsert(/* params */)   // Write 'InsertInput' to cache

GQLHelper Usage Example

// QueryById
GQLHelper.Fragments.Dog.queryById({ apolloClient, dogsId });

// Query Collection
GQLHelper.Fragments.Dog.queryByObjects({ apolloClient, options: { variables: { where: { breed: { _eq: 'scottie' } } } });

// Insert Mutation
GQLHelper.Fragments.Dog.insert({ apolloClient, dog: newDog);

// ... ETC. All the methods follow a the same pattern, and all allow the standard ApolloClient options objects to be populated and passed through.

GQLHooks Generated Methods

// GQLHooks Object - Available Methods

// Query
GQLHooks.Fragments.Dog.useQueryById(/* params */)            // Hook to query by id
GQLHooks.Fragments.Dog.useQueryByIdLazy(/* params */)        // Hook to query by id - lazy execution
GQLHooks.Fragments.Dog.useQueryObjects(/* params */)         // Hook to query one or multiple entities
GQLHooks.Fragments.Dog.useQueryObjectsLazy(/* params */)     // Hook to query one or multiple entities - lazy execution

// Subscribe
GQLHooks.Fragments.Dog.useSubscribeToById(/* params */)      // Hook to subscribe by id
GQLHooks.Fragments.Dog.useSubscribeToObjects(/* params */)   // Hook to subscribe to one or multiple entities

// Insert
GQLHooks.Fragments.Dog.useInsert(/* params */)               // Hook to insert single entity
GQLHooks.Fragments.Dog.useInsertWithOnConflict(/* params */) // Hook to insert with strongly types onconflict options
GQLHooks.Fragments.Dog.useInsertObjects(/* params */)        // Hook to insert one or multiple entities

// Update
GQLHooks.Fragments.Dog.useUpdateById(/* params */)           // Hook to update by id
GQLHooks.Fragments.Dog.useUpdateObjects(/* params */)        // Hook to update one or multiple entities

// Delete
GQLHooks.Models.Dog.useRemoveById(/* params */)              // Hook to delete entity by id
GQLHooks.Models.Dog.useRemoveObjects(/* params */)           // Hook to delete one or multiple entities

GQLHooks Usage Example

// QueryById
{ dog, loading, errors } = await GQLHooks.Fragments.Dog.queryById({ apolloClient, dogsId });

// Query Collection
{ objects, loading, errors } = await GQLHooks.Fragments.Dog.useQueryByObjects({ apolloClient, options: { variables: { where: { breed: { _eq: 'scottie' } } } });

// Insert Mutation
const [addDog] = GQLHooks.Fragments.Dog.insert({ apolloClient, dog: newDog);
addDog({ type:'scottie' });


// ... ETC. All the methods follow a the same pattern, and all allow the standard ApolloClient options objects to be populated and passed through.

Notes on 'WriteQuery' methods

All client/cache WriteQuery and WriteFragment methods accept Hasura <TypeName>_Insert_Input object graphs as well as the expected Fragment graphs.

Some usage notes:

fieldMap Parameter
This parameter is options. It allows typenames to be specified for child relationships (and automatically added during the conversion). It also allows for specific fields to be ignored. This can be automated in the future, but is non-trivial dev. So, for future version or community pull request.

Client Fields
Client (@client) fields can be added via in <TypeName>_Insert_Input object, by prefixing them with a triple underscore (___). This is converted to the fieldname (without prefix) for cache adds. Client fields are removed completely for api inserts (useInsert* and insert* methods). This is kind of hacky, and there is probably a much better way to achieve this. #futureDev

<TypeName>_Insert_Input Types
All of the client* and cache* methods accept both Fragment and <TypeName>_Insert_Input graphs. However, when adding <TypeName>_Insert_Input graphs to child properties of an object (for example - adding a new object to a child array field), the <TypeName>_Insert_Input will need to be cast to the fragment type. However, it will still be converted appropriately at runtime.

Example Usage
See packages/graphql-codegen-hasura-core/src/utils.test.ts for example usage

Further Information

Apollo Version

Please note, this library currently only supports version 3 of the React Apollo client

Quick Start

To quickly view the type of code that gets generated, view the autogen code files in the demo project.

To quickly try out the code-generation:

  1. Checkout the project to your local computer
  2. Navigate to the demo directory
  3. Run the following commands
  1. lerna bootstrap
  2. npm run build-and-generate

The Plugins

These plugins require and augment the existing fantastic GraphQL code generator plugins available from graphql-code-generator

Structure

This is a mono-repo project that contains multiple npm packages (under the packages folder), in addition to a demo project (under the demo folder).

Usage

The easiest way to quickly get going is to view the following files in the demo project as a starting point:

It is important to note: The TypeScript Generation leverages the files created in the GQL generation step. As such, it is important to run the GQL generation step prior to the TypeScript generation step.

Installation

  1. Start with an existing TypeScript React app, or create one using:
    create-react-app my-project --typescript
  1. Add the package dependencies required by graphql-codegen-hasura:
    npm install --save-dev @graphql-codegen/cli @graphql-codegen/introspection @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo
  1. Add the graphql-codegen-hasura packages:
    npm install graphql-codegen-hasura-core;
    npm install --save-dev graphql-codegen-hasura-gql graphql-codegen-hasura-typescript graphql-codegen-hasura-react graphql-codegen-hasura-client-config graphql-codegen-hasura-shared
  1. Create configuration YAML files (see below)

  2. Run the codegen commands in a command console

Note: node v12 or greater is required.

graphql-codegen --config=graphql-codegen-gql.yaml;
graphql-codegen --config=graphql-codegen-typescript.yaml

Local Installation

If you would like to run the graphql-codegen-hasura packages locally from the demo project (in src/demo), you can run the following command:

  npm run bootstrap

This will use Lerna to Link local packages together and install remaining package dependencies. See Lerna documentation for further details.

Configuration

See graphql-code-generator documentation for configuration instructions. See below for the specific configuration flags that are available on these plugins

graphql-codegen-hasura-gql plugin

graphql-codegen-hasura-typescript plugin

graphql-codegen-hasura-react plugin

graphql-codegen-hasura-client-config plugin

Plugin Details

graphql-codegen-hasura-gql plugin

Overview

Generates CRUD gql mutations and queries for every Fragment defined in the targeted (code) documents.

See demo/src/autogen/hasura/gql.ts for generated output files.

graphql-codegen-hasura-typescript plugin

Overview

Generates CRUD TypeScript helper methods for every Fragment defined in the targeted (code) documents. Provides wrapped client.query & client.mutate calls, in addition to adding some convenience features.

See demo/src/autogen/hasura/ts.ts for generated output files.

graphql-codegen-hasura-react plugin

Overview

Generates CRUD React Hooks for every Fragment defined in the targeted (code) documents.

See demo/src/autogen/hasura/ts-react.ts for generated output files.

graphql-codegen-hasura-client-config plugin

Overview

Generates TypeScript Type Policies and Resolver Types for tables related to GQL fragments found in the targeted documents (code).

See demo/src/autogen/hasura/ts-config.ts for generated output files.

Naming Conventions

A basic naming convention being followed:

Demo

The demo project is simply:

Motivation

Hasura is a fantastic tool for the rapid development of a GraphQL backend. Apollo GraphQL is a suite of GraphQL tooling, including a great Client. graphql-code-generator is a very helpful code generation tool and library, to automate the generation of much of the boilerplate code required to use GraphQL. However, there still remained quite a bit of boiler plate code to be written.

The consistency and predictability of the Hasura GQL backend implementation, provides an opportunity to automate much of the remaining code for standard single-table mutations and queries, and multi-table helper code. These plugins are designed to provide this.

Related/Companion Libraries

TBD

Disclaimers & Known Issues

This code was initially developed for use in a single separate commercial project. It is being shared in case useful to others, and as a contribution to the development community and the great GraphQL tools that already exist. The original implementation did just enough to meet the goals and needs of the initial project.

Known Issues Include:

Refinements and Enhancements Needed

There are many refinements and enhancements that would be beneficial, and contributions to that end are encouraged. Notable examples include:

  1. Remove requirement for primary key field to be named id*
  2. Support tables with multiple primary keys
  3. Automatically generate fieldMap property for clientWriteInsertQuery method (see packages/graphql-codegen-hasura-core/src/utils.test.ts for examples of this method)
  4. Improve client field handling mechanism for *WriteQueryInsert methods
  5. Add more automated tests
  6. Add validation (especially for checking for package prerequisites). See these docs
  7. Rewrite the plugins to use the graphql-code-generator recommended Visitor pattern**

Help Wanted

Notes

Companion Libraries

This library is part of a collection of companion tools and libraries under the AbleStack umbrella. All of these libraries share the common goal:

Helping small teams and solo-developers build big ideas rapidly and affordably

To achieve these goals, the following principles are applied:

Release Notes

Notes 4.9.1

Notes 4.9.0