How to modify Prisma query to match nested structure expected by GraphQL schema mutation (Order creation)

I am using Apollo Server and GraphQL + Prisma. I am running into some trouble when trying to create an ecomm feature of order creation.

Since order creation involves a payment, order and orderItems, creating the mutation using dataSources + resolvers has proven challenging.

I am close to a working solution but for some reason my return values are null for my order fields and my orderItems fields.

Graphql Schema + SDL:

type Mutation {
  "Mutation for when a user places an order"
  placeOrder(input: PlaceOrderInput!): PlaceOrderMutationResponse!
}

input PlaceOrderInput {
  user_id: ID!
  orderItems: [OrderItemInput!]!
  payment: PaymentInput!
}

"""
This type represents the composite response containing order details,
including associated payment and order items.
"""
type CompositeOrder {
  "Order details"
  order: Order

  "Payment details associated with the order"
  payment: Payment

  "List of order items associated with the order"
  orderItems: [OrderItem!]
}

"""
Mutation response for the placeOrder mutation
"""
type PlaceOrderMutationResponse {
  "Response code"
  code: Int!

  "Indicates if the operation was successful"
  success: Boolean!

  "Message associated with the response"
  message: String!

  "Composite order details"
  order: CompositeOrder
}

This type represents an order in the system"
type Order {
  "Unique identifier for the order"
  id: ID!

  "User who placed the order"
  user: User!

  "Current status of the order (e.g., "pending", "shipped")"
  status: OrderStatus!

  "Total price of the order"
  total_price: Float!

  "List of order items associated with this order (required, cannot be empty)"
  orderItems: [OrderItem!]!

  "Payment associated with the order"
  payment: Payment
}

"This type represents an item within an order"
type OrderItem {
  "Unique identifier for the order item"
  id: ID!

  "Quantity of the product included in this order item"
  quantity: Int!

  "Price of the product at the time the order was placed"
  price: Float!

  "Order this order item belongs to"
  order: Order!

  "Product associated with this order item"
  product: Product!
}

"This type represents a payment for an order"
type Payment {
  "Unique identifier for the payment"
  id: ID!

  "User who owns this payment transaction"
  user: User

  "Mock payment method used (e.g., credit card)"
  payment_method: String

  "Optional month of expiration for the payment method"
  exp_month: Int

  "Optional year of expiration for the payment method"
  exp_year: Int

  "Masked card number for the payment method (e.g., "XXXX XXXX XXXX XXXX")"
  card_number: String

  "Mock payment status (e.g., "PENDING", "PROCESSED")"
  payment_status: String!
}



orderAPI.ts file where the bulk of the creation logic is handled:

export class OrderAPI {
  // Dependency injection of product api for flexibility
  constructor(public readonly productAPI: ProductAPIInterface) {}

  async placeOrder(input: PlaceOrderInput, user: User): Promise<PlaceOrderMutationResponse> {
    try {
      const PAYMENT_STATUS = {
        PENDING: 'PENDING',
        PROCESSED: 'PROCESSED',
      };

      // Step 1: Fetch product prices using dependency-injected productAPI
      const productIds = input.orderItems.map((item) => item.product_id);
      const priceMap = await this.productAPI.getPricesByIds(productIds);

      // Step 2: Process order items with retrieved prices
      const orderItemsWithPrices = input.orderItems.map((item) => ({
        ...item,
        price: priceMap[item.product_id], // Get price from priceMap by product ID
      }));

      // Validate product existence (optional):
      const allProductsExist = orderItemsWithPrices.every((item) => item.price !== undefined);
      if (!allProductsExist) {
        throw new Error('One or more products not found');
      }

      // Step 3: Calculate total price using retrieved prices
      const totalPrice = orderItemsWithPrices.reduce(
        (acc, item) => acc + item.price * item.quantity,
        0,
      );

      // Step 2: Create the order with eager loading
      const newOrder = await prisma.order.create({
        data: {
          user: { connect: { id: user.id } },
          status: 'Pending', // Set initial order status
          total_price: totalPrice,
          OrderItem: {
            create: orderItemsWithPrices.map((item) => ({
              product: { connect: { id: item.product_id } },
              quantity: item.quantity,
              price: item.price, // Use the fetched price here
            })),
          },
          payment: {
            create: {
              user: { connect: { id: user.id } },
              payment_method: 'CREDIT_CARD',
              exp_month: input.payment.exp_month,
              exp_year: input.payment.exp_year,
              card_number: input.payment.card_number, // Replace with a secure way to store card details
              payment_status: PAYMENT_STATUS.PENDING,
            },
          },
        },
        include: { OrderItem: true, payment: true }, // Include associated data for response
      });

      // Step 3: Return success response with the expected structure
      return {
        code: 200,
        success: true,
        message: 'Order created successfully',
        order: newOrder,
      };
    } catch (error) {
      console.error('Error creating order:', error);
      return {
        code: 500,
        success: false,
        message: 'Failed to create order',
        order: null,
      };
    }
  }
}

