AutoRelay
AutoRelay is a librairy designed to work alongside TypeGraphQL and make it easy to paginate your results using the Relay spec.
Please note this is currently a W.I.P, expect frequent breaking changes in the API until 1.0.0
AutoRelay is meant to be plug and play with TypeGraphQL and TypeORM (more orm support such as sequelize-typescript could be supported). Simply decorate your relations with @RelayedConnection
as such:
@Entity()
@ObjectType()
class User {
@OneToMany(() => Recipe)
@RelayConnection(() => Recipe)
recipes: Recipe[];
}
This will result in the following working SDL:
// Autogenrated by AutoRelay
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String!
endCursor: String!
}
// AutoGenerated by AutoRelay
type UserRecipeConnection {
edges: [UserRecipeEdge]!
pageInfo: PageInfo!
}
// AutoGenerated by AutoRelay
type UserRecipeEdge {
cursor: String!
node: Recipe!
}
type Recipe {
// ...
}
type User {
recipes(...): UserRecipeConnection
}
where User.recipes
is now a fully working field resolver, that automatically takes Relay ConnectionArguments (first, before, after, last) and returns a Relay Connection containing the results.
Installation
This lib is meant to be used with TypeGraphQL. It will not work with other code-first graphql librairies
-
Install the npm package:
npm install auto-relay --save
-
(Install an ORM if you plan to use @RelayedConnection
)
currently only TypeORM is supported
npm install @auto-relay/typeorm
Quick Start
Simply configure AutoRelay to use your ORM of choice, and you're ready to go !
index.ts
import { TypeOrmConnection } from '@auto-relay/typeorm'
import { AutoRelayConfig } from 'auto-relay'
new AutoRelayConfig({ orm: () => TypeOrmConnection })
Step-by-step guide
AutoRelay was designed with two goals in mind : Firstly, to automate the pagination between two entities that share a relationship in TypeORM. Secondly, to make it easy and boilerplate-free to implement your own Relay logic. This guide will showcase both of those.
Making a relationship relayable.
Let's say you currently have two entities / graphql objects, User
and Recipe
. A recipe is always linked to an user, and a given user can have multiple recipes. Your classes might look like that :
export class User {
@PrimaryGeneratedColumn()
@Field(() => ID)
id: number;
@Column()
@Field()
name: string;
@OneToMany(() => Recipe)
@Field(() => [Recipe])
recipes: Recipe[]
}
export class Recipe {
@PrimaryGeneratedColumn()
@Field(() => ID)
id: number;
@Column()
@Field(() => Int)
rating: number
@ManyToOne(() => User)
user: User
}
With some custom logic (either lazy-loading or field resolvers) to fetch User.recipes / Recipe.user.
With AutoRelay, we're gonna replace all that logic with a single decorator, @RelayedConnection
.
Our User
will now look like this :
export class User {
@PrimaryGeneratedColumn()
@Field(() => ID)
id: number;
@Column()
@Field()
name: string;
@OneToMany(() => Recipe)
@RelayedConnection(() => Recipe)
recipes: Recipe[]
}
This will auto-magically create a few GraphQL types, such as UserRecipeConnection
and UserRecipeEdge
. Our User.recipes field now takes Relay's ConnectionArguments and returns UserRecipeConnection!
.
Our TypeORM integration gets the repository for Recipe
, translates the ConnectionArguments to an offset/limit tuple and fetches recipes connected to this User
.
Making a Query Relayable
Let's imagine we now have an users
query, that we want to paginate using Relay. AutoRelay offers a few helpers with that.
@Resolver(of => User)
export class UserResolver {
constructor(
protected readonly userRepository: Repository<User>
)
@RelayedQuery(() => User)
async users(
@RelayLimitOffset() {limit, offset}: RelayLimitOffsetArgs
): Promise<[number, User[]]> {
return this.userRepository.findAndCount({
where: {
},
skip: offset,
take: limit
})
}
}
And that's it! Again, AutoRelay has taken care under the hood of a few things:
- It's created all the necessary GraphQL types for us.
- It's ensured the
users
query expects Connection Arguments, but conveniently translated them to limit/offset for us. - It takes the return of our
findAndCount
calls and automatically transforms it to a Relay Connection
as expected by GraphQL.
Extending edges (relationship metadata)
Often, we have relationships that contains metadata. This is particulary the case for N:M relationships, where the join table might contain data our graphql client might want.
AutoRelay offers a simple API to extend the returned Edges
with information contained in a join table.
class EntityA {
@OneToMany(() => JoinEntity)
joinEntity: JoinEntity
}
class EntityB {
@OneToMany(() => JoinEntity)
joinEntity: JoinEntity
}
class JoinEntity {
@Column()
@Field()
metadata: string;
@ManyToOne(() => EntityA)
entityA: EntityA
@ManyToOne(() => EntityB)
entityB: EntityB
}
Let's say we want EntityB to be queryable with Relay arguments from EntityA. Our code would simply become :
class EntityA {
@OneToMany(() => JoinEntity)
joinEntity: JoinEntity
@RelayedConnection(model => EntityB, through => JoinEntity)
entitiesB: EntityB[];
}
This would result in the following working SDL:
type EntityA {
// ...
entitiesB: EntitiesAToBConnection!
}
type EntityB {
// ...
}
type EntitiesAToBConnection {
edges: [EntityAToBEdge]!
pageInfo: PageInfo!
}
type EntityAToBEdge {
cursor: String!
metadata: String!
node: EntityB
}