Nest: Add example for GraphQL Relay

Created on 22 Dec 2018  路  6Comments  路  Source: nestjs/nest

Please add example how to do a GraphQL for Relay with connections, edges, nodes, totalCount...
https://facebook.github.io/relay/docs/en/graphql-server-specification.html

example question 馃檶

Most helpful comment

This is my approach inspired by (but not using the entire) https://www.npmjs.com/package/nestjs-graphql-relay:

package.json

{
  "dependencies": {
    /* ... */
    "@nestjs/graphql": "^7.2.0",
    "@types/graphql-relay": "^0.4.11",
    "apollo-server-fastify": "^2.11.0",
    "graphql": "<15.0.0",
    "graphql-relay": "^0.6.0",
    "graphql-tools": "^4.0.7",
  }
}

nestjs-graphql-relay.ts

import { Field, ObjectType, Int, ID, InterfaceType } from '@nestjs/graphql';
import * as Relay from 'graphql-relay';

@InterfaceType()
export abstract class Node {
  @Field(() => ID)
  id!: string;
}

@ObjectType({ isAbstract: true })
export abstract class Aggregate {
  @Field(() => Int)
  readonly count: number;
}

@ObjectType()
export class PageInfo implements Relay.PageInfo {
  @Field(() => Boolean, { nullable: true })
  hasNextPage?: boolean;
  @Field(() => Boolean, { nullable: true })
  hasPreviousPage?: boolean;
  @Field(() => String, { nullable: true })
  startCursor?: Relay.ConnectionCursor;
  @Field(() => String, { nullable: true })
  endCursor?: Relay.ConnectionCursor;
}

project.service.ts

import { Field, Int, ObjectType, ID } from '@nestjs/graphql';
import { Node } from '../nestjs-graphql-relay';

@ObjectType({implements: Node})
export class Project extends Node {
  @prop({alias: 'id'})
  _id?: string;

  @Field(() => ID)
  id!: string;

  @IsString()
  @prop()
  @Field()
  name!: string;

  @IsString()
  @prop()
  @Field()
  slug!: string;
}

project.resolver.ts

import { Resolver, Args, Query, ID, Mutation, Field, ObjectType, ResolveField, Parent, Int } from '@nestjs/graphql';
import { Project } from './project.entity';
import { ProjectService } from './project.service';
import * as Relay from 'graphql-relay';
import { PageInfo, Aggregate } from './nestjs-graphql-relay';

@ObjectType({isAbstract: true})
abstract class ProjectsEdge implements Relay.Edge<Project> {
  @Field(() => Project)
  readonly node!: Project;

  @Field()
  readonly cursor!: Relay.ConnectionCursor;
}

@ObjectType()
export class ProjectsConnection implements Relay.Connection<Project> {
  @Field()
  readonly pageInfo!: PageInfo;

  @Field(() => [ProjectsEdge])
  readonly edges!: Relay.Edge<Project>[];

  @Field(() => Aggregate, {nullable: true})
  readonly aggregate?: Aggregate;
}

@Resolver(() => Project)
export class ProjectResolver {

  constructor(
    private readonly projectService: ProjectService,
  ) {}

  @Query(returns => ProjectsConnection)
  async projects(): Promise<ProjectsConnection> {
    const edges = (await this.projectService.findAll())
      .map(_ => ({node: _, cursor: _.id} as ProjectsEdge));
    const startCursor = edges.length >= 1 ? edges[0].cursor : undefined;
    const endCursor = edges.length >= 1 ? edges[edges.length - 1].cursor : undefined;
    return {
      pageInfo: {hasPreviousPage: false, hasNextPage: false, startCursor, endCursor},
      edges};
  }
}

All 6 comments

Have you figured out any pattern to implement Relay specification?

i would be interested in this issue as well!

Some community projects:

