GraphQL Clients

In my last post (1), we set up a GraphQL server with Hot Chocolate; in those post, I will show how we can call this server from various clients. First, we make calls from a C# app with Strawberry Shake, a client provided by the Hot Chocolate team; then we will make calls from a React web app with the two popular GraphQL clients Apollo and Relay. When using these clients it is important to remember that they are just making HTTPS (or whatever other transport you decide to use) calls behind the scene; these clients are simply wrappers that provide extra tooling to make your life easier. The server we are building against can be found on GitHub (2).

Strawberry Shake

Writing a Strawberry Shake client against .NET 5+ is very easy; a completed demo can be found at (3). First, we need to install the Strawberry Shake dotnet tools by running dotnet tool install StrawberryShake.Tools --local on the command line. Now we will create our client with dotnet graphql init https://localhost:44377/graphql/ -n LibraryClient -p ./Client Add a namespace property in the created .graphqlrc.json file under the extensions:strawberryShake alongside the url property; this is the namespace the generated client will be placed under. See (4) for more options.

Next, we need to define some queries and mutations for our app to run; place these in various .graphql files inside the newly-created Client folder. When the project is built, these files will be found and compiled into your library using source generators; there will also be a number of files created under a Generated folder to support your editor experience.

fragment BooksPage on AllBooksConnection {
  nodes {
    id
    isbn
    name
  }
}

query App {
  allBooks {
    ...BooksPage
  }
}

mutation CreateUser($username: String!) {
  createUser(name: $username) {
    id name
  }
}

Now that we have our client, we can tie into it through DI. Here is an example of using it with a console app; if it were an ASP.NET Core or Blazor website, you would move the IoC configuration to the ConfigureServices method and inject the ILibraryClient wherever you needed to use it.

static async Task Main(string[] args)
{
    var serviceCollection = new ServiceCollection();

    serviceCollection.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://localhost:44377") });

    serviceCollection
        .AddLibraryClient()
        .ConfigureHttpClient(client => client.BaseAddress = new Uri("https://localhost:44377/graphql"));

    var serviceProvider = serviceCollection.BuildServiceProvider();
    var client = serviceProvider.GetRequiredService<ILibraryClient>();

    var result = await client.App.ExecuteAsync();
    var data = result.Data;

    data.AllBooks.Nodes[0].MockedField = "test";
    var mockedData = data.AllBooks.Nodes[0].MockedField;

    var createUserResult = await client.CreateUser.ExecuteAsync("abcd");
    var createdUser = createUserResult.Data;
}

Testing

Because Strawberry Shake exposes a partial interface for all generated objects, we can easily mock our client wherever we inject it for use in tests; here is an example mock of our AppQuery call.

[Test]
public void MockAppQuery()
{
    var mockResult = new Mock<IOperationResult<IAppResult>>();
    mockResult.Setup(s => s.Data).Returns(new AppResult(
        new App_AllBooks_AllBooksConnection(new List<App_AllBooks_Nodes_Book>
        {
            new App_AllBooks_Nodes_Book(Guid.NewGuid(), "978-1617294532", "C# In Depth, Fourth Edition")
        })
    ));

    var mockAppQuery = new Mock<IAppQuery>();
    mockAppQuery.Setup(s => s.ExecuteAsync(It.IsAny<CancellationToken>()))
        .ReturnsAsync(mockResult.Object);

    var mockClient = new Mock<ILibraryClient>();
    mockClient.Setup(s => s.App).Returns(mockAppQuery.Object);

    // todo: act

    // todo: assert
}

When to Use

Use this client if you are calling a GraphQL API from a C# client; this could be if your backend calls Shopify, GitHub, or other GraphQL server, or if you are developing a Blazor website or WPF, Xamarin, or .NET Maui app.

Apollo

Apollo is the most popular GraphQL client for JS-based apps because of its ease of use; to achieve this, however, it does not enforce some of the ideals of GraphQL. These are discussed in more detail in the When to Use section. A completed demo based on the npx create-react-app library --template typescript template can be found at (5). To use this client, first, we need to install the latest @apollo/client and graphql packages with your package manager of choice; I use npm, since I am most familiar with it: npm i @apollo/client graphql Next, we need to create an ApolloClient instance; this client will tell our app where the graphql server lives and how to talk to it, and how to cache data. Put this in src/client.ts (This step, and many of the following steps, are demonstrated in the Apollo get-started docs at (6).)

