How to use nested models correctly on GraphQLSchemaPlugin?

Hi all, I’m trying to create an API with webiny and encountered a problem regarding nested models.

Here’s my model called Lyric:

import LyricLine from "./lyricLine.model";
import Artist from "./artist.model";

/**
 * A simple "Lyric" data model, that consists of a couple of simple fields.
 */
export default ({ createBase }) =>
    pipe(
        withName("Lyric"),
        withFields(() => ({
            title: string({ validation: validation.create("required,minLength:3,maxLength:100") }),
            about: string({ validation: validation.create("maxLength:500") }),
            embeddedURL: string({ validation: validation.getValidator('url') }),
            userId: string({ validation: validation.create("required")}),
            lyricLines: fields({ instanceOf: LyricLine }),
            artists: fields({ instanceOf: Artist }),
            views: number({ value: 0 }),
            releaseAt: date(),
            createdAt: date(),
        })),
        withHooks({
        }),
        withProps({
        })
    )(createBase());

Here’s the one called LyricLine (which is used as a nested model above):

/**
 * Lyric line data model. Contains a single line of a lyrics
 */
export default ({ createBase }) =>
    pipe(
        withName("LyricLine"),
        withFields(() => ({
            line: string({ validation: validation.create("required,minLength:1,maxLength:500") }),
            description: string({ validation: validation.create("maxLength:5000") }),
            descriptionUserId: string(),
            createdAt: date(),
        })),
        withHooks({
            beforeSave() {
                console.log('before save');
            }
        }),
        withProps({
        })
    )(createBase());

Here are the schema and resolver:


const lyricFetcher = ctx => ctx.models.Lyric;

const plugin: GraphQLSchemaPlugin = {
    type: "graphql-schema",
    name: "graphql-schema-lyrics",
    schema: {
        typeDefs: gql`
            type LyricDeleteResponse {
                data: Boolean
                error: LyricError
            }

            type LyricCursors {
                next: String
                previous: String
            }

            type LyricListMeta {
                cursors: LyricCursors
                hasNextPage: Boolean
                hasPreviousPage: Boolean
                totalCount: Int
            }

            type LyricError {
                code: String
                message: String
                data: JSON
            }

            type Lyric {
                id: ID
                title: String
                about: String
                lyricLines: LyricLine!
                artists: Artist!
                embeddedURL: String
                views: Number
                createdAt: DateTime
                releaseAt: DateTime
            }

            input LyricInput {
                id: ID
                title: String!
                embeddedURL: String!
                about: String
                lyricLines: [LyricLineInput]!
                artists: [ArtistInput]!
                releaseAt: DateTime
            }

            input LyricListWhere {
                title: String
                views: Number
                createdAt: Int
                releaseAt: Int
            }

            input LyricListSort {
                title: Int
                views: Number
                createdAt: Int
                releaseAt: Int
            }

            type LyricResponse {
                data: Lyric
                error: LyricError
            }

            type LyricLineResponse {
                data: LyricLine
                error: LyricError
            }

            type ArtistResponse {
                data: Artist
                error: LyricError
            }

            type LyricListResponse {
                data: [Lyric]
                meta: LyricListMeta
                error: LyricError
            }

            type LyricQuery {
                getLyric(id: ID): LyricResponse

                listLyrics(
                    where: LyricListWhere
                    sort: LyricListSort
                    limit: Int
                    after: String
                    before: String
                ): LyricListResponse
            }

            type LyricMutation {
                createLyric(data: LyricInput!): LyricResponse
                createLyricLine(data: LyricInput!): LyricResponse

                updateLyric(id: ID!, data: LyricInput!): LyricResponse

                deleteLyric(id: ID!): LyricDeleteResponse
            }

            extend type Query {
                lyrics: LyricQuery
            }

            extend type Mutation {
                lyrics: LyricMutation
            }

            type LyricLine {
                id: ID
                line: String
                description: String
                descriptionUserId: String
                createdAt: DateTime
            }

            input LyricLineInput {
                id: ID
                line: String!
                description: String
                descriptionUserId: String
                createdAt: DateTime
            }

            type Artist {
                id: ID
                name: String
                realName: String
                description: String
                descriptionUserId: String
                picture: String
                views: Int
                isVerified: Boolean
                createdAt: DateTime
            }

            input ArtistInput {
                id: ID
                name: String!
                realName: String
                description: String
                descriptionUserId: String
                picture: String
                isVerified: Boolean
            }
        `,
        resolvers: {
            Query: {
                lyrics: emptyResolver
            },
            Mutation: {
                lyrics: emptyResolver
            },
            LyricQuery: {
                getLyric: hasScope("lyrics:get")(resolveGet(lyricFetcher)),
                listLyrics: hasScope("lyrics:list")(resolveList(lyricFetcher))
            },
            LyricMutation: {
                createLyric: hasScope("lyrics:create")(resolveCreate(lyricFetcher)),
                updateLyric: hasScope("lyrics:update")(resolveUpdate(lyricFetcher)),
                deleteLyric: hasScope("lyrics:delete")(resolveDelete(lyricFetcher))
            }
        }
    }
};

When I use the createLyric mutation, it runs, but with the following error:

{
  "data": {
    "lyrics": {
      "createLyric": {
        "data": null,
        "error": {
          "data": null,
          "message": "instanceOf is not a constructor",
          "code": ""
        }
      }
    }
  }
}

I’d like to know what went wrong :slight_smile:

Thanks to @Adrian_Smijulj, I’ve resolved the issue :grinning:

The problem was the following 2 lines:

import LyricLine from "./lyricLine.model";
import Artist from "./artist.model";

So, Instead of directly importing other models, we have to inject it via context. I made some changes to my models.ts and everything went fine! :grinning: (it’s passing LyricLine and Artist models into Lyrics)

        const LyricLine = lyricLine({ createBase });
        const Artist = artist({ createBase });

        context.models = {
            LyricLine,
            Artist,
            Lyric: lyric({ createBase, LyricLine, Artist }),
            createBase
        };
1 Like