I am building a B2B wholesale site using Nextjs and Apollo Client for the front-end, with Keystonejs running the backend. This question is more for the backend and setting up the schema for Keystonejs.
This site is based off of a tutorial from Wes Bos, https://advancedreact.com/. I was hoping to expand upon the idea and have been mostly successful thus far. Until I realized that product entry was not going to be as easy as his example.
First off excuse my ignorance with the backend of this project as I may use the wrong terminology. I say schema but maybe I mean database design? I know Keystone refers to them also as lists. So Product, CartItem, Order are all lists. Like so:
export const Product = list({
fields: {
name: text({ validation: { isRequired: true } }),
slug: text({ isIndexed: 'unique', label: 'Pretty URL)'}),
hotdeal: checkbox({ label: 'Hot Deal?' }),
inventory: decimal(),
price: integer(),
category: relationship({
ref: 'Category.product',
}),
photo: relationship({
ref: 'ProductImage.product',
many: true,
ui: {
displayMode: 'cards',
cardFields: ['image', 'altText'],
inlineCreate: { fields: ['image', 'altText'] },
inlineEdit: { fields: ['image', 'altText'] },
},
}),
}
});
My products are going to have several categories. And within each category products will have different fields in Keystone. For example we may have a category for laptops. Then another category for t-shirts. The product fields for the laptop may be:
- Brand
- Model
- Name
- Price
- CPU
- Memory
- Screen size
T-shirt product fields may have:
- Name
- Size
- Color
- Material
- Price
You can see the only common fields they share are Name and Price. So having a schema for just product didn't work. Because filling out a form for a laptop that shows size, color and material wouldn't make sense. Nor would seeing a field for CPU/memory/screen size make sense when entering a new t-shirt.
I thought I could create separate schema for each category. So, the unique fields for each category did not show up in other categories. Then I would create a main Product schema file that had a relationship with each category. But to me it just sounds overly complex and not scalable as new categories may be added.
So how do I go about setting up the schema in a way that makes sense for unique products, so that product entry within Keystone isn't just a never-ending form with every possible product field? But a flexible and scalable approach. Maybe my tool is limited, and it just isn't possible with Keystone.
NOTE: This is not a full-blown ecommerce site. No transactions or sales will be made online. It will just handle the orders.
Here is a link to my repo. https://github.com/brudolph/green-mountain-cannabis/tree/main/backend
UPDATE: So with the answer @Molomby gave it had me thinking on how the relationship between the parent class Product
and the subclasses, ShirtProduct
and LaptopProduct
woul;d work.
So like you mention @Molomby I have the Product
class and ShirtProduct
and LaptopProduct
subclasses. With each subclass referencing the Product
class through the relationship
field. But it's just a one-sided relationship. To make it two-sided so Product
knows about the subclasses it seems I will need a relationship
field for each subclass in Product
(see example below). Am I correct in that assumption? Which doesn't sound very scalable. My client mentioned being able to add new categories which is possible since I have a Category
list but then I would need to create the new category class each time. So for example he adds "dog-food", I would need to create DogFoodProduct
class.
export const Product = list({
fields: {
name: text({ validation: { isRequired: true } }),
slug: text({ isIndexed: 'unique', label: 'Pretty URL)'}),
hotdeal: checkbox({ label: 'Hot Deal?' }),
inventory: decimal(),
price: integer(),
category: relationship({
ref: 'Category.product',
}),
shirt: relationship({
ref: 'ShirtProduct',
}),
laptop: relationship({
ref: 'LaptopProduct',
}),
dogFood: relationship({
ref: 'DogFoodProduct',
}),
photo: relationship({
ref: 'ProductImage.product',
many: true,
ui: {
displayMode: 'cards',
cardFields: ['image', 'altText'],
inlineCreate: { fields: ['image', 'altText'] },
inlineEdit: { fields: ['image', 'altText'] },
},
}),
}
});