import {
  ApolloClient,
  ApolloLink,
  HttpLink,
  InMemoryCache,
} from '@apollo/client'
import env from './env'

const httpLink = new HttpLink({
  uri: env.GraphQLEndpoint,
})

export const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: ApolloLink.from([httpLink]),
})

Example environment variable configuration, which I like to place at src/env.ts:

const env = {
  GraphQLEndpoint: process.env.GRAPHQL_ENDPOINT || 'https://localhost:44377/graphql/'
}

export default env

Next, we will configure our GraphQL codegen system. Because we are using TypeScript and React, we would like to have our queries and mutations strongly typed and use hooks to perform our calls. While Apollo has a codegen system you can import, it has many bugs and I have not been able to get it working satifactorily; I prefer the @graphql-codegen library. To use this library, we will install our packages with npm i --save-dev @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-react-apollo @graphql-codegen/typescript-operations Next, we will copy our schema, which can typically be found by using the server’s provided introspection, to data/schema.graphql and create a codegen.yml file at the root of the project:

schema: ./data/schema.graphql
documents: 'src/**/*.tsx'
generates:
  src/types-and-hooks.ts:
    plugins:
      - typescript
      - typescript-operations
      - typescript-react-apollo

Note that we tell it where our schema is (this could also be a URL pointing to a live server, which is useful when the schema is in active development), which documents to scan for scripts (you can also point it to .graphql files, if you would rather not have your queries and mutations in your .tsx files), where to put the generated code, and which plugins to use when generating it. Once we add "graphql:codegen": "graphql-codegen" to the scripts section of the package.json and run it, we will be able to write queries and use them with hooks in our React components. The best part about this library is if we were using Angular instead, for example, we could have used the typescript-apollo-angular plugin and nothing else would change around integrating with GraphQL other than the final usage (e.g. we would access it with dependency injection instead of hooks). Additional configuration options can be found at (7).

The next couple paragraphs are mostly React-focused; Apollo is not limited to working with React, so you can ignore the React-specific pieces if you are using a different frontend library.

Now we configure our ApolloProvider with an instance of our client in our App file. Now everything is set up, and we can use the generated hooks in our React components.

App.tsx
import { ApolloProvider, gql } from '@apollo/client'
import { useAppQuery } from './types-and-hooks'
import { client } from './client'
import BooksPage from './BooksPage'
import CreateUser from './CreateUser'

gql`
  query App {
    allBooks {
      ...BooksPage
    }
  }
`

// exported so we can access it for testing
export function App() {
  const { data, loading, error } = useAppQuery()

  if (error) {
    return <div>Error!</div>
  }

  if (loading) {
    return <div>Loading...</div>
  }

  return (
    <div className="App container">
      <CreateUser />
      <BooksPage data={data?.allBooks} />
    </div>
  )
}

function AppRoot() {
  return (
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  )
}

export default AppRoot
BooksPage.tsx
import { gql } from '@apollo/client'
import { BooksPageFragment } from './types-and-hooks'

gql`
  fragment BooksPage on AllBooksConnection {
    nodes {
      id
      isbn
      name
    }
  }
`

interface Props {
  query: BooksPageFragment | null
}

function BooksPage({ query }: Props) {
  return (
    <div className="BooksPage">
      {query?.nodes?.map((m) => (
        <div key={m.id}>
          {m.name} - {m.isbn}
        </div>
      ))}
    </div>
  )
}

export default BooksPage
CreateUser.tsx
import { gql } from '@apollo/client'
import { useCreateUserMutation } from './types-and-hooks'

gql`
  mutation CreateUser($username: String!) {
    createUser(name: $username) {
      id
      name
    }
  }
`

function CreateUser() {
  const [command] = useCreateUserMutation()

  const createUser = () =>
    command({
      variables: {
        username: 'asdf',
      },
    })

  return (
    <div className="CreateUser">
      <button style={{ float: 'right' }} onClick={createUser}>
        Create User
      </button>
    </div>
  )
}

export default CreateUser

Note: that the final solution I shared differs slightly so it runs against the final version of the server after we update it to follow Relay conventions (the GraphQL gold standard client) at the end of this file. The way I wrote it here reflects the state of the server at the end of my previous post (1).

Testing

