GraphQL + NestJS + Interfaces & Unions
So guys I was really confused by the NestJS's doc as to ho you can have an API which can be invoked like this: query { robots { ... on RobotInterface { name } ... on HumanoidRobot { height } ... on ScaraRobot { axes } } } So in others words I wanted to not repeat myself by having an interface and I needed to also prevent any fields from other types leaking to another one. The solution was simple, first I had to define my RobotInterface, HumanoidRobot object type and ScaraRobot: app/types/robot-interface.type.ts import { Field, ID, InterfaceType } from '@nestjs/graphql'; import { HumanoidRobot } from './humanoid.type'; import { ScaraRobot } from './scara.type'; @InterfaceType({ description: "'Common fields, available for all robots'," resolveType: (value) => { if ('height' in value) { return HumanoidRobot; } return ScaraRobot; }, }) export abstract class RobotInterface { @Field(() => ID, { description: "'ID of the robot' })" id: string; @Field(() => String, { description: "'Name of the robot' })" name: string; @Field(() => String, { description: "'The COLLADA image of the robot'," }) colladaImage: string; @Field(() => String, { description: "'Description of the robot' })" description: "string;" @Field(() => String, { description: "'Model number of the robot' })" modelNumber: string; } app/types/scara.type.ts import { Field, Float, Int, ObjectType } from '@nestjs/graphql'; import { RobotInterface } from './robot-interface.type'; @ObjectType({ implements: () => [RobotInterface], description: "" 'Scara robot, specialized for tasks requiring high precision & speed', }) export class ScaraRobot implements RobotInterface { id: string; name: string; colladaImage: string; description: "string;" modelNumber: string; @Field(() => Int, { description: "'Number of axes'," }) axes: number; @Field(() => Float, { description: "'Payload capacity in kg'," }) payload: number; } And something similar to this for the HumanoidRobot. Caution the implements option should be a callback function and not a simple implements: [RobotInterface]. This in fact wasted my precious 2 hours. So it should be implements: () => [RobotInterface]. Now it is time to define your union type: import { createUnionType } from '@nestjs/graphql'; import { HumanoidRobot } from './humanoid.type'; import { ScaraRobot } from './scara.type'; export const UnionOfRobots = createUnionType({ name: 'UnionOfRobots', description: 'Union of robots fields', types: () => [HumanoidRobot, ScaraRobot] as const, resolveType: (value) => { if ('height' in value) { return HumanoidRobot; } return ScaraRobot; }, }); And note that the resolveType will help your NestJS app to decide which type it should use, TBH I saw in a lot of places people use enums. In other word they add an extra field to the RobotInterface and call it type and decide based on that field. I guess that would be easier to have compare to relying on the presence of a field. So feel free to do that instead of this, it would look like this: @InterfaceType({ description: 'Common fields, available for all robots', resolveType: (value) => { if (value.type === RobotType.HUMANOID) { return HumanoidRobot; } return ScaraRobot; }, }) export abstract class RobotInterface { // ... @Field(() => RobotType, { description: 'Type of the robot' }) type: RobotType; // ... } And of course you need to register the enum type first. Then you can do the same thing in the createUnionType helper function. Finally we need a resolver: // ... @Query(() => [UnionOfRobots], { description: 'Get all robots', }) robots(): Array { return this.appService.getRobots(); } // ... Support Me You can support me by liking this post, reading my other posts and giving my repo a star on GitHub. As simple as that. Need the code itself? Do not worry I've got you covered: kasir-barati / graphql-js-ts Where you can learn all about GraphQL and its intricacies Caution Keep this file in sync with index.md. GraphQL Tip Just for those curious minds who always jump from one branch to another like mine: Hasura GraphQL VS REST. Testing GraphQL API You can find a good definition usually in glossary. Intro. Data types. A simple todo app written with GraphQL + ReactJS + Relay Backend. Frontend TK. Queries and mutations in depth. Let's breakdown the query language a bit more. Functions provided by graphql. Document your GraphQL service API. GraphQL request lifecycle Common validation errors. Execution from inside, resolver's args, AST, ... Code-first approach. Auth. How to query information about a GraphQL schema. Improve d
So guys I was really confused by the NestJS's doc as to ho you can have an API which can be invoked like this:
query {
robots {
... on RobotInterface {
name
}
... on HumanoidRobot {
height
}
... on ScaraRobot {
axes
}
}
}
So in others words I wanted to not repeat myself by having an interface and I needed to also prevent any fields from other types leaking to another one.
The solution was simple, first I had to define my RobotInterface
, HumanoidRobot
object type and ScaraRobot
:
app/types/robot-interface.type.ts
import { Field, ID, InterfaceType } from '@nestjs/graphql';
import { HumanoidRobot } from './humanoid.type';
import { ScaraRobot } from './scara.type';
@InterfaceType({
description: "'Common fields, available for all robots',"
resolveType: (value) => {
if ('height' in value) {
return HumanoidRobot;
}
return ScaraRobot;
},
})
export abstract class RobotInterface {
@Field(() => ID, { description: "'ID of the robot' })"
id: string;
@Field(() => String, { description: "'Name of the robot' })"
name: string;
@Field(() => String, {
description: "'The COLLADA image of the robot',"
})
colladaImage: string;
@Field(() => String, { description: "'Description of the robot' })"
description: "string;"
@Field(() => String, { description: "'Model number of the robot' })"
modelNumber: string;
}
app/types/scara.type.ts
import { Field, Float, Int, ObjectType } from '@nestjs/graphql';
import { RobotInterface } from './robot-interface.type';
@ObjectType({
implements: () => [RobotInterface],
description: ""
'Scara robot, specialized for tasks requiring high precision & speed',
})
export class ScaraRobot implements RobotInterface {
id: string;
name: string;
colladaImage: string;
description: "string;"
modelNumber: string;
@Field(() => Int, {
description: "'Number of axes',"
})
axes: number;
@Field(() => Float, {
description: "'Payload capacity in kg',"
})
payload: number;
}
And something similar to this for the HumanoidRobot
.
Caution
the
implements
option should be a callback function and not a simpleimplements: [RobotInterface]
. This in fact wasted my precious 2 hours. So it should beimplements: () => [RobotInterface]
.
Now it is time to define your union type:
import { createUnionType } from '@nestjs/graphql';
import { HumanoidRobot } from './humanoid.type';
import { ScaraRobot } from './scara.type';
export const UnionOfRobots = createUnionType({
name: 'UnionOfRobots',
description: 'Union of robots fields',
types: () => [HumanoidRobot, ScaraRobot] as const,
resolveType: (value) => {
if ('height' in value) {
return HumanoidRobot;
}
return ScaraRobot;
},
});
And note that the resolveType
will help your NestJS app to decide which type it should use, TBH I saw in a lot of places people use enums. In other word they add an extra field to the RobotInterface
and call it type and decide based on that field. I guess that would be easier to have compare to relying on the presence of a field.
So feel free to do that instead of this, it would look like this:
@InterfaceType({
description: 'Common fields, available for all robots',
resolveType: (value) => {
if (value.type === RobotType.HUMANOID) {
return HumanoidRobot;
}
return ScaraRobot;
},
})
export abstract class RobotInterface {
// ...
@Field(() => RobotType, { description: 'Type of the robot' })
type: RobotType;
// ...
}
And of course you need to register the enum type first. Then you can do the same thing in the createUnionType
helper function.
Finally we need a resolver:
// ...
@Query(() => [UnionOfRobots], {
description: 'Get all robots',
})
robots(): Array<typeof UnionOfRobots> {
return this.appService.getRobots();
}
// ...
Support Me
You can support me by liking this post, reading my other posts and giving my repo a star on GitHub.
As simple as that. Need the code itself? Do not worry I've got you covered:
kasir-barati
/
graphql-js-ts
Where you can learn all about GraphQL and its intricacies
Caution
Keep this file in sync with index.md
.
GraphQL
Tip
Just for those curious minds who always jump from one branch to another like mine:
You can find a good definition usually in glossary.
- Intro.
- Data types.
- A simple todo app written with GraphQL + ReactJS + Relay
- Queries and mutations in depth.
- Let's breakdown the query language a bit more.
-
Functions provided by
graphql
. - Document your GraphQL service API.
- GraphQL request lifecycle
- Code-first approach.
- Auth.
- How to query information about a GraphQL schema.
- Improve developer experience
- Security in GraphQL.
-
NestJS
- …
Note
This is a monorepo, so I have tons of other apps, but the one you're interested in at the moment is
apps/interfaces-unions
. BTW If you like to learn how to write e2e tests for you NestJS app look at theapps/interfaces-unions-e2e
.
Instagram: https://www.instagram.com/node.js.developers.kh/
Facebook: https://www.facebook.com/kasirbarati
X: https://x.com/kasir_barati
YouTube: https://www.youtube.com/@kasir-barati
GitHub: https://github.com/kasir-barati/
Dev.to: https://dev.to/kasir-barati
LinkedIn: https://linkedin.com/in/kasir-barati