Reputation: 121
Let's say I have 'firstName' and 'lastName' properties on an Author type schema created as Strapi content type.
I am able to query them with graphql, but what if I want to query 'fullName' property without adding that field on my content type?
As field doesn't exist, now it says: Cannot query field \"fullName\" on type \"Author\".
How can I extend existing type schema with that additional "virtual" field?
Upvotes: 3
Views: 4344
Reputation: 418
Just found this post and also found the appropriate solution. This example repo demonstrates how to use a service function with custom controller methods and a custom GraphQL schema to get what you want. I just implemented the same in my own project.
Your case would not need a service function. You just need to do 2 things:
fullName
property in /api/authors/config/schema.graphql.js
like below:module.exports = {
definition:
extend type Author {
fullName: AuthorFullName
}
type AuthorFullName {
firstName: String
lastName: String
}
`,
};
find
and findOne
controller methods for Author
like below:module.exports = {
async find( ctx ) {
let entities;
if ( ctx.query._q ) {
entities = await strapi.services.author.search( ctx.query );
} else {
entities = await strapi.services.author.find( ctx.query );
}
// Add computed field `fullName` to entities.
entities.map( entity => {
entity.fullName = `${entity.firstName} ${entity.lastName}`;
return entity;
} );
return entities.map( entity => sanitizeEntity( entity, { model: strapi.models.author } ) );
},
async findOne( ctx ) {
const { id } = ctx.params;
let entity = await strapi.services.author.findOne( { id } );
if ( ! entity ) {
return ctx.notFound();
}
// Add computed field `fullName` to entity.
entity.fullName = `${entity.firstName} ${entity.lastName}`;
return sanitizeEntity( entity, { model: strapi.models.author } );
},
};
This allows REST API calls to get the fullName
returned and also tells GraphQL to include fullName
in its schema as well, so find
and findOne
can pass it along to GraphQL properly.
I hope this helps because I feel like I just leveled up big time after learning this!
Upvotes: 2
Reputation: 121
I managed to do it with the following code in the schema.graphql file placed inside the api/author/config folder:
module.exports = {
definition: `type AuthorOverride {
firstName: String
lastName: String
fullName: String
}`,
query: `
authors: [AuthorOverride]
`,
type: {
Author: false
},
resolver: {
Query: {
authors: {
description: 'Return the authors',
resolver: 'Author.find'
}
}
}
};
The key was to define schema with additional field while using different type name (AuthorOverride) to avoid duplicate type error.
Also, to set type: { Author: false } so that original type won't be queriable.
Now, inside my resolver function 'Author.find' (placed in my Author.js controller) I can map fullName value.
If someone has a more appropriate solution for extending graphql schema in Strapi, feel free to post it.
Upvotes: 6
Reputation: 379
For Strapi v4 things have changed. See extending-the-schema
For example I extended UploadFile by doing:
"use strict";
module.exports = {
/**
* An asynchronous register function that runs before
* your application is initialized.
*
* This gives you an opportunity to extend code.
*/
register({ strapi }) {
const extension = ({ nexus }) => ({
types: [
nexus.extendType({
type: "UploadFile",
definition: (t) => {
t.string("path", {
description: "Path to file on its host.",
resolve: (root, args, ctx) => (new URL(root.url).pathname),
});
},
}),
],
});
strapi.plugin("graphql").service("extension").use(extension);
},
/**
* An asynchronous bootstrap function that runs before
* your application gets started.
*
* This gives you an opportunity to set up your data model,
* run jobs, or perform some special logic.
*/
bootstrap(/*{ strapi }*/) {},
};
Upvotes: 0
Reputation: 6595
None of these worked for me. From the docs, it looks like they've changed v4
to extend in a global context rather than per module/entity (src/api/...
). Like so:
// src/index.js
"use strict";
module.exports = {
register({ strapi }) {
const extensionService = strapi.plugin("graphql").service("extension");
const extension = () => ({
typeDefs: `
type Author {
fullName: String
}
`,
resolvers: {
Author: {
fullName(author) {
return strapi.service("api::author.author").getFullName(author);
},
},
},
});
extensionService.use(extension);
},
};
This could be wrong and I hope it is. It is a step backend in my eyes, architecturally speaking. But, you could always add this logic to a file in each entity folder (such as src/api/graphql/index.js
), then import it to this global file. A bit like what happening with the service logic above to keep the separation of concerns. It's just a bit 'manual'.
For those that want to see what strapi.service("api::author.author").getFullName(author)
is calling:
// src/api/author/services/author.js
"use strict";
const { createCoreService } = require("@strapi/strapi").factories;
module.exports = createCoreService("api::author.author", () => ({
getFullName(author) {
return `${author.firstName} ${author.lastName}`;
},
}));
Upvotes: 3