62

I have some object types that I'd like to use as both input and output - for instance a currency type or a reservation type.

How do I define my schema to have a type that supports both input and output - I don't want to duplicate code if I don't have to. I'd also prefer not to create duplicate input types of things like currency and status enums.

export const ReservationInputType = new InputObjectType({
  name: 'Reservation',
  fields: {
    hotelId: { type: IntType },
    rooms: { type: new List(RoomType) },
    totalCost: { type: new NonNull(CurrencyType) },
    status: { type: new NonNull(ReservationStatusType) },
  },
});

export const ReservationType = new ObjectType({
  name: 'Reservation',
  fields: {
    hotelId: { type: IntType },
    rooms: { type: new List(RoomType) },
    totalCost: { type: new NonNull(CurrencyType) },
    status: { type: new NonNull(ReservationStatusType) },
  },
});
MonkeyBonkey
  • 46,433
  • 78
  • 254
  • 460

4 Answers4

41

In the GraphQL spec, objects and input objects are distinct things. Quoting the spec for input objects:

Fields can define arguments that the client passes up with the query, to configure their behavior. These inputs can be Strings or Enums, but they sometimes need to be more complex than this.

The Object type... is inappropriate for re‐use here, because Objects can contain fields that express circular references or references to interfaces and unions, neither of which is appropriate for use as an input argument. For this reason, input objects have a separate type in the system.

An Input Object defines a set of input fields; the input fields are either scalars, enums, or other input objects. This allows arguments to accept arbitrarily complex structs.

While an implementation might provide convenience code to create an object and a corresponding input object from a single definition, under the covers, the spec indicates that they'll have to be separate things (with separate names, such as Reservation and ReservationInput).

radoh
  • 4,554
  • 5
  • 30
  • 45
CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • where is it stated that about the separate names for type and input? – 4F2E4A2E Feb 17 '18 at 06:23
  • @4F2E4A2E: Well, the section of the specification that I quoted covers this (Object versus Input Object). – CommonsWare Feb 17 '18 at 12:00
  • @4F2E4A2E: "it is possible to have both definitions with the same name but with different types, right?" -- not to the best of my knowledge. Symbols have to be unique in most programming languages. – CommonsWare Feb 18 '18 at 13:32
  • Thanks, i've tested it out, it's clearly not possible. @MonkeyBonkey: don't forget to mark this as answered. – 4F2E4A2E Feb 18 '18 at 21:29
  • 11
    That this is not possible is a major flaw from a developer perspective. I hope they had a good reason to set it up like this. Leads to very WET code. – Imre_G Aug 09 '18 at 17:32
  • "All types within a GraphQL schema must have unique names. No two provided types may have the same name." [GraphQL / Schema](http://spec.graphql.org/October2021/#sec-Schema) – Edward Brey Jun 29 '22 at 12:19
12

While working on a project I had a similar problem with code duplication between input and type objects. I did not find the extend keyword very helpful as it only extended the fields of that specific type. So the fields in type objects cannot not be inherited in input objects.

In the end I found this pattern using literal expressions helpful:

const UserType = `
    name: String!,
    surname: String!
`;

const schema = graphql.buildSchema(`
    type User {
        ${UserType}
    }
    input InputUser {
        ${UserType}
    }
`) 
spedy
  • 2,200
  • 25
  • 26
  • 2
    This is the best solution I think. Personally though I prefer to use a file.graphql because its distinctive and removes complication. I think the creators of graphql should be working on a solution to share the data between type and input via a variable type. – Steve Tomlin Aug 17 '20 at 06:45
  • @SteveTomlin do you know how to solve it in .graphql files? I can't find any reference – Roy Leibovitz Dec 28 '21 at 09:51
  • Sure. An example: import * as schemaGame from './_game.graphql'; – Steve Tomlin Dec 28 '21 at 20:30
6

You can do something like this:

export const createTypes = ({name, fields}) => {
  return {
    inputType: new InputObjectType({name: `${name}InputType`, fields}),
    objectType: new ObjectType({name: `${name}ObjectType`, fields})
  };
};

const reservation = createTypes({
  name: "Reservation",
  fields: () => ({
    hotelId: { type: IntType },
    rooms: { type: new List(RoomType) },
    totalCost: { type: new NonNull(CurrencyType) },
    status: { type: new NonNull(ReservationStatusType) }
  })
});
// now you can use:
//  reservation.inputType
//  reservation.objectType
Pankaj Vishwani
  • 332
  • 1
  • 4
  • 9
1

this is something that i did for my project (works good):

const RelativeTemplate = name => {
  return {
    name: name,
    fields: () => ({
      name: { type: GraphQLString },
      reference: { type: GraphQLString }
    })
  };
};
const RelativeType = {
  input: new GraphQLInputObjectType(RelativeTemplate("RelativeInput")),
  output: new GraphQLObjectType(RelativeTemplate("RelativeOutput"))
};
AW_
  • 51
  • 6