objection-graphql icon indicating copy to clipboard operation
objection-graphql copied to clipboard

How to retrieve DB context in a Mutation with `extendWithMutations`

Open Trellian opened this issue 6 years ago • 7 comments

Hi @timhuff ,

Regarding mutations, I am ditching my embedded code approach and going with your excellent extendWithMutations() approach, but I have a couple of usage questions, please:

  1. What is the best way to setup and call extendWithMutations() for mutations on multiple models? I have multiple mutations per model, and obviously multiple models with this need. Should I do:
schema = mainModule
  .builder()
  .model(Person)
  .extendWithMutations(mutationTypesPerson)
  .model(Book)
  .extendWithMutations(mutationTypesBook)
  .build();    
  1. How do I get access to the database context in the mutation code, without creating a new DB connection for each query? Obviously, I need to make database calls inside the mutation, but I'm too dumb to see how to do it :o

Do you perhaps have a simple example?

TIA, Adrian

Trellian avatar Oct 10 '18 08:10 Trellian

I have tried:

import {
  GraphQLObjectType, 
  GraphQLNonNull, 
  GraphQLInputType,
  GraphQLInputObjectType,
  GraphQLString,
  GraphQLInt 
} from 'graphql'

// import { CvU}
//...
export const CvUserType = new GraphQLObjectType({
  name: 'CvUserType',
  description: 'Use this object to create new cvUser',
  fields: () => ({
    id: {
      type: new GraphQLNonNull(GraphQLInt),
      description: 'Record ID',
    },
    first_name: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'First Name',
    },
    last_name: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'Last Name',
    },
  }),
})

export const CreateCvUserInputType = new GraphQLInputObjectType({
  name: 'CreateCvUserInputType',
  description: 'CvUser',
  fields: () => ({
    first_name: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'First Name',
    },
    last_name: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'Last Name',
    },
  }),
})


export function createCvUser (builder) {
  const theBuilder = builder
  return new GraphQLObjectType({
    name: 'RootMutationType',
    description: 'Domain API actions',
    fields: () => ({
      createCvUser: {
        description: 'Creates a new cvUser',
        type: CvUserType,
        args: {
          input: { type: new GraphQLNonNull(CreateCvUserInputType )},
        },
        resolve: (root, inputCvUser) => {
          return theBuilder.insertAndFetch(inputCvUser.input)
        },
      },
    }),
  })
}

but I get insertAndFetch is not a function...

Please, even a slapped together answer will help!

Trellian avatar Oct 14 '18 12:10 Trellian

I notice in the mutationType in the example, that the first parameter for resolve: (root, inputPerson) is coming through as undefined, even though inputPerson is fine, and accessible.

Is this possibly a bug?

Trellian avatar Dec 27 '18 07:12 Trellian

Unsure if it is the same bug- but I haven't been able to get mutations working at all.

I get the error- Expected type CreatePersonType!, found \"test\".

My query looks like this:

mutation{
  createPerson(input:"test") {
    id
  }
}

That query looks wrong to me. Any ideas on how it should look?

yarnball avatar Dec 28 '18 22:12 yarnball

it probably needs to look more like this: mutation { createPerson(input: { firstName: "test" }) } input expects to receive an object with match fields.

That will work, but even better, declare the personType and createPersonInputType, initialise them via variables, and pass the inputType object.

Trellian avatar Dec 29 '18 05:12 Trellian

Thanks @Trellian that got it working.

Why is this example so verbose? Is there a way to write it simpler like the below example?

This is how I'd write it if I wasn't using objection-graphql

/schema.graphql

type Mutation {
	CreatePerson(firstName: String!): String
}

/resolvers.js

Mutation: {
		CreatePerson: async (_, { firstName }) => await PersonModel.query()
                      .allowInsert("[username]")
                      .insert({username})
	},

yarnball avatar Dec 29 '18 14:12 yarnball

@yarnball no problem :)

It can be done, as you say, without using an input object, it's just considered better style to use one, especially in large projects, to help manage changes to the interface. With an input object, you have one authoritative place to make changes to your interface, making maintenance easier and more reliable.

If you don't mind, would you please post your working code for reference? Other people will find it useful (especially me) :)

Trellian avatar Dec 30 '18 04:12 Trellian

@Trellian Sure, for my code I create a wrapper so I can easily pass it a set of options.

Forgive the field names- they probably won't make total sense.

I'm very open to feedback!

Only issue I had with the below, is I cannot find a way to pass more than one mutation. I tried (without success): mutationAction([mutation1, mutation2])

mutationAction(mutation1)
mutationAction(mutation2)
module.exports = mutationObj =>{
  mutationObj = mutate[mutationObj]
  return new GraphQLObjectType({
    name: mutationObj.docs_mutationName,
    description: mutationObj.docs_mutationDesc,
    fields: () => ({
      [mutationObj.mutationName]: {
        description: mutationObj.docs_actionTitle,
        type: new GraphQLObjectType({
          name: mutationObj.mutationName,
          description: mutationObj.docs_actionDescription,
          fields: () => mutationObj.returnFields
        }),
        args: {
          [mutationObj.inputFieldTitle]: {
            type: new GraphQLNonNull(
              new GraphQLInputObjectType({
                name: mutationObj.argumentName,
                description: mutationObj.argumentName,
                fields: () =>  mutationObj.inputFields
              })
            )
          }
        },
        resolve: mutationObj.resolver
      }
    })
  })
}

So I pass it an object like this:

	docs_mutationName:"userCreateType",
	docs_mutationDesc: "Domain API actions",
	docs_actionTitle: "Creates a new user",
	mutationName: "userCreate",
	docs_actionDescription:
		"Use this object to create new person",
	returnFields: {
		id: {
			description: "ID",
			type: new GraphQLNonNull(GraphQLInt)
		},
		username: {
			description: "Username",
			type: new GraphQLNonNull(GraphQLString)
		},
	},
	inputFieldTitle: "input",
	argumentName: "UserCreateType",
	inputFields: {
		username: {
			type: new GraphQLNonNull(GraphQLString),
			description: "Username"
		},
		title: {
			type: new GraphQLNonNull(GraphQLString),
			description: "Title"
		}
	},
	resolver: async (root, inputUser) => {
		const { username, title } = inputUser.input
		const user = await UserModel.query()
			.allowInsert("[username, title]")
			.insert({ username, title })
		return {
			id: user.id,
			username: `Successfuly created ${username} with title ${title}`
		}
	}

yarnball avatar Jan 09 '19 10:01 yarnball