End to End React with Prisma 2

Start at the beginning and work your way through this project. The code for each step as well as the finished project can be found in the Github repository.


    8. Add Nested Author information

    Objective: Incorporate author information into our feeds and bundles.

    Now that we have feeds and bundles, let's first associate authors with them so we know who wrote them. In our schema.prisma file we have Users as a separate database table so we will need to establish a relationship between the new bundle or feed and an author.

    First go to utils/api/typeDefs.ts and add author to the Feed and Bundle definitions and then add a User definition:

    graphql

    type Feed {
    id: String
    name: String
    url: String
    author: User
    }
    type Bundle {
    id: String
    name: String
    description: String
    author: User
    }
    type User {
    id: String
    auth0: String
    nickname: String
    picture: String
    bundles: [Bundle]
    feeds: [Feed]
    }

    Now, we need to go to our resolvers file and update our create mutations:

    utils/api/resolvers.ts

    ts

    createFeed: async (parent, { data }, { prisma, user }) => {
    const author = { author: { connect: { id: user.id } } };
    const result = await prisma.feed.create({ data: { ...data, ...author } });
    return result;
    },
    createBundle: async (parent, { data }, { prisma, user }) => {
    const author = { author: { connect: { id: user.id } } };
    const result = await prisma.bundle.create({
    data: { ...data, ...author },
    });
    return result;
    },

    We can see that we are creating an author object which has this syntax of connect. This means that it will connect a particular feed to a user that we already have in our database. Where is the user being created in our database? Well, recall that in our context function from step 5 that we will create a user object for each logged in user that makes any request against our api if it already isn't present. This ensures that we have a user item to connect with when we create the feed or bundle object.

    While we are building the backend though, this actually won't work for us because we use the frontend to create the session that the backend uses and we won't get to that until the frontend section of this course. For now, let's hard code a fake user account that will allow us to develop and then we can switch back this code once we are on the frontend.

    To do this, comment out the auth0.getSession line in the context function and replace it with the following fake user account. You may download the /blank.png and place it in the public/ directory in the root of the project.

    Click to download the blank file

    /utils/api/context.ts

    ts

    // const { user: auth0User } = await auth0.getSession(req);
    // Comment this out after the frontend is built
    const auth0User = { nickname: 'faker', sub: '1', picture: '/blank.png' }

    Now, try creating a second create feed mutation like we did before except this time, we can add the author fragment to the mix:

    ts

    mutation createFeedMutation($data: FeedCreateInput) {
    createFeed(data: $data) {
    ...FeedFragment
    }
    }
    fragment FeedFragment on Feed {
    id
    name
    url
    author {
    ...AuthorFragment
    }
    }
    fragment AuthorFragment on User {
    id
    auth0
    picture
    nickname
    }

    Here are the variables, note that we need to change both the url and the id to something other than what we sent the first time or we will get an error because they are supposed to be unique.

    json

    {
    "data": {
    "name": "CNN Feed",
    "url": "http://rss.cnn.com/rss/cnn_topstories.rss",
    "id": "2"
    }
    }

    When that successfully runs, we should see a return that now has author nested within the feed but we'll instead see that the author field returns null. Why is that?

    It turns out that it is due to the fact that the author information is contained within a different model. Prisma 2 does provide a way to include nested relations, but you have to opt into them specifically- they do not simply conform to the shape of the query coming in. This means that even if we could hard-code the prisma return to include author, it wouldn't help us if we decided to look at how many bundles that particular author owned, as an example.

    Fortunately, graphQL provides an excellent way to solve this, so we can populate exactly the nested fields we need at an arbitrary depth. What we need to do is add nested definitions at the resolver level. To make it specific, if we want to make sure that the field author on feed returns the author object, we define the Feed model in the resolvers block and write a function to tell it how to look up that information using Prisma 2, like this:

    utils/api/resolvers.ts

    ts

    export const resolvers = {
    Feed: {
    author: ({ id }, args, { prisma }) => {
    const modelResponse = await prisma.feed.findOne({
    where: { id },
    include: { author: true },
    })
    return modelResponse.author
    },
    },
    }

    We tell it to lookup the author information by using the include and then once we find that feed object, we just return the author portion of it. We utilize the id field in the parent object, which in this case is of the feed object.

    This all works great for looking up the author, but how can we make it more generic? We can create a function to generate that function on the fly:

    utils/api/resolvers.ts

    ts

    const createFieldResolver = (modelName, parName) => ({
    [parName]: async ({ id }, args, { prisma }) => {
    const modelResponse = await prisma[modelName].findOne({
    where: { id },
    include: { [parName]: true },
    })
    return modelResponse[parName]
    },
    })

    Then we call it for the nested fields within our models:

    ts

    export const resolvers {
    Feed: {
    ...createFieldResolver('feed', 'author'),
    },
    Bundle: {
    ...createFieldResolver('bundle', 'author'),
    },
    }

    Now try the feeds query again:

    graphql

    {
    feeds {
    id
    url
    author {
    id
    }
    }
    }

    You should see that author is now being populated within the returned feeds in the case where an author is present.