DEV Community

Cover image for GraphQL + NestJS + Interfaces & Unions

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
  },
});
Enter fullscreen mode Exit fullscreen mode

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;

  // ...
}
Enter fullscreen mode Exit fullscreen mode

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();
}
// ...
Enter fullscreen mode Exit fullscreen mode

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

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay