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 {
    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].


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.


Finally we need a resolver:

// ...
@Query(() => [RobotInterface], {
  description: 'Get all robots',
})
robots(): Array<RobotInterface> {
  return this.appService.getRobots();
}
// ...

Unions

This is not really any different that interfaces, just use the createUnionType helper function and pass your classes, then specify on which type it should return what:

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 then specify that as the return type of your query, or mutation.


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:


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 the apps/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