2
votes

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?

2

2 Answers

6
votes

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.

1
votes

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:

  1. Define 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
    }
  `,
};
  1. Next, you need to override the 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 it's 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!