Apollo-server: Multiple types per field in schema?

Created on 30 Jul 2016  路  4Comments  路  Source: apollographql/apollo-server

Hello guys, I'm making a live music app that shows you who's playing, where and when. I want to enable a field to have two possible types.

There are two types of users that can post "events". Is this type thing possible? If yes, how would you make it work? I have an owner_type column in my database that can be 'artist' or 'venue', but I don't know how I can use it to set the type in Apollo.

type Event {
    owner: Artist || Venue,  // I want to set the type based on a value read from the DB using knex
    venue: Venue,
    artists: [Artist],
}

type Artist {
    events: [Event],
}

type Venue {
    events: [Event],
}

Currently I'm working around this by setting it like this:

type Event {
    owner_id: Int,
    owner_type: String,
    venue: Venue,
    artists: [Artist],
}

Most helpful comment

Thanks @helfer, it took me a while to figure out how to deal with my connectors, but it worked. The app is more complex than what's shown, but I hope my solution will aid anyone who might be struggling with this:

Schema:

type Artist {
    id: Int,
    name: String,
}

type Venue {
    id: Int,
    name: String,
}

union Owner = Artist | Venue

type Event {
    id: Int!,
    owner: Owner,
}

type RootQuery {
  event(id: Int): Event,
  venue(id: Int): Venue,
}

Resolvers:

    RootQuery: {
        event(_, args) {
            return eventModel.getEventById(args.id);
        },
        venue(_, args) {
            return venueModel.getVenueById(args.id);
        },
    },
    Event: {
        owner(event) {
            return eventModel.getOwner(event.id)
        },
    },
    Venue: {
        id(venue) {
            return venue.id;
        },
        name(venue) {
            return venue.name;
        },
    },
    Artist: {
        id(artist) {
            return artist.id;
        },
        name(artist) {
            return artist.name;
        },
    },
    Owner: { // this needs to receive an object with a type property for it to work, check your connectors!
        __resolveType(owner, ctx, info) {
            if(owner.type == 'artist') {
                return info.schema.getType('Artist');
            } else return info.schema.getType('Venue');
        },
    }

The query:

{
  event(id: 3) {
    owner {
      ... on Artist {
        name
      }
    }
  }
}

If anyone's using Knex, here's my connector:

export class Event {
    getOwner(eventId) {
        return knex('event')
            // Check the owner type
            .where('event.id', eventId)
            .then(result => {
                // Customise result for each owner type
                if(result[0].owner_type === 'artist') {
                    return knex('event')
                        .join('artist', 'artist.id', 'event.owner_id')
                        .select('artist.id', 'artist.name')
                        .where('event.id', eventId)
                        .limit(1)
                        .first()
                        .then(response => {
                            response.type = 'artist';
                            return response;
                        })
                } else if(result[0].owner_type === 'venue') {
                    return knex('event')
                        .join('venue', 'venue.id', 'event.venue_id')
                        .select('venue.id', 'venue.name')
                        .where('event.id', eventId)
                        .limit(1)
                        .first()
                        .then(response => {
                            response.type = 'venue';
                            return response;
                        })

                }
            });
    }
}

All 4 comments

You can use a union:

union ArtistOrVenue = Artist | Venue

Thanks @helfer, it took me a while to figure out how to deal with my connectors, but it worked. The app is more complex than what's shown, but I hope my solution will aid anyone who might be struggling with this:

Schema:

type Artist {
    id: Int,
    name: String,
}

type Venue {
    id: Int,
    name: String,
}

union Owner = Artist | Venue

type Event {
    id: Int!,
    owner: Owner,
}

type RootQuery {
  event(id: Int): Event,
  venue(id: Int): Venue,
}

Resolvers:

    RootQuery: {
        event(_, args) {
            return eventModel.getEventById(args.id);
        },
        venue(_, args) {
            return venueModel.getVenueById(args.id);
        },
    },
    Event: {
        owner(event) {
            return eventModel.getOwner(event.id)
        },
    },
    Venue: {
        id(venue) {
            return venue.id;
        },
        name(venue) {
            return venue.name;
        },
    },
    Artist: {
        id(artist) {
            return artist.id;
        },
        name(artist) {
            return artist.name;
        },
    },
    Owner: { // this needs to receive an object with a type property for it to work, check your connectors!
        __resolveType(owner, ctx, info) {
            if(owner.type == 'artist') {
                return info.schema.getType('Artist');
            } else return info.schema.getType('Venue');
        },
    }

The query:

{
  event(id: 3) {
    owner {
      ... on Artist {
        name
      }
    }
  }
}

If anyone's using Knex, here's my connector:

export class Event {
    getOwner(eventId) {
        return knex('event')
            // Check the owner type
            .where('event.id', eventId)
            .then(result => {
                // Customise result for each owner type
                if(result[0].owner_type === 'artist') {
                    return knex('event')
                        .join('artist', 'artist.id', 'event.owner_id')
                        .select('artist.id', 'artist.name')
                        .where('event.id', eventId)
                        .limit(1)
                        .first()
                        .then(response => {
                            response.type = 'artist';
                            return response;
                        })
                } else if(result[0].owner_type === 'venue') {
                    return knex('event')
                        .join('venue', 'venue.id', 'event.venue_id')
                        .select('venue.id', 'venue.name')
                        .where('event.id', eventId)
                        .limit(1)
                        .first()
                        .then(response => {
                            response.type = 'venue';
                            return response;
                        })

                }
            });
    }
}

@savovs In you Resolvers section, what does info.schema.getType('Artist') return?

    Owner: { // this needs to receive an object with a type property for it to work, check your connectors!
        __resolveType(owner, ctx, info) {
            if(owner.type == 'artist') {
                return info.schema.getType('Artist');
            } else return info.schema.getType('Venue');
        },
    }

I'm trying to figure out exactly what I should be returning here. I have a type field on my unioned objects. Is it a string, object, or something else?

Hi @twonmulti

It will return the GraphQLObjectType defined for them.
Then this is what resolveType will return to resolve the abstract type..

Was this page helpful?
0 / 5 - 0 ratings