Apollo can be tested very easily. First, we need to create an array of mock responses. Each mock will specify which query the mock is for and what data is returned. We will now pass these mocks to Apollo’s MockedProvider If we do not trigger our UI updates to process with an await act call, it is in the loading state; after that, it either sets the data or error depending what our mock returned. If we were not using fragments, we could set addTypename={false} on the MockedProvider and leave out the __typename fields in our mocks to make things simpler.

import { AppDocument, BooksPageFragment } from './types-and-hooks'
import { App } from './App'
import { MockedProvider, MockedResponse } from '@apollo/client/testing'
import { screen, render, act } from '@testing-library/react'

const mocks: MockedResponse[] = [
  {
    request: {
      query: AppDocument,
    },
    result: {
      data: {
        allBooks: {
          __typename: 'AllBooksConnection',
          nodes: [
            {
              __typename: 'Book',
              id: 1,
              isbn: '978-1617294532',
              name: 'C# In Depth, Fourth Edition',
            },
            {
              __typename: 'Book',
              id: 2,
              isbn: '978-1617295683',
              name: 'GraphQL in Action',
            },
          ] as BooksPageFragment,
        },
      },
    },
  },
]

const errMocks: MockedResponse[] = [
  {
    request: {
      query: AppDocument,
    },
    error: new Error('An error occurred'),
  },
]

it('renders loading state', () => {
  render(
    <MockedProvider mocks={mocks}>
      <App />
    </MockedProvider>,
  )

  const domPiece = screen.getByText('Loading...')
  expect(domPiece).toBeInTheDocument()
})

it('renders book list', async () => {
  render(
    <MockedProvider mocks={mocks}>
      <App />
    </MockedProvider>,
  )

  await act(async () => await new Promise((resolve) => setTimeout(resolve, 0)))

  const domPiece = screen.getByText('C# In Depth, Fourth Edition - 978-1617294532')
  expect(domPiece).toBeInTheDocument()
})

it('renders error state', async () => {
  render(
    <MockedProvider mocks={errMocks}>
      <App />
    </MockedProvider>,
  )

  await act(async () => await new Promise((resolve) => setTimeout(resolve, 0)))

  const domPiece = screen.getByText('Error!')
  expect(domPiece).toBeInTheDocument()
})

Caching

Apollo comes with a built-in cache to help minimize network calls. It populates items that it can build a cache id for when you perform a query, and will update known items with the response from a mutation; it will not insert new items from a mutation’s response, however. By default, cache items are generated using the __typename and id or _id field, but this can be customized by setting the typePolicies For example, if I wanted to use the isbn field instead of the id field as my cache key (the __typename field is always used), I could use this:

const cache = new InMemoryCache({
  typePolicies: {
    Book: {
      keyFields: ["isbn"],
    },
  },
});

An excellent discussion of cache manipulation can be found at (8).

When to Use

I prefer this framework when building a non-React JS frontend, such as an Angular UI, but be careful to not treat each query endpoint as a single call, like a REST API would, and be careful when choosing your cache strategy to keep your UI quick and responsive while still displaying the correct information.

Relay

Relay is a GraphQL client built for React by Facebook. It is a little more confusing to learn than Apollo, partially because it heavily relies on fragments, rather than simply building and making the calls you need directly. Its benefit, however, is that each component declares which fields of which types it needs, and the app makes a single call to the server when it loads or navigates to a new page. A completed demo based on the npx create-react-app library --template typescript template can be found at (9).

First, we need to install the required dependencies with npm i relay-runtime react-relay and npm i --save-dev relay-compiler babel-plugin-relay @types/relay-runtime @types/react-relay Then add a section to the package.json to call the relay-compiler tool and some configuration values so it can generate the code correctly. Other configuration parameters can be found at (10).

"scripts": {
  "relay": "relay-compiler"
},
"relay": {
  "src": "./src",
  "schema": "./data/schema.graphql",
  "language": "typescript"
},

Now that we have Relay installed, we need the schema to the API our app is querying; this can typically be found by using the server’s provided introspection, unless it is not published, in which case you probably are not supposed to be calling the server. Once you have this, place it in your project at data/schema.graphql

Next, we will declare a TypeScript definition so we can use it without compiler errors; I placed this in src/types.d.ts

declare module 'babel-plugin-relay/macro' {
  export { graphql } from 'react-relay'
}

Now we will set up our environment variables at src/env.ts:

const env = {
  GraphQLEndpoint: process.env.GRAPHQL_ENDPOINT || 'https://localhost:44377/graphql/'
}

export default env