Mutation resolver itself:

 Mutation: {
    placeOrder: requireAuthentication(
      async (
        _parent: unknown,
        { input }: MutationPlaceOrderArgs,
        { dataSources, user }: GQLContext,
      ) => {
        const { orderAPI } = dataSources;

        const newOrder = await orderAPI.placeOrder(input, user!);

        console.log(newOrder);

        return newOrder;
      },
    ),
  },

when I console log new Order above this is the output:

 {
[0]   code: 200,
[0]   success: true,
[0]   message: 'Order created successfully',
[0]   order: {
[0]     id: 'a5d581b0-d5c5-4947-b26e-39b3cd55749a',
[0]     user_id: 'd300bb1e-ee8a-40b8-a7da-9b5e602e0044',
[0]     status: 'Pending',
[0]     total_price: 159.92,
[0]     payment_id: '6e397b4c-12bc-4fd2-9a18-7276b1970cec',
[0]     created_at: 2024-05-15T02:10:55.294Z,
[0]     updated_at: 2024-05-15T02:10:55.294Z,
[0]     deleted: false,
[0]     OrderItem: [ [Object] ],
[0]     payment: {
[0]       id: '6e397b4c-12bc-4fd2-9a18-7276b1970cec',
[0]       user_id: 'd300bb1e-ee8a-40b8-a7da-9b5e602e0044',
[0]       payment_method: 'CREDIT_CARD',
[0]       exp_month: 12,
[0]       exp_year: 28,
[0]       card_number: '8888-2232-4443-1111',
[0]       payment_status: 'PENDING',
[0]       created_at: 2024-05-15T02:10:55.294Z,
[0]       updated_at: 2024-05-15T02:10:55.294Z,
[0]       deleted: false
[0]     }
[0]   }
[0] }

Mutation ran from apollo sandbox:

mutation Mutation($input: PlaceOrderInput!) {
  placeOrder(input: $input) {
    code
    success
    message
    order {
      order {
        status
        total_price
      }
      payment {
        payment_method
        card_number
      }
      orderItems {
        quantity
        price
        id
      }
    }
  }
}

But as you can see I get null back for some fields:

{
  "data": {
    "placeOrder": {
      "code": 200,
      "success": true,
      "message": "Order created successfully",
      "order": {
        "order": null,
        "payment": {
          "payment_method": "CREDIT_CARD",
          "card_number": "8888-2232-4443-1111"
        },
        "orderItems": null
      }
    }
  }
}

Relevant Prisma Models:

model Payment {
  id             String   @id @default(uuid())
  user           User     @relation(fields: [user_id], references: [id])
  user_id        String // Change type to String
  payment_method String // Mock payment method (e.g., "credit card")
  exp_month      Int? // Optional, month of expiration (e.g., 1 for January)
  exp_year       Int? // Optional, year of expiration (e.g., 2024)
  card_number    String? // Optional, masked card number (e.g., "XXXX XXXX XXXX XXXX")
  payment_status String   @default("PROCESSED") // Mock payment status (e.g., "success", "failure")
  created_at     DateTime @default(now())
  updated_at     DateTime @updatedAt
  deleted        Boolean  @default(false)
  Order          Order[]
}

model Order {
  id          String      @id @default(uuid())
  user        User        @relation(fields: [user_id], references: [id])
  user_id     String
  status      String
  total_price Float
  payment     Payment     @relation(fields: [payment_id], references: [id])
  payment_id  String
  created_at  DateTime    @default(now())
  updated_at  DateTime    @updatedAt
  deleted     Boolean     @default(false)
  OrderItem   OrderItem[]
}

model OrderItem {
  id         String   @id @default(uuid())
  order      Order    @relation(fields: [order_id], references: [id])
  order_id   String // Field will exist in db
  product    Product  @relation(fields: [product_id], references: [id])
  product_id String // Field will exist in db
  quantity   Int
  price      Float
  created_at DateTime @default(now())
  updated_at DateTime @updatedAt
  deleted    Boolean  @default(false)
}

Is my design off? Am I misusing Apollo Server or not using it’s full capabilities?
I don’t think I should have to query for order: {order: {}}... it should be top level when created. And it’s strange that only payment properly returns.

I can’t seem to figure out how to get any and all fields related to order creation to return in my gql mutation response.

What should I do?

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật