Correlating Fields to Columns

You'll need to provide a bit of information about each column and its relationship to the table, if any. For fields with a one-to-one correspondence, use the sqlColumn property.

const User = new GraphQLObjectType({
  //...
  fields: () => ({
    id: {
      // the column name is assumed to be the same as the field name
      type: GraphQLInt
    },
    email: {
      type: GraphQLString,
      extensions: {
        joinMonster: {
          // if the column name is different, it must be specified
          sqlColumn: 'email_address'
        }
      }
    },
    idEncoded: {
      description: 'The ID base-64 encoded',
      type: GraphQLString,
      extensions: {
        joinMonster: {
          sqlColumn: 'id'
        }
      },
      // this field uses a sqlColumn and applies a resolver function on the value
      // if a resolver is present, the `sqlColumn` MUST be specified even if it is the same name as the field
      resolve: user => toBase64(user.id)
    }
  })
})

function toBase64(clear) {
  return Buffer.from(String(clear)).toString('base64')
}

In the case of the id field, the sqlColumn was omitted. Since it has no resolver, it is assumed to have to come from the table and the column name is assumed to be the same as the field name. The same inference is not made if a resolver is present.

Note

Some external libraries add resolvers to your schema, such as Optics, or the logger option in graphql-tools. If using one of these, sqlColumn cannot be omitted.

Computed Columns

You can manipulate the data in your query without losing the benefit of batched requests.

Maybe your field(s) needs a SQL column to compute a value. If there isn't a simple one-to-one correspondence of columns to field, you can use sqlDeps. sqlDeps is an array of columns that will get retrieved if the GraphQL field is requested. These are exposed to your resolver, so you can write a resolve function to compute a value in JavaScript. For example, a first_name and last_name column can be depended on for a fullName field in your API.

const User = new GraphQLObjectType({
  //...
  fields: () => ({
    fullName: {
      description: "A user's first and last name",
      type: GraphQLString,
      extensions: {
        joinMonster: {
          // perhaps there is no 1-to-1 mapping of field to column
          // this field depends on multiple columns
          sqlDeps: ['first_name', 'last_name']
        }
      },
      resolve: user => `${user.first_name} ${user.last_name}`
    }
  })
})

You can also do computed columns in the SQL itself with a raw expression using sqlExpr. This is a function that generated the expression. Its parameters are the table alias (generated automatically by joinMonster), the GraphQL arguments on that field, and a context object.

const User = new GraphQLObjectType({
  //...
  fields: () => ({
    capitalizedLastName: {
      type: GraphQLString,
      extensions: {
        joinMonster: {
          // do a computed column in SQL with raw expression
          sqlExpr: (table, args) => `UPPER(${table}.last_name)`
        }
      }
    },
    fullNameAnotherWay: {
      description: 'Another way we can get the full name.',
      type: GraphQLString,
      extensions: {
        joinMonster: {
          sqlExpr: table => `${table}.first_name || ' ' || ${table}.last_name`
        }
      }
    }
  })
})