Next we have to write a couple tools for Relay to tie into the server with; I put this at src/relay-env.ts

import {
    Environment,
    Network,
    RecordSource,
    RequestParameters,
    Store,
    Variables
  } from 'relay-runtime'
import env from './env'

const url = env.GraphQLEndpoint

function fetchQuery(
  operation: RequestParameters,
  variables: Variables,
) {
  return fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      query: operation.text,
      variables,
    }),
  }).then(response => {
    return response.json();
  });
}

const environment = new Environment({
  network: Network.create(fetchQuery),
  store: new Store(new RecordSource()),
});

export default environment;

Finally, we are ready to write some React components. First, we will write our App function:

import { Environment, QueryRenderer } from 'react-relay'
import defaultEnvironment from './relay-env'
import type {
  App_Query,
  App_Query$data,
} from './__generated__/App_Query.graphql'
import { graphql } from 'babel-plugin-relay/macro'
import BooksPage from './BooksPage'
import CreateUser from './CreateUser'

const query = graphql`
  query App_Query {
    allBooks {
      ...BooksPage_query
    }
  }
`

interface Props {
  error: Error | null
  props: App_Query$data | null
}

export function App({ error, props }: Props) {
  if (error) {
    return <div>Error!</div>
  }

  if (!props) {
    return <div>Loading...</div>
  }

  return (
    <div className="App container">
      <CreateUser />
      <BooksPage query={props.allBooks} />
    </div>
  )
}

export interface AppRootProps {
  environment?: Environment
}

function AppRoot({ environment }: AppRootProps) {
  // note: QueryRenderer<App_Query> is actually correct; it's a generic type that uses a Babel plugin like the graphql`` tags
  return (
    <QueryRenderer<App_Query>
      environment={environment ?? defaultEnvironment}
      query={query}
      render={(renderProps) => <App {...renderProps} />}
      variables={{}}
    />
  )
}

export default AppRoot

Tip: to get this working, first write your graphql tags for this and the below components, run npm run relay to generate the __generated folder, then write the rest of the code. If you try to paste this content as-is, npm run relay will not work because it is referencing missing components.

The query is the root query of the app. Unlike Apollo, which works similar to a REST API where you can make many queries as you go, Relay only has one root query that pulls the data for all child components. Note that we are using the TypeScript graphql definition we declared above; this is how the relay-compiler tool determines the GraphQL scripts it needs to generate. Here, we declare our root query with any arguments required, specify which object we are querying, and reference the fragment declared by a child component. Note also the genericly-typed QueryRenderer ; this gives the type system the info it needs to type the render and variables props.

Our App is just a React component; because it is our root render node within the QueryRenderer with arguments to represent query errors and props, which are just the query response nodes. Here, we are passing the props.allBooks piece into our child component.

Next, update the index.tsx file to call AppRoot instead of App and pass it the environment we defined in the relay-env file:

AST’s and JavaScript: Write Code that Writes Code

The adoption of abstract syntax trees in JavaScript has led to an explosion in tooling that has changed the landscape for developers.  The usage of ASTs allows JavaScript developers to better identify potential bugs before executing their code, as well as ensuring a consistent code quality across a codebase.  Their use in build tools has changed how we write our JavaScript applications by not only letting us target future specifications of the JavaScript language but also to compile non-spec compliant syntax (e.g. JSX) to JavaScript for use in the browser.  And by leveraging this data structure, developers can have 100% confidence in updating thousands of JavaScript files using an AST-based script by leveraging codemods.

While these tools carry a ton of power and potential, the use of code modification via AST manipulation scares off many developers.  The name itself will cause your average developer to scratch his head and proclaim, “Well, I’m not a Computer Science major so I don’t think a tool like that is for me.” However, given the current tooling available, AST manipulation is very approachable. [Read more…]

Browser-less Unit Testing your React/Redux Code with Mocha, Chai, and Enzyme

A lot of JavaScript applications out there require having a browser available to run your unit tests.  For years it seemed like the de-facto configuration for unit testing was some combination of Karma, PhantomJS, and either QUnit or Jasmine.  While I think that there is definitely value in making sure that your application runs properly in a browser, given that that is how your users will interact with it, my personal opinion is that the majority of your test suite should be able to be run outside of a browser environment.  This article will describe what Enzyme brings to the table in terms of unit testing your React/Redux code without needing a browser.

[Read more…]

Vue.js – The Next Library for Angular 1 Developers

