Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: increase documentation across the codebase #503

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
180 changes: 180 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# graphql-node-resource

This library implements the [Node GraphQL type defined by Relay](https://relay.dev/docs/guides/graphql-server-specification/#object-identification). Relay requires that a GraphQL server
has a way to identify and refetch GraphQL resources. Its meant to be used within a GraphQL API that fetches its types from a REST API.


```mermaid
graph LR;
frontend(frontend using relay)-->graphql-api(GraphQL API);
graphql-api-->rest-api(REST API);
rest-api-->graphql-api;
graphql-api-->frontend;
```
_example architecture_


## What is a Node?

A `Node` is a refetchable GraphQL resource. All `Node`s must have a _globally unique ID_ and be fetchable via this ID. Relay will refetch these resources in the following way:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: maybe reference the gql interface, perhaps even update the code snippet below such that Foo implements Node? The Node interface is the fundamental part, right? it's the smart impl that's the magic?


```
node(id: $resource_id) {
... on Foo {
bar
baz
}
}
```

## The Node Class

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: this is kind of ambiguous to me, should I be reading this something like, "The NodeType, a sensible Node Implementation"?


The [NodeType](./src/types/NodeType.ts#) class does two things:
1. it defines the `Node` type required by Relay
1. it implements a solution to asynchronously resolve the Node by fetching it from a backing REST API

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: just want to make sure I understanding the semantics clearly, this is most likely just me making noise...

  1. does it define the Node or does it provide the concrete implementation for a Node in the relay sense?
  2. the solution is just an implementation of the relay gql interface that's got some smarts, right? provides implementation for node, nodes by asynchronously fetching...?


It is an extension of the [GraphQLObjectType](https://graphql.org/graphql-js/type/#graphqlobjecttype) provided by `graphql-js`.

Every node consists of a backing HTTP REST resource that will be used to fetch the node. This library exposes an [HttpResource class](./src/resources/HttpResource.ts) that implements basic CRUD API operations.

## Usage

### Creating a new Node type

If your API is a standard CRUD API, then all you need to do is create a new instance of a NodeType:

```
import {GraphQLInt} from 'graphql';

// Foo.ts
export default new NodeType({
// this determines the GraphQL type name
name: 'Foo',

// this provides graphql documentation on this type
description: 'Foo is the catch all example for all things programming',

// returns an object containing all of the fields for this graphql type
fields() {
return {
bar: {
type: GraphQLInt,
description: 'A unit of Foo'
}
}
},

createResource(ctx) {
// as long as the `/foo` endpoint follows standard CRUD, then HttpResource should be able
// to handle fetching it correctly
return new HttpResource({
endpoint: "/foo"
})
}
});


```



### Adding the Node Field to Your Query Object

In order to fulfill the contract required by Relay, you need to expose a field `node` on your `Query` type.

```
// query.ts
import { getConfig } from '@4c/graphql-node-resource/config';

const config = getConfig();

export default new GraphQLObjectType({
name: 'Query',

fields: {
node: config.nodeField,
nodes: config.nodesField,
},
})
```

This will expose `node` and `nodes` on your Query graphql type.

### Resolving a Node Type

There are two ways to resolve a Node type from a parent type:
1. use the `NodeType.getResource(context)` function to get the HTTPResource object and use that to get the node
2. resolve the field to an object containing an id such as `{id: 'foo123'}

### Using Get Resource

You can resolve the resource directly by getting the HTTPResource object from the NodeType instance:
```
// Baz.ts
export default new GraphQLObjectType({
name: 'Baz',
fields: {
foo: {
type: new GraphQLList(Foo),
resolve(context) {
const resource = Foo.getResource(context) as HTTPResource;
return resource.get(); // get a list of Foos
}
}
}
})
```

This is useful when you don't have an ID and need to resolve a list of resources or
fetch a list of resources and then find the object you are looking for.

### Passing an object with an id

The `NodeType` class provides default resolvers for all defined fields. These resolvers will try to fetch the Node from the provided resource and then resolve to the matching field in the response. In order to do this, the `id` field must be present in the object that is being resolved.

In the example below, the resolver for the `foo` field returns an object with an `id` property. The default resolvers provided by the NodeType will use the `id` property to try and fetch the `Foo` resource.

```
// Baz.ts
export default new GraphQLObjectType({
name: 'Baz',
fields: {
foo: {
type: Foo,
resolve: (parent, args, context, info) => {
return {
id: 'foo:123'
}
}
}
}
})
```

This is useful when you have a list of IDs in the parent type. For instance, if `Baz` has a list of Foo `id`s, then you could pass the list of IDs to the Foo type. See [this example](example/types/Author.ts#L30) for more information.

### Connections

TODO

### Fetching Nodes from a list endpoint

TODO

### Fetching non node types

TODO

### Performance

TODO

## Best Practices

The following is a list of suggested best practices:
1. One HttpResource for one GraphQL type: an HttpResource should be responsible for fetching a single Resource.

## Example Application

For a full end-to-end example of this library, see [example/README.md](example/README.md). This demonstrates how to connect
this library to a real backend REST API.
32 changes: 32 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Example Application

This example application shows how to create a GraphQL API over a REST API utilizing the `graphql-node-resource` library. There
are two parts to this example application:
1. a REST API implemented via NodeJS with Express. The API exposes a simple blog resource and supports pagination.
2. a GraphQL API that exposes the REST API. This shows how to utilize the `graphql-node-resource` library properly.

## api/api.ts

This is where the REST API lives. It may be useful to look at the entry and exit points of the API to see what the GraphQL server passes
as input and expects as output. This is especially useful for looking at the paginated connection endpoints. Connections need to be supported
via the underlying REST API.

## GraphQL API

The GraphQL API is composed of 3 main parts:
1. `setup.ts` - this is required to tell the library how to fetch resources from the REST API as well as some basic configuration
2. `types/*.ts` - these are the GraphQL types that make up the GraphQL API
3. `index.ts` - this is the entry point for the GraphQL API. It calls the setup.ts module and combines all of the types

## Starting Point

A good starting point would be the `types/Query.ts` file. This is the entrypoint into any of the API endpoints.

## Running the App

```sh
yarn # install the deps
yarn start # start the app
```

The app will run the GraphIQL tool. Navigate to the [outputted server url](http://localhost:8081/graphql) to interact with the API.
Loading