This document outlines a comprehensive architectural plan for a GraphQL schema, focusing on an illustrative e-commerce platform use case. This plan encompasses the design of types, queries, mutations, subscriptions, resolver strategies, and integration considerations, providing a robust foundation for a scalable and maintainable GraphQL API.
The chosen domain is a typical e-commerce platform, enabling us to demonstrate a wide range of GraphQL features. Key entities and their relationships are:
Relationships:
The schema will be defined using the GraphQL Schema Definition Language (SDL).
Query: Entry point for reading data.Mutation: Entry point for writing, updating, or deleting data.Subscription: Entry point for real-time data updates.In addition to standard GraphQL scalars (String, Int, Float, Boolean, ID), we will define custom scalars for common patterns:
DateTime: Represents a date and time string (e.g., ISO 8601).PositiveInt: A custom scalar ensuring integer values are positive.Currency: Represents monetary values with currency codes.Each major entity will have a corresponding object type.
#### 2.5. Enums & Sorting/Filtering Enums for fixed values. Input enums for sorting and filtering.
graphql
type Mutation {
# --- User & Auth Mutations ---
registerUser(input: CreateUserInput!): User!
login(email: String!, password: String!): AuthPayload! # AuthPayload includes token, user
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
# --- Product Mutations (Admin only) ---
createProduct(input: CreateProductInput!): Product!
updateProduct(input: UpdateProductInput!
This document provides a detailed and production-ready GraphQL schema design for an e-commerce platform. It includes type definitions, queries, mutations, subscriptions, conceptual resolver structures, and integration examples for both server-side (Node.js with Apollo Server) and client-side (React with Apollo Client).
This GraphQL schema is designed for a robust e-commerce application, enabling clients to interact with various entities such as users, products, categories, reviews, shopping carts, and orders. The design prioritizes clarity, maintainability, and extensibility, leveraging GraphQL's strong typing system.
Key Features:
The following is the complete GraphQL Schema Definition Language (SDL) for our e-commerce platform.
# --- ENUMS ---
enum OrderStatus {
PENDING
PROCESSING
SHIPPED
DELIVERED
CANCELLED
}
enum UserRole {
CUSTOMER
ADMIN
}
# --- TYPES ---
type User {
id: ID!
email: String!
username: String!
firstName: String
lastName: String
roles: [UserRole!]!
address: Address
orders: [Order!]!
cart: Cart
reviews: [Review!]!
createdAt: String!
updatedAt: String!
}
type Address {
street: String!
city: String!
state: String!
zipCode: String!
country: String!
}
type Product {
id: ID!
name: String!
description: String
price: Float!
stock: Int!
category: Category!
imageUrls: [String!]!
reviews: [Review!]!
createdAt: String!
updatedAt: String!
}
type Category {
id: ID!
name: String!
products: [Product!]!
createdAt: String!
updatedAt: String!
}
type Review {
id: ID!
product: Product!
user: User!
rating: Int! # 1-5 stars
comment: String
createdAt: String!
updatedAt: String!
}
type Cart {
id: ID!
user: User!
items: [CartItem!]!
totalItems: Int!
totalAmount: Float!
}
type CartItem {
id: ID!
product: Product!
quantity: Int!
}
type Order {
id: ID!
user: User!
items: [OrderItem!]!
totalAmount: Float!
status: OrderStatus!
shippingAddress: Address!
paymentInfo: PaymentInfo!
createdAt: String!
updatedAt: String!
}
type OrderItem {
id: ID!
product: Product!
quantity: Int!
priceAtPurchase: Float! # Price at the time of order
}
type PaymentInfo {
cardType: String! # e.g., "Visa", "MasterCard"
last4: String! # Last 4 digits of the card
expiryMonth: Int!
expiryYear: Int!
}
# --- INPUT TYPES (for Mutations) ---
input AddressInput {
street: String!
city: String!
state: String!
zipCode: String!
country: String!
}
input CreateUserInput {
email: String!
username: String!
password: String! # In a real app, this would be hashed server-side
firstName: String
lastName: String
address: AddressInput
roles: [UserRole!] = [CUSTOMER] # Default role
}
input UpdateUserInput {
firstName: String
lastName: String
address: AddressInput
roles: [UserRole!]
}
input CreateProductInput {
name: String!
description: String
price: Float!
stock: Int!
categoryId: ID!
imageUrls: [String!]
}
input UpdateProductInput {
name: String
description: String
price: Float
stock: Int
categoryId: ID
imageUrls: [String!]
}
input CreateCategoryInput {
name: String!
}
input UpdateCategoryInput {
name: String
}
input CreateReviewInput {
productId: ID!
rating: Int!
comment: String
}
input AddToCartInput {
productId: ID!
quantity: Int!
}
input UpdateCartItemInput {
cartItemId: ID!
quantity: Int!
}
input PlaceOrderInput {
shippingAddress: AddressInput!
paymentInfo: PaymentInfoInput!
# In a real app, this might include a payment token from a payment gateway
}
input PaymentInfoInput {
cardType: String!
last4: String!
expiryMonth: Int!
expiryYear: Int!
# In a real app, sensitive payment details would be handled via a secure tokenization process
}
# --- QUERIES ---
type Query {
# User Queries
users(limit: Int = 10, offset: Int = 0): [User!]!
user(id: ID!): User
me: User # Get current authenticated user
# Product Queries
products(
categoryId: ID
minPrice: Float
maxPrice: Float
search: String
limit: Int = 10
offset: Int = 0
): [Product!]!
product(id: ID!): Product
# Category Queries
categories(limit: Int = 10, offset: Int = 0): [Category!]!
category(id: ID!): Category
# Review Queries
reviewsByProduct(productId: ID!, limit: Int = 10, offset: Int = 0): [Review!]!
review(id: ID!): Review
# Cart Queries
cart(userId: ID!): Cart # Or `myCart` if authentication is handled
myCart: Cart # Get current authenticated user's cart
# Order Queries
orders(userId: ID, status: OrderStatus, limit: Int = 10, offset: Int = 0): [Order!]!
order(id: ID!): Order
myOrders: [Order!]! # Get current authenticated user's orders
}
# --- MUTATIONS ---
type Mutation {
# User Mutations
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
# Product Mutations (Admin only typically)
createProduct(input: CreateProductInput!): Product!
updateProduct(id: ID!, input: UpdateProductInput!): Product!
deleteProduct(id: ID!): Boolean!
updateProductStock(productId: ID!, newStock: Int!): Product! # Specific mutation for stock
# Category Mutations (Admin only typically)
createCategory(input: CreateCategoryInput!): Category!
updateCategory(id: ID!, input: UpdateCategoryInput!): Category!
deleteCategory(id: ID!): Boolean!
# Review Mutations
createReview(input: CreateReviewInput!): Review!
updateReview(id: ID!, rating: Int, comment: String): Review!
deleteReview(id: ID!): Boolean!
# Cart Mutations
addToCart(input: AddToCartInput!): Cart!
updateCartItem(input: UpdateCartItemInput!): Cart!
removeCartItem(cartItemId: ID!): Cart!
clearCart: Cart!
# Order Mutations
placeOrder(input: PlaceOrderInput!): Order!
updateOrderStatus(orderId: ID!, status: OrderStatus!): Order! # Admin only
cancelOrder(orderId: ID!): Order! # User or Admin
}
# --- SUBSCRIPTIONS ---
type Subscription {
orderUpdated(orderId: ID!): Order!
productStockUpdated(productId: ID!): Product!
newReviewAdded(productId: ID!): Review!
}
Resolvers are functions that tell the GraphQL server how to fetch the data for a particular field. Each field in your schema needs a resolver. If a field's parent object has a resolver, the child field's resolver will receive the parent's resolved value.
Here's a conceptual structure for how resolvers would be organized, often within a resolvers.js or index.js file:
// resolvers.js
import {
users,
user,
me
} from './resolvers/userResolver';
import {
products,
product
} from './resolvers/productResolver';
// ... other query imports
import {
createUser,
updateUser,
deleteUser
} from './resolvers/userResolver';
import {
createProduct,
updateProduct,
deleteProduct,
updateProductStock
} from './resolvers/productResolver';
// ... other mutation imports
import {
orderUpdated,
productStockUpdated,
newReviewAdded
} from './resolvers/subscriptionResolver';
// ... other subscription imports
const resolvers = {
// Enum Resolvers (if needed for custom value mapping, rare)
OrderStatus: {
PENDING: 'PENDING',
PROCESSING: 'PROCESSING',
SHIPPED: 'SHIPPED',
DELIVERED: 'DELIVERED',
CANCELLED: 'CANCELLED',
},
UserRole: {
CUSTOMER: 'CUSTOMER',
ADMIN: 'ADMIN',
},
// Type Resolvers (for nested fields or computed fields)
User: {
// Example: Resolve 'orders' field of a User type
// This resolver will be called if a query specifically asks for `user.orders`
orders: async (parent, args, context, info) => {
// 'parent' here is the resolved User object
return context.dataSources.orderAPI.getOrdersByUserId(parent.id);
},
cart: async (parent, args, context, info) => {
return context.dataSources.cartAPI.getCartByUserId(parent.id);
},
reviews: async (parent, args, context, info) => {
return context.dataSources.reviewAPI.getReviewsByUserId(parent.id);
},
},
Product: {
category: async (parent, args, context, info) => {
// 'parent' is the resolved Product object
return context.dataSources.categoryAPI.getCategoryById(parent.categoryId);
},
reviews: async (parent, args, context, info) => {
return context.dataSources.reviewAPI.getReviewsByProductId(parent.id);
},
},
Category: {
products: async (parent, args, context, info) => {
return context.dataSources.productAPI.getProductsByCategoryId(parent.id);
},
},
Cart: {
items: async (parent, args, context, info) => {
// Assuming cart items are stored separately and linked by cart ID
return context.dataSources.cartAPI.getCartItems(parent.id);
},
totalItems: (parent) => parent.items.reduce((acc, item) => acc + item.quantity, 0),
totalAmount: (parent) => parent.items.reduce((acc, item) => acc + (item.product.price * item.quantity), 0),
},
CartItem: {
product: async (parent, args, context, info) => {
// 'parent' is the resolved CartItem object
return context.dataSources.productAPI.getProductById(parent.productId);
},
},
Order: {
items: async (parent, args, context, info) => {
return context.dataSources.orderAPI.getOrderItems(parent.id);
},
user: async (parent, args, context, info) => {
return context.dataSources.userAPI.getUserById(parent.userId);
},
},
OrderItem: {
product: async (parent, args, context, info) => {
return context.dataSources.productAPI.getProductById(parent.productId);
},
},
Review: {
product: async (parent, args, context, info) => {
return context.dataSources.productAPI.getProductById(parent.productId);
},
user: async (parent, args, context, info) => {
return context.dataSources.userAPI.getUserById(parent.userId);
},
},
// Root Query Resolvers
Query: {
// User Queries
users, // Function imported from userResolver.js
user,
me,
This document presents a comprehensive GraphQL schema design for an E-commerce application. It includes detailed type definitions, queries for data retrieval, mutations for data manipulation, and subscriptions for real-time updates. Furthermore, it outlines the structure for resolvers and provides practical integration examples for client-side consumption.
GraphQL is a powerful query language for APIs and a runtime for fulfilling those queries with your existing data. It provides a complete and understandable description of the data in your API, giving clients the power to ask for exactly what they need and nothing more, making it easier to evolve APIs over time.
This schema design focuses on a typical E-commerce domain, covering core entities such as Users, Products, Categories, and Orders. The goal is to provide a flexible and efficient API for managing these resources, supporting both standard CRUD (Create, Read, Update, Delete) operations and real-time functionalities.
The following is the complete GraphQL Schema Definition Language (SDL) for our E-commerce API.
# --- Scalar Types ---
# Custom scalar for representing a date-time string (e.g., ISO 8601)
scalar DateTime
# --- Enums ---
enum UserRole {
CUSTOMER
ADMIN
SELLER
}
enum OrderStatus {
PENDING
PROCESSING
SHIPPED
DELIVERED
CANCELLED
RETURNED
}
# --- Object Types ---
type User {
id: ID!
username: String!
email: String!
role: UserRole!
createdAt: DateTime!
updatedAt: DateTime!
orders(
# Pagination arguments for orders
first: Int = 10
after: String
): [Order!]!
}
type Product {
id: ID!
name: String!
description: String
price: Float!
imageUrl: String
category: Category!
stock: Int!
createdAt: DateTime!
updatedAt: DateTime!
}
type Category {
id: ID!
name: String!
description: String
products(
# Pagination arguments for products within a category
first: Int = 10
after: String
): [Product!]!
}
type OrderItem {
product: Product!
quantity: Int!
priceAtPurchase: Float! # Price at the time of order
}
type Order {
id: ID!
user: User!
items: [OrderItem!]!
totalAmount: Float!
status: OrderStatus!
createdAt: DateTime!
updatedAt: DateTime!
}
# --- Input Types for Mutations ---
input CreateUserInput {
username: String!
email: String!
password: String! # Passwords should be hashed before storage
role: UserRole = CUSTOMER # Default to customer
}
input UpdateUserInput {
username: String
email: String
password: String
role: UserRole
}
input CreateProductInput {
name: String!
description: String
price: Float!
imageUrl: String
categoryId: ID! # Link to an existing category
stock: Int!
}
input UpdateProductInput {
name: String
description: String
price: Float
imageUrl: String
categoryId: ID
stock: Int
}
input CreateCategoryInput {
name: String!
description: String
}
input UpdateCategoryInput {
name: String
description: String
}
input OrderItemInput {
productId: ID!
quantity: Int!
}
input CreateOrderInput {
userId: ID! # The user placing the order
items: [OrderItemInput!]!
}
input UpdateOrderInput {
status: OrderStatus
}
# --- Query Type ---
type Query {
# User Queries
user(id: ID!): User
users(
first: Int = 10
after: String
): [User!]!
# Product Queries
product(id: ID!): Product
products(
categoryId: ID
search: String
minPrice: Float
maxPrice: Float
first: Int = 10
after: String
): [Product!]!
# Category Queries
category(id: ID!): Category
categories(
first: Int = 10
after: String
): [Category!]!
# Order Queries
order(id: ID!): Order
orders(
userId: ID
status: OrderStatus
first: Int = 10
after: String
): [Order!]!
}
# --- Mutation Type ---
type Mutation {
# User Mutations
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User
deleteUser(id: ID!): Boolean! # Returns true if deletion was successful
# Product Mutations
createProduct(input: CreateProductInput!): Product!
updateProduct(id: ID!, input: UpdateProductInput!): Product
deleteProduct(id: ID!): Boolean!
updateProductStock(id: ID!, newStock: Int!): Product
# Category Mutations
createCategory(input: CreateCategoryInput!): Category!
updateCategory(id: ID!, input: UpdateCategoryInput!): Category
deleteCategory(id: ID!): Boolean!
# Order Mutations
createOrder(input: CreateOrderInput!): Order!
updateOrderStatus(id: ID!, input: UpdateOrderInput!): Order
cancelOrder(id: ID!): Order # Sets order status to CANCELLED
}
# --- Subscription Type ---
type Subscription {
# Real-time updates for new orders
newOrder(userId: ID): Order! # Optionally filter by user
# Real-time updates when a product's stock changes significantly
productStockUpdated(productId: ID): Product!
# Real-time updates when an order's status changes
orderStatusUpdated(orderId: ID, userId: ID): Order!
}
Beyond the built-in String, Int, Float, Boolean, and ID scalars, we introduce a custom scalar:
DateTime: Represents a date and time value, typically stored as an ISO 8601 string. This requires a custom serialization/deserialization logic in the resolvers.Enums provide a way to define a set of allowed values for a field.
UserRole: Defines the roles a user can have (e.g., CUSTOMER, ADMIN, SELLER).OrderStatus: Defines the various states an order can be in (e.g., PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED, RETURNED).These are the fundamental data structures of our API.
User: Represents a user account with basic details and a list of associated orders (with pagination).Product: Details about a specific product, including its category and stock level.Category: Groups products, with a list of products belonging to it (with pagination).OrderItem: Represents a single item within an order, capturing the product, quantity, and the price at the time of purchase (important for historical accuracy).Order: Represents a customer order, linking to the user, listing order items, total amount, and its current status.Input types are special object types used as arguments for mutations. They prevent repetitive argument definitions and allow for cleaner, more organized mutation signatures.
CreateUserInput, UpdateUserInput: For creating and updating user profiles.CreateProductInput, UpdateProductInput: For creating and updating product details.CreateCategoryInput, UpdateCategoryInput: For creating and updating categories.OrderItemInput: Used within CreateOrderInput to specify products and quantities for a new order.CreateOrderInput, UpdateOrderInput: For creating and updating orders.Queries define the read operations clients can perform.
user(id: ID!) to fetch a single user, users to fetch a list of users with pagination.product(id: ID!) for a single product, products for a list with filtering capabilities (by category, search term, price range) and pagination.category(id: ID!) for a single category, categories for a list with pagination.order(id: ID!) for a single order, orders for a list with filtering by user or status and pagination.Mutations define the write operations (create, update, delete) clients can perform.
createUser, updateUser, deleteUser.createProduct, updateProduct, deleteProduct, updateProductStock (a specific mutation for a common update).createCategory, updateCategory, deleteCategory.createOrder, updateOrderStatus, cancelOrder.Subscriptions enable real-time, push-based communication from the server to the client.
newOrder(userId: ID): Notifies clients when a new order is placed, with an optional filter to receive updates only for orders belonging to a specific user.productStockUpdated(productId: ID): Notifies clients when a specific product's stock changes (e.g., after an order or a restock).orderStatusUpdated(orderId: ID, userId: ID): Notifies clients about changes in an order's status, with optional filters.Resolvers are functions that tell GraphQL how to fetch the data for a particular field. Every field in the schema (e.g., User.username, Query.product, Mutation.createUser, Subscription.newOrder) has a corresponding resolver function.
Resolver Structure Example (Conceptual - Node.js/JavaScript):
// Example data sources (e.g., database models, external APIs)
const UserModel = require('./models/User');
const ProductModel = require('./models/Product');
const CategoryModel = require('./models/Category');
const OrderModel = require('./models/Order');
const { PubSub } = require('graphql-subscriptions'); // For subscriptions
const pubsub = new PubSub();
// Define PubSub topic constants
const NEW_ORDER = 'NEW_ORDER';
const PRODUCT_STOCK_UPDATED = 'PRODUCT_STOCK_UPDATED';
const ORDER_STATUS_UPDATED = 'ORDER_STATUS_UPDATED';
const resolvers = {
// Custom Scalar Resolver
DateTime: {
serialize(value) {
return value.toISOString(); // Convert Date object to ISO string
},
parseValue(value) {
return new Date(value); // Convert client string to Date object
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
return new Date(ast.value);
}
return null;
},
},
// Field Resolvers for Object Types (e.g., User.orders)
User: {
orders: async (parent, { first, after }) => {
// 'parent' here is the User object resolved by a Query.user or Query.users
// Implement pagination logic here (e.g., using cursor-based pagination)
return OrderModel.find({ userId: parent.id }).limit(first).skip(after ? parseInt(after) : 0);
},
},
Product: {
category: async (parent) => {
// 'parent' is the Product object
return CategoryModel.findById(parent.categoryId);
},
},
OrderItem: {
product: async (parent) => {
// 'parent' is the OrderItem object
return ProductModel.findById(parent.productId);
},
},
// Query Resolvers
Query: {
user: async (parent, { id }) => UserModel.findById(id),
users: async (parent, { first, after }) => UserModel.find().limit(first).skip(after ? parseInt(after) : 0),
product: async (parent, { id }) => ProductModel.findById(id),
products: async (parent, args) => {
// Build query based on args (categoryId, search, minPrice, maxPrice, pagination)
let query = {};
if (args.categoryId) query.categoryId = args.categoryId;
if (args.search) query.name = { $regex: args.search, $options: 'i' }; // Case-insensitive search
if (args.minPrice) query.price = { ...query.price, $gte: args.minPrice };
if (args.maxPrice) query.price = { ...query.price, $lte: args.maxPrice };
return ProductModel.find(query).limit(args.first).skip(args.after ? parseInt(args.after) : 0);
},
category: async (parent, { id }) => CategoryModel.findById(id),
categories: async (parent, { first, after }) => CategoryModel.find().limit(first).skip(after ? parseInt(after) : 0),
order: async (parent, { id }) => OrderModel.findById(id),
orders: async (parent, args) => {
let query = {};
if (args.userId) query.userId = args.userId;
if (args.status) query.status = args.status;
return OrderModel.find(query).limit(args.first).skip(args.after ? parseInt(args.after) : 0);
},
},
// Mutation Resolvers
Mutation: {
createUser: async (parent, { input }) => {
// Hash password before saving
const hashedPassword = await hashPassword(input.password);
const newUser = await UserModel.create({ ...input, password: hashedPassword });
return newUser;
},
updateUser: async (parent, { id, input }) => {
if (input.password) input.password = await hashPassword(input.password);
return UserModel.findByIdAndUpdate(id, input, { new: true });
},
deleteUser: async (parent, { id }) => {
const result = await UserModel.findByIdAndDelete(id);
return !!result; // Return true if a document was deleted
},
createProduct: async (parent, { input }) => ProductModel.create(input),
updateProduct: async
\n