nestjs-graphql-relay npm package (https://github.com/piic/nestjs-plugins#readme) by @piic. (currently has a hard dependency on TypeORM)

Sample resolver usage:

@ObjectType({ isAbstract: true })
abstract class RecipesEdge implements Relay.Edge<Recipe> {
  @Field(() => Recipe)
  readonly node!: Recipe;

  @Field()
  readonly cursor!: Relay.ConnectionCursor;
}

@ObjectType()
export class RecipesConnection implements Relay.Connection<Recipe> {
  @Field()
  readonly pageInfo!: PageInfo;

  @Field(() => [RecipesEdge])
  readonly edges!: Array<Relay.Edge<Recipe>>;

  @Field(() => Aggregate)
  readonly aggregate: Aggregate;
}

Other: https://github.com/kazekyo/nestjs-graphql-relay example by @kazekyo (last used with NestJS 6)

Since NestJS recently incorporated its own GraphQL library (instead of using type-graphql), I think NestJS should also explicitly (but optional) support Relay connections. Most likely in the spirit of https://github.com/wemaintain/auto-relay by @wemaintain (reference: https://github.com/MichalLytek/type-graphql/issues/142#issuecomment-582132843).

This is my approach inspired by (but not using the entire) https://www.npmjs.com/package/nestjs-graphql-relay:

package.json

{
  "dependencies": {
    /* ... */
    "@nestjs/graphql": "^7.2.0",
    "@types/graphql-relay": "^0.4.11",
    "apollo-server-fastify": "^2.11.0",
    "graphql": "<15.0.0",
    "graphql-relay": "^0.6.0",
    "graphql-tools": "^4.0.7",
  }
}

nestjs-graphql-relay.ts

import { Field, ObjectType, Int, ID, InterfaceType } from '@nestjs/graphql';
import * as Relay from 'graphql-relay';

@InterfaceType()
export abstract class Node {
  @Field(() => ID)
  id!: string;
}

@ObjectType({ isAbstract: true })
export abstract class Aggregate {
  @Field(() => Int)
  readonly count: number;
}

@ObjectType()
export class PageInfo implements Relay.PageInfo {
  @Field(() => Boolean, { nullable: true })
  hasNextPage?: boolean;
  @Field(() => Boolean, { nullable: true })
  hasPreviousPage?: boolean;
  @Field(() => String, { nullable: true })
  startCursor?: Relay.ConnectionCursor;
  @Field(() => String, { nullable: true })
  endCursor?: Relay.ConnectionCursor;
}

project.service.ts

import { Field, Int, ObjectType, ID } from '@nestjs/graphql';
import { Node } from '../nestjs-graphql-relay';

@ObjectType({implements: Node})
export class Project extends Node {
  @prop({alias: 'id'})
  _id?: string;

  @Field(() => ID)
  id!: string;

  @IsString()
  @prop()
  @Field()
  name!: string;

  @IsString()
  @prop()
  @Field()
  slug!: string;
}

project.resolver.ts

import { Resolver, Args, Query, ID, Mutation, Field, ObjectType, ResolveField, Parent, Int } from '@nestjs/graphql';
import { Project } from './project.entity';
import { ProjectService } from './project.service';
import * as Relay from 'graphql-relay';
import { PageInfo, Aggregate } from './nestjs-graphql-relay';

@ObjectType({isAbstract: true})
abstract class ProjectsEdge implements Relay.Edge<Project> {
  @Field(() => Project)
  readonly node!: Project;

  @Field()
  readonly cursor!: Relay.ConnectionCursor;
}

@ObjectType()
export class ProjectsConnection implements Relay.Connection<Project> {
  @Field()
  readonly pageInfo!: PageInfo;

  @Field(() => [ProjectsEdge])
  readonly edges!: Relay.Edge<Project>[];

  @Field(() => Aggregate, {nullable: true})
  readonly aggregate?: Aggregate;
}

@Resolver(() => Project)
export class ProjectResolver {

  constructor(
    private readonly projectService: ProjectService,
  ) {}

  @Query(returns => ProjectsConnection)
  async projects(): Promise<ProjectsConnection> {
    const edges = (await this.projectService.findAll())
      .map(_ => ({node: _, cursor: _.id} as ProjectsEdge));
    const startCursor = edges.length >= 1 ? edges[0].cursor : undefined;
    const endCursor = edges.length >= 1 ? edges[edges.length - 1].cursor : undefined;
    return {
      pageInfo: {hasPreviousPage: false, hasNextPage: false, startCursor, endCursor},
      edges};
  }
}

I created a repository with a relay-compliant GraphQL server using TypeGraphQL and TypeORM. It covers things such as global object identification and bi-directional relay cursor pagination (following the specification).

I believe that it could be easily adapted to NestJS.

https://github.com/calmonr/typegraphql-relay

It's a boilerplate, not a library.

It has room for improvement (ordering, filtering, dataloader, more examples, etc) but most important parts are implemented and working properly.

I really liked the @Superd22 (auto-relay) implementation but it was inspired by @kazekyo and @9renpoto that has an issue when paginating. The user must specify before when paginating backwards (aka: a weird corner case). (good job tho)

Feel free to send suggestion, issues, pull requests.

Thank you.

cc @ceefour @johannesschobel @Simonpedro @igo @GrayStrider @mattleff @icereval @hugohernani

Was this page helpful?
0 / 5 - 0 ratings