I have 2 models, company & user. From a db perspective a company has many users. When creating a single user, I want to take advantage of the power of graphQL by returning the company associated with the user. However, this only works when doing a query. When attempting a mutation, the object is mutated, but the requested relational data always returns null
In the Model we declare a one -> many relationship and include the company model schema in our user model schema to access the data
User Model Schema
type User {
clients: [Client!]
company: Company <------- Company Relation
companyId: UUID
confirmed: Boolean
defaultPortfolioSize: Int
email: String!
firstName: String!
lastLogin: String!
lastName: String!
id: UUID!
isActive: Boolean
isStaff: Boolean
isSuperuser: Boolean
password: String
phoneNumber: String
priceNotification: Boolean
priceThreshold: Float
sentimentNotification: Boolean
sentimentThreshold: Float
token: String
clientCount: Int
notificationCount: Int
portfolioCount: Int
stockAverageCount: Float
totalValue: Float
stockList: [PortfolioStock!]
}
In the user mutation, we pass a company id which we use to connect the user to the associated company object
User Mutation
user(
companyId: UUID <---- Company ID for relation
confirmed: Boolean
defaultPortfolioSize: Int
delete: Boolean
email: String
firstName: String
lastName: String
id: UUID
isActive: Boolean
isStaff: Boolean
isSuperuser: Boolean
password: String
phoneNumber: String
priceNotification: Boolean
priceThreshold: Float
sentimentNotification: Boolean
sentimentThreshold: Float
username: String
): User!
The resolver is pretty straightforward. We verify authorization and then continue the request.
User Mutation Resolver
user: async (_, params, { user }) => {
if (params.id) {
await authorize(user, Permission.MODIFY_USER, { userId: params.id });
} else {
// Anyone can register
}
return await userDataLoader.upsertUser(user, params);
},
The dataloader is where the magic happens. We call upsertUser to create, update, and delete any object. Here we successfully create a user and can verify in the db the creation.
User Dataloader
upsertUser: async (user, params) => {
...
/* Register */
if (!params.companyId) {
throw new UserInputError("Missing 'companyId' parameter");
}
if (!params.password) {
throw new UserInputError("Missing 'password' parameter");
}
let newUser = new User({
billingAddressId: 0,
dateJoined: new Date(),
defaultPortfolioSize: 45,
isActive: true,
isStaff: false,
isSuperuser: false,
lastLogin: new Date(),
phoneNumber: '',
priceNotification: false,
priceThreshold: 0,
sentimentNotification: false,
sentimentThreshold: 0,
subscriptionStatus: false,
...params,
});
newUser = await newUser.save();
newUser.token = getJWT(newUser.email, newUser.id);
EmailManager(
EmailTemplate.CONFIRM_ACCOUNT,
`${config.emailBaseUrl}authentication/account-confirmation/?key=${
newUser.token
}`,
newUser.email
);
return newUser;
},
// Including the users query dataloader for reference
users: async params => {
return await User.findAll(get({ ...defaultParams(), ...params }));
},
Here is an example mutation where we create a user object and request a response with the nested company relation.
Example Mutation
mutation {
user(
companyId: "16a94e71-d023-4332-8263-3feacf1ad4dc",
firstName: "Test"
lastName: "User"
email: "[email protected]"
password: "PleaseWork"
) {
id
company {
id
name
}
email
firstName
lastName
}
}
However, when requesting the relation to be included in the response object, the api returns null rather than the object.
Example Response
ACTUAL:
{
"data": {
"user": {
"id": "16a94e71-d023-4332-8263-3feacf1ad4dc",
"company": null,
"email": "[email protected]",
"firstName": "Test",
"lastName": "User"
}
}
}
EXPECTED:
{
"data": {
"user": {
"id": "16a94e71-d023-4332-8263-3feacf1ad4dc",
"company": {
"id": "16a94e71-d023-4332-8263-3feacf1ad4dc",
"name": "Test Company",
},
"email": "[email protected]",
"firstName": "Test",
"lastName": "User"
}
}
}
I suppose I am slightly confused as to why graphQL cannot graph my nested object during a mutation, but can do so via a query.
newUser
data is incomplete and doesn't hold thecompany
information. Ideally, you'd want to have a specific resolver for thecompany
field on theUser
type and implement your association that way ; this would guarantee that the resolver would always run whenever you return a typeUser
(if thecompany
field is queried, of course). – Thomas Hennes