I have 4 subgraph services namely products, couriers, cargo_service, tax. (Not a real-world example, just playing with graphql federation concepts).
Below is my code for those services.
products service
const { ApolloServer} = require("@apollo/server");
const{ startStandaloneServer } = require('@apollo/server/standalone');
const {gql} = require("graphql-tag");
const { buildSubgraphSchema } = require("@apollo/subgraph");
const PORT = 4001;
const typeDefs = gql`
type Product @key(fields: "id") {
id: ID!
name: String
price: Float
courier: Courier @external
estimatedShippingCharge: Float @requires(fields: "courier {shippingCharge tax} ")
}
extend type Courier @key(fields: "id") {
id: ID! @external
shippingCharge: Float @external
tax: Float @external
}
extend type Query {
product(id: ID!): Product
products: [Product]
}
`;
const resolvers = {
Product: {
__resolveReference(object) {
return products.find(product => product.id === object.id);
},
estimatedShippingCharge(object) {
console.log(object);
return object.courier.shippingCharge + object.courier.tax;
}
},
Query: {
product(_, {id}) {
return products.find(product => product.id === id);
},
products() {
return products;
}
}
}
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }])
});
startStandaloneServer(server, {
listen:{
port: PORT
}
}).then(({url}) => {
console.log(`Products service ready at ${url}`);
});
const products = [
{
id: "1",
name: "Headphones",
price: 10.99
},
{
id: "2",
name: "Keyboard",
price: 20.99
},
{
id: "3",
name: "Mouse",
price: 15.99
}
]
couriers service
const { ApolloServer} = require("@apollo/server");
const{ startStandaloneServer } = require('@apollo/server/standalone');
const {gql} = require("graphql-tag");
const { buildSubgraphSchema } = require("@apollo/subgraph");
const PORT = 4002;
const typeDefs = gql`
type Courier @key(fields: "id") {
id: ID!
name: String
shippingCharge: Float @external
tax: Float @external
}
extend type Product @key(fields: "id") {
id: ID!
courier: Courier
}
extend type Query {
courier(id: ID!): Courier
couriers: [Courier]
}
`;
const resolvers = {
Courier: {
__resolveReference(object) {
return couriers.find(courier => courier.id === object.id);
}
},
Product: {
__resolveReference(object) {
return products.find(product => product.id === object.id);
}
},
Query: {
courier(_, {id}) {
return couriers.find(courier => courier.id === id);
},
couriers() {
return couriers;
}
}
}
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }])
});
startStandaloneServer(server, {
listen:{
port: PORT
}
}).then(({url}) => {
console.log(`Products service ready at ${url}`);
});
const couriers = [
{
id: "1",
name: "DHL"
},
{
id: "2",
name: "FedEx"
}
];
const products = [
{
id: "1",
courier: couriers[0]
},
{
id: "2",
courier: couriers[1]
},
{
id: "3",
courier: couriers[0]
}
];
cargo_service
const { ApolloServer} = require("@apollo/server");
const{ startStandaloneServer } = require('@apollo/server/standalone');
const {gql} = require("graphql-tag");
const { buildSubgraphSchema } = require("@apollo/subgraph");
const PORT = 4003;
const typeDefs = gql`
extend type Courier @key(fields: "id") {
id: ID!
shippingCharge: Float
}
`;
const resolvers = {
Courier: {
__resolveReference(object) {
return shippingCharges.find(shippingCharge => shippingCharge.id === object.id);
}
}
}
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }])
});
startStandaloneServer(server, {
listen:{
port: PORT
}
}).then(({url}) => {
console.log(`Products service ready at ${url}`);
});
const shippingCharges = [
{
id: "1",
shippingCharge: 100.99
},
{
id: "2",
shippingCharge: 200.99
}
]
tax service
const { ApolloServer} = require("@apollo/server");
const{ startStandaloneServer } = require('@apollo/server/standalone');
const {gql} = require("graphql-tag");
const { buildSubgraphSchema } = require("@apollo/subgraph");
const PORT = 4004;
const typeDefs = gql`
extend type Courier @key(fields: "id") {
id: ID!
tax: Float
}
`;
const resolvers = {
Courier: {
__resolveReference(object) {
return taxes.find(tax => tax.id === object.id);
}
}
}
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }])
});
startStandaloneServer(server, {
listen:{
port: PORT
}
}).then(({url}) => {
console.log(`Products service ready at ${url}`);
});
const taxes = [
{
id: "1",
tax: 100.99
},
{
id: "2",
tax: 200.99
}
];
And following is my gateway code.
const { ApolloServer } = require("@apollo/server");
const { ApolloGateway, IntrospectAndCompose } = require("@apollo/gateway");
const { startStandaloneServer } = require("@apollo/server/standalone");
const {serializeQueryPlan} = require('@apollo/query-planner');
const PORT = 4000;
const supergraphSdl = new IntrospectAndCompose({
subgraphs: [
{ name: "products", url: "http://localhost:4001" },
{ name: "couriers", url: "http://localhost:4002" },
{ name: "cargo_service", url: "http://localhost:4003" },
{ name: "tax", url: "http://localhost:4004" },
],
});
const gateway = new ApolloGateway({
supergraphSdl,
experimental_didResolveQueryPlan: function(options) {
if (options.requestContext.operationName !== 'IntrospectionQuery') {
console.log(serializeQueryPlan(options.queryPlan));
}
}
});
const server = new ApolloServer({gateway});
startStandaloneServer(server, {
listen:{
port: PORT
}
}).then(({url}) => {
console.log(`Products service ready at ${url}`);
});
When I queried the following document I get an error.
query {
product(id: "1") {
id
name
price
courier {
id
name
shippingCharge
tax
}
estimatedShippingCharge
}
}
The error is as below.
{
"data": {
"product": {
"id": "1",
"name": "Headphones",
"price": 10.99,
"courier": {
"id": "1",
"name": "DHL",
"shippingCharge": 100.99,
"tax": 100.99
},
"estimatedShippingCharge": null
}
},
"errors": [
{
"message": "Cannot read properties of undefined (reading 'shippingCharge')",
"path": [
"product",
"estimatedShippingCharge"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"stacktrace": [
"GraphQLError: Cannot read properties of undefined (reading 'shippingCharge')",
" at downstreamServiceError (D:\\poc_ballerina_gateway_requires\\javascript_services\\node_modules\\@apollo\\gateway\\dist\\executeQueryPlan.js:514:16)",
" at D:\\poc_ballerina_gateway_requires\\javascript_services\\node_modules\\@apollo\\gateway\\dist\\executeQueryPlan.js:334:59",
" at Array.map (<anonymous>)",
" at sendOperation (D:\\poc_ballerina_gateway_requires\\javascript_services\\node_modules\\@apollo\\gateway\\dist\\executeQueryPlan.js:334:44)",
" at process.processTicksAndRejections (node:internal/process/task_queues:95:5)",
" at async D:\\poc_ballerina_gateway_requires\\javascript_services\\node_modules\\@apollo\\gateway\\dist\\executeQueryPlan.js:279:49",
" at async executeNode (D:\\poc_ballerina_gateway_requires\\javascript_services\\node_modules\\@apollo\\gateway\\dist\\executeQueryPlan.js:199:17)",
" at async executeNode (D:\\poc_ballerina_gateway_requires\\javascript_services\\node_modules\\@apollo\\gateway\\dist\\executeQueryPlan.js:190:27)",
" at async executeNode (D:\\poc_ballerina_gateway_requires\\javascript_services\\node_modules\\@apollo\\gateway\\dist\\executeQueryPlan.js:173:40)",
" at async D:\\poc_ballerina_gateway_requires\\javascript_services\\node_modules\\@apollo\\gateway\\dist\\executeQueryPlan.js:96:35"
],
"serviceName": "products"
}
}
]
}
Since courier -> shippingCharge
resolved correctly in the query, Why does it complain as undefined when trying to resolve shippingCharge
as undefined?.
Is there any mistake in my code?.
According to the documentation of @requires
, they should support this kind of operation.
https://www.apollographql.com/docs/federation/entities-advanced/#using-requires-with-object-subfields
PS: Used the Apollo router (https://www.apollographql.com/docs/router/quickstart/) as well. Got same kind of error