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.


    9. Add FeedTag and BundleTag relations

    Objective: Create tags for our feeds and bundles.

    Next, let's add tags to Bundles and Feeds. Recall, that in the schema.prisma file we have FeedTags and BundleTags and they are actually separate tables in our database so we will need relations to hook them up to our feeds and bundles.

    We will need to update the typeDefs file in order to let graphQL know that both feeds and bundles can have tags and also when we create either a feed or a bundle that we can attach an existing tag and/or create a new tag along with it.

    This ability to either attach or create new tags all in one go is part of what makes Prisma so amazing. The syntax for creating a new tag is create while attaching an existing one is attach. Below is what the updated typeDefs file looks like- warning it is pretty verbose but the only reason why is that we need to create new types for all of the nested create/attach types for both the feed and bundle items.

    Next, we should add the feeds object on the bundle. We are limiting the input parameter so that we can only add existing feeds to a new bundle but that once created, you can always look up how many feeds are in a bundle and how many bundles have a particular feed in it.

    utils/api/typeDefs.ts

    ts

    import { gql } from 'apollo-server-micro'
    export const typeDefs = gql`
    type Feed {
    id: String
    name: String
    url: String
    author: User
    tags: [FeedTag]
    bundles: [Bundle]
    }
    type Bundle {
    id: String
    name: String
    description: String
    author: User
    tags: [BundleTag]
    feeds: [Feed]
    }
    type User {
    id: String
    auth0: String
    nickname: String
    picture: String
    bundles: [Bundle]
    feeds: [Feed]
    }
    type BundleTag {
    id: String
    name: String
    bundles: [Bundle]
    }
    type FeedTag {
    id: String
    name: String
    feeds: [Feed]
    }
    input FeedInput {
    id: String
    }
    input BundleInput {
    id: String
    }
    input FeedCreateInput {
    id: String
    url: String
    name: String
    tags: NestedFeedTagCreateInput
    }
    input NestedFeedTagCreateInput {
    create: [FeedTagCreateInput]
    connect: [FeedTagWhereUniqueInput]
    }
    input FeedTagCreateInput {
    id: String
    name: String
    }
    input FeedTagWhereUniqueInput {
    id: String
    name: String
    }
    input BundleCreateInput {
    id: String
    name: String
    description: String
    tags: NestedBundleTagCreateInput
    feeds: NestedBundleFeedCreateInput
    }
    input NestedBundleTagCreateInput {
    create: [BundleTagCreateInput]
    connect: [BundleTagWhereUniqueInput]
    }
    input BundleTagCreateInput {
    id: String
    name: String
    }
    input BundleTagWhereUniqueInput {
    id: String
    name: String
    }
    input NestedBundleFeedCreateInput {
    create: [FeedCreateInput]
    connect: [FeedWhereUniqueInput]
    }
    input FeedWhereUniqueInput {
    id: String
    url: String
    }
    type Query {
    hello: String
    feed(data: FeedInput): Feed
    bundle(data: BundleInput): Bundle
    feeds: [Feed]
    bundles: [Bundle]
    }
    type Mutation {
    clearAll: String
    createFeed(data: FeedCreateInput): Feed
    createBundle(data: BundleCreateInput): Bundle
    }
    `

    utils/api/resolvers.ts

    ts

    export const resolvers = {
    Feed: {
    ...createFieldResolver('feed', 'author'),
    ...createFieldResolver('feed', 'tags'),
    ...createFieldResolver('feed', 'bundles'),
    },
    Bundle: {
    ...createFieldResolver('bundle', 'author'),
    ...createFieldResolver('bundle', 'tags'),
    ...createFieldResolver('bundle', 'feeds'),
    },
    BundleTag: {
    ...createFieldResolver('bundleTag', 'bundles'),
    },
    FeedTag: {
    ...createFieldResolver('feedTag', 'feeds'),
    },
    Query: {
    hello: (parent, args, context) => `hi!`,
    feed: (parent, { data: { id } }, { prisma }) =>
    prisma.feed.findOne({ where: { id } }),
    bundle: (parent, { data: { id } }, { prisma }) =>
    prisma.bundle.findOne({ where: { id } }),
    feeds: (parent, args, { prisma }) => prisma.feed.findMany(),
    bundles: (parent, args, { prisma }) => prisma.bundle.findMany(),
    },
    Mutation: {
    clearAll: async (parent, args, { prisma }) => {
    await prisma.savedArticle.deleteMany()
    await prisma.bundle.deleteMany()
    await prisma.feed.deleteMany()
    await prisma.bundleTag.deleteMany()
    await prisma.feedTag.deleteMany()
    await prisma.user.deleteMany()
    return 'done!'
    },
    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
    },
    },
    }

    Let's do a sanity check and make sure all these relations are working the way that we think they are. You'll notice that I snuck in a new mutation called clearAll. This is an easy way to delete the database so we'll have a clean slate. We'll use this here- call the mutation mutation { clearAll } and you should see a response of all done!.

    ts

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

    json

    {
    "data": {
    "name": "CNN Feed",
    "url": "http://rss.cnn.com/rss/cnn_topstories.rss",
    "id": "1",
    "tags": {
    "create": [
    { "id": "100", "name": "News" },
    { "id": "101", "name": "TV" }
    ]
    }
    }
    }

    We should see that a large block returns with the feed, author, and 2 feedTags in it. For illustrative purposes, let's also see what this would look like if we were to use a feed that already exists. Run the following mutation next:

    json

    {
    "data": {
    "name": "NY Times",
    "url": "https://rss.nytimes.com/services/xml/rss/nyt/World.xml",
    "id": "2",
    "tags": {
    "connect": [{ "id": "100" }, { "id": "101" }],
    "create": [{ "id": "102", "name": "Worldwide" }]
    }
    }
    }

    In the response we should see that there are 3 tags- the two existing News and TV along with the new Worldwide one.

    Finally, let's try creating a bundle with these 2 feeds attached to it.

    graphql

    mutation createBundleMutation($data: BundleCreateInput) {
    createBundle(data: $data) {
    ...BundleFragment
    }
    }
    fragment BundleFragment on Bundle {
    id
    name
    description
    feeds {
    id
    name
    }
    }

    In the variables we are connecting to the 2 feeds that we've already created and then creating a new bundleTag with the name "World News". Since bundleTags and feedTags are separate items, any tags you create in one are not available in the other and you'll need to recreate them.

    json

    {
    "data": {
    "name": "hi",
    "description": "This bundle is about fun stuff",
    "id": "10",
    "tags": {
    "create": [{ "id": "201", "name": "World News" }]
    },
    "feeds": {
    "connect": [{ "id": "1" }, { "id": "2" }]
    }
    }
    }

    This mutation should return successfully and you should see a new bundle connected to a new bundle tag and 2 existing feeds.