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: Stringname: Stringurl: Stringauthor: Usertags: [FeedTag]bundles: [Bundle]}type Bundle {id: Stringname: Stringdescription: Stringauthor: Usertags: [BundleTag]feeds: [Feed]}type User {id: Stringauth0: Stringnickname: Stringpicture: Stringbundles: [Bundle]feeds: [Feed]}type BundleTag {id: Stringname: Stringbundles: [Bundle]}type FeedTag {id: Stringname: Stringfeeds: [Feed]}input FeedInput {id: String}input BundleInput {id: String}input FeedCreateInput {id: Stringurl: Stringname: Stringtags: NestedFeedTagCreateInput}input NestedFeedTagCreateInput {create: [FeedTagCreateInput]connect: [FeedTagWhereUniqueInput]}input FeedTagCreateInput {id: Stringname: String}input FeedTagWhereUniqueInput {id: Stringname: String}input BundleCreateInput {id: Stringname: Stringdescription: Stringtags: NestedBundleTagCreateInputfeeds: NestedBundleFeedCreateInput}input NestedBundleTagCreateInput {create: [BundleTagCreateInput]connect: [BundleTagWhereUniqueInput]}input BundleTagCreateInput {id: Stringname: String}input BundleTagWhereUniqueInput {id: Stringname: String}input NestedBundleFeedCreateInput {create: [FeedCreateInput]connect: [FeedWhereUniqueInput]}input FeedWhereUniqueInput {id: Stringurl: String}type Query {hello: Stringfeed(data: FeedInput): Feedbundle(data: BundleInput): Bundlefeeds: [Feed]bundles: [Bundle]}type Mutation {clearAll: StringcreateFeed(data: FeedCreateInput): FeedcreateBundle(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 {idnameurltags {...FeedTagFragment}author {...AuthorFragment}}fragment AuthorFragment on User {idauth0picturenickname}fragment FeedTagFragment on FeedTag {idname}
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 {idnamedescriptionfeeds {idname}}
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.