在Supabase GraphQL中实现分类关联产品查询的问题求助
问题描述
我在Supabase中拥有两张表:
category:id (int4 主键), name (text)product:id (int4 主键), name (text), price (int8), category_id (外键关联category的id)
我希望在resolver函数中编写查询语句,以获取分类及其所有关联的产品。尝试使用.join方法,但Supabase提示该方法不存在。以下是我的相关代码:
category.schema.js
module.exports = ` type Category { id: Int name: String! product:Product! } type Query { getCategoryList: [category_product]! getCategory(id: Int!): Category } type Mutation { addCategory(name: String!): Category updateCategory(id:ID!,name: String!): Category deleteCategory(id:ID!): Category } `;
product.schema.js
module.exports = ` type Product { id: Int name: String! price: Int! isDeleted: Boolean! category: Category! } type Query { getProductList: [Product] getProduct(id: Int!): Product } type Mutation { addProduct(name: String!, price: Int!, category: Int!): Product updateProduct(id: Int!,name:String,price:Int!): Product deleteProduct(id:ID!): Product } `;
category.resolver.js
const { isEmpty } = require("lodash"); const supabase = require("../../supabase"); let validator = {}; module.exports = { Query: { getCategoryList: async () => { try { const {data,error} = await supabase.from("category").select(`id,name,product(id,name,price)`); if (error) { throw new Error(error.message); } console.log("data :",data); return data; } catch (error) { console.log("Error",error); return null; } }, getCategory: async (parent, args) => { const id = args.id; if (!/^[0-9a-fA-F]{24}$/.test(id)) { validator.error = ERROR_MSG.INVALID_ID; throw new ValidationException(validator); } const result = category .findById(id) .populate([ { path: "productId", select: "name price isDeleted _id", }, ]) .select("_id name productId"); if (!result) { validator.category = ERROR_MSG.CATEGORY_ERROR; throw new ValidationException(validator); } return result; }, }, Mutation: { addCategory: async (parent, args) => { const name = args.name; if (!/^[a-zA-Z ]+$/.test(name)) { validator.name = WARNING_MSG.CATEGORY_NAME_WARNING; } const categotyExists = await category.findOne({ name }); if (categotyExists) { validator.category = WARNING_MSG.CATEGORY_WARNING; } if (!isEmpty(validator)) { throw new ValidationException(validator); } else { let categories = await category.create({ name: args.name, }); return { categories }; } }, updateCategory: async (parent, args) => { const { id, name } = args; const categotyExists = await category.findOne({ name }); if (categotyExists) { validator.error = WARNING_MSG.CATEGORY_WARNING; } else { if (/^[0-9a-fA-F]{24}$/.test(id)) { const result = category.findByIdAndUpdate( id, { name: args.name, }, { new: true } ); return result; } else { validator.error = ERROR_MSG.CATEGORY_ERROR; } } if (!isEmpty(validator)) { throw new ValidationException(validator); } }, deleteCategory: async (parent, args) => { const id = args.id; if (id) { const result = product.findByIdAndUpdate( id, { isDeleted: true, }, { new: true } ); return result; } }, }, };
解决方案
1. 核心问题说明
Supabase基于PostgREST,不支持.join方法,关联查询需通过嵌套select语法或GraphQL类型解析器实现。同时你的代码混合了MongoDB语法(如findById、populate),需全部替换为Supabase API。
2. 修正GraphQL Schema
category.schema.js(修正字段类型与返回值)
module.exports = ` type Category { id: Int name: String! products: [Product!]! # 一个分类对应多个产品,改为数组类型 } type Query { getCategoryList: [Category!]! # 修正返回类型为合法的Category数组 getCategory(id: Int!): Category } type Mutation { addCategory(name: String!): Category updateCategory(id: ID!, name: String!): Category deleteCategory(id: ID!): Category } `;
product.schema.js(修正外键参数名)
module.exports = ` type Product { id: Int name: String! price: Int! isDeleted: Boolean! category: Category! } type Query { getProductList: [Product] getProduct(id: Int!): Product } type Mutation { addProduct(name: String!, price: Int!, category_id: Int!): Product # 参数名与数据库外键一致 updateProduct(id: Int!, name: String, price: Int!): Product deleteProduct(id: ID!): Product } `;
3. 完整修正后的category.resolver.js
const { isEmpty } = require("lodash"); const supabase = require("../../supabase"); let validator = {}; // 补充定义缺失的常量与异常类 const ERROR_MSG = { INVALID_ID: "无效的ID格式", CATEGORY_ERROR: "分类不存在" }; const WARNING_MSG = { CATEGORY_NAME_WARNING: "分类名称只能包含字母和空格", CATEGORY_WARNING: "该分类已存在" }; class ValidationException extends Error { constructor(validator) { super("验证错误"); this.validator = validator; } } module.exports = { // 解析Category类型的products关联字段 Category: { products: async (parent) => { const { data, error } = await supabase .from("product") .select("id, name, price, isDeleted") .eq("category_id", parent.id); if (error) throw new Error(error.message); return data; } }, Query: { getCategoryList: async () => { try { const { data, error } = await supabase.from("category").select("id, name"); if (error) throw new Error(error.message); return data; } catch (error) { console.log("Error", error); return null; } }, getCategory: async (parent, args) => { const id = args.id; validator = {}; // Supabase使用int类型ID,无需校验MongoDB ObjectID格式 if (isNaN(id) || id <= 0) { validator.error = ERROR_MSG.INVALID_ID; throw new ValidationException(validator); } try { const { data, error } = await supabase .from("category") .select("id, name") .eq("id", id) .single(); if (error) { validator.category = ERROR_MSG.CATEGORY_ERROR; throw new ValidationException(validator); } return data; } catch (error) { console.log("Error", error); throw error; } }, }, Mutation: { addCategory: async (parent, args) => { const name = args.name; validator = {}; if (!/^[a-zA-Z ]+$/.test(name)) { validator.name = WARNING_MSG.CATEGORY_NAME_WARNING; } const { data: categoryExists } = await supabase .from("category") .select("id") .eq("name", name) .single(); if (categoryExists) { validator.category = WARNING_MSG.CATEGORY_WARNING; } if (!isEmpty(validator)) { throw new ValidationException(validator); } const { data, error } = await supabase .from("category") .insert({ name }) .select() .single(); if (error) throw new Error(error.message); return data; }, updateCategory: async (parent, args) => { const { id, name } = args; validator = {}; if (isNaN(id) || id <= 0) { validator.error = ERROR_MSG.INVALID_ID; throw new ValidationException(validator); } const { data: categoryExists } = await supabase .from("category") .select("id") .eq("name", name) .neq("id", id) .single(); if (categoryExists) { validator.error = WARNING_MSG.CATEGORY_WARNING; } if (!isEmpty(validator)) { throw new ValidationException(validator); } const { data, error } = await supabase .from("category") .update({ name }) .eq("id", id) .select() .single(); if (error) throw new Error(error.message); return data; }, deleteCategory: async (parent, args) => { const id = args.id; validator = {}; if (isNaN(id) || id <= 0) { validator.error = ERROR_MSG.INVALID_ID; throw new ValidationException(validator); } // 若需保留关联产品,可先将product的category_id设为null,再删除分类 const { data, error } = await supabase .from("category") .delete() .eq("id", id) .select() .single(); if (error) throw new Error(error.message); return data; }, }, };
4. 关键注意点
- Supabase关联查询:可通过
select('id,name,product(*)')直接嵌套查询,或通过类型解析器单独查询关联数据(后者更灵活) - 字段一致性:GraphQL Schema字段需与数据库表字段、Resolver返回值类型匹配
- 移除MongoDB语法:所有
findById、populate等MongoDB操作需替换为Supabase的from、select、eq等API
内容的提问来源于stack exchange,提问作者Parth Goti
相关产品推荐
相关产品推荐