Angular is the most successful JavaScript framework ever. I cannot back this up with any numbers, but based on my experience as a developer over the past few years, it is everywhere. It is truly a complete framework, and it’s no wonder why it has achieved so much success in the industry. However, like all technology, it is quickly becoming dated, and new options have entered the fold.

Libraries like React and Angular 2+ have learned from their predecessors and employ strategies and optimizations that result in less code bloat and better performance.  These new-age frameworks also leverage bleeding edge development tools such as Webpack and Babel, which allow developers to utilize future standards (and non-standards) of the JavaScript language, resulting in increased productivity and cleaner code.

[Read more…]

Using Aurelia’s Dependency Injection Library In Non-Aurelia App, Part 2

In my last post, we looked at Aurelia’s dependency injection library and how you might be able to use it as a standalone library in your vanilla JavaScript application.  Now, however, I want to take a look at how you might be able to wire it into your React application, and then finally how you might hook it into React/Redux application.

[Read more…]

Using Aurelia’s Dependency Injection Library In Non-Aurelia App, Part 1

If you are anything like me then you like to try to keep your code loosely coupled, even your JavaScript code.  The ES2015 module spec helped solve a lot of issues with dependency management in JavaScript apps, but it did not really do anything to prevent having code that is tightly coupled to the specific imports. When Aurelia was originally announced, one of the things that first caught my eye was that it included a dependency injection library that was designed to be standalone so you could use it even if you were not including the rest of the Aurelia framework.  Now that Aurelia has had some time to mature, I decided to see how exactly it might look to use the dependency injection library in a variety of non-Aurelia applications.

In this two-part blog series, I will unpack a few basics about the library itself, and then show how it might be used in three different apps: a vanilla JavaScript app, a React app, and then a React app that uses Redux for its state management.

[Read more…]

Writing Node Applications as a .NET Developer – My experience in Developing in Node vs .NET/C# (Part 3)

While the previous posts described what one needs to know prior to starting a Node project, what follows is some of my experiences that I came across while writing a Node application.  

How do I structure my project?

The main problem I had when developing my Node application was figuring out a sound application structure. As mentioned earlier, there is a significant difference between Node and C# when it comes to declaring file dependencies. C#’s using statement is more of a convenience feature for specifying namespaces and its compiler does the dirty work of determining what files and DLLs are required to compile a program. Node’s CommonJS module system explicitly imports a file or dependency into a dependent file at runtime. In C#, I generally inject a class’s dependencies via constructor injection, delegating object instantiation and resolution to an Inversion of Control container. In Javascript, however, I tend to write in a more functional manner where I write and pass around functions instead of stateful objects.

[Read more…]

Writing Node Applications as a .NET Developer – Getting Ready to Develop (Part 2)

In the previous blog post, I provided a general overview of some the key differences between the two frameworks. With this out of the way we’re ready to get started writing an application. However, there are some key decisions to make regarding what development tools to use as well as getting the execution environment set up.

Selecting an IDE/Text Editor

Before I could write a line of code, I needed to decide on an IDE/Text Editor that I wanted to use to write my application. As a C# developer, I was spoiled with the number of features that Visual Studio offered a developer that allowed for a frictionless and productive developing experience. I wanted to have this same experience when writing a Node application so before deciding on an IDE, I had a few prerequisites:

  • Debugging capabilities built into the IDE
  • Unobtrusive and generally correct autocomplete
  • File navigation via symbols (CTRL + click in Visual Studio with Resharper extension)
  • Refactoring utilities that I could trust; Find/Replace wasn’t good enough

[Read more…]

Writing Node Applications as a .NET Developer

As a .NET developer, creating modern web apps using Node on the backend can seem daunting.  The amount of tooling and setup required before you can write a “modern” application has resulted in the development community to display “Javascript Fatigue”; a general wariness related to the exploding amount of tooling, libraries, frameworks and best practices that are introduced on a seemingly daily basis.  Contrast this with building an app in .NET using Visual Studio where the developer simply selects a project template to build off of and they’re ready to go. [Read more…]

A Dive into SystemJS – Production Considerations

Previously we have looked at the basic configuration of SystemJS and what happens when you attempt to load modules. What we have covered so far is good enough for a development system, but things are different when you try to push your code to production and performance is much more important. It might be fine for a development system to make XHR requests for each individual script file, but that is not ideal for most production systems. This article will attempt to evaluate the production setup that is needed to attain good performance. [Read more…]