I have a small Node project (simple guessing game base on locations) to serve a React frontend. It is using an Apollo server to retrieve data from an AWS RDS MySQL database with knex as the query builder. I can successfully retrieve data from a single table but when the query contains a join then the child object in graphql is always null.
When I access the node server locally and query the data for guesses I have the guess data but the associated user and question data is always null as the image shows.
Here is the code - I've removed some of the surplus code for the queries that are working fine.
Database
CREATE TABLE `question` (
`id` int NOT NULL AUTO_INCREMENT,
`lat` float NOT NULL,
`lng` float NOT NULL,
`question` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `guess` (
`id` int NOT NULL AUTO_INCREMENT,
`lat` float NOT NULL,
`lng` float NOT NULL,
`question_id` int NOT NULL,
`user_id` int NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `question_id` (`question_id`),
CONSTRAINT `guess_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
CONSTRAINT `guess_ibfk_2` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`)
);
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(11) NOT NULL,
`icon` varchar(11) NOT NULL,
`score` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
);
Apollo Server Application
index.js
const { ApolloServer } = require("apollo-server");
const typeDefs = require("./schema");
const mapDatabase = require("./datasources/map");
const resolvers = require("./resolver");
const knexConfig = {
client: "mysql",
connection: {
host: "my-server",
user: "my-username",
password: "my-password",
database: "db-name",
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => ({
mapDatabase: new mapDatabase(knexConfig),
}),
});
// The `listen` method launches a web server.
server.listen().then(({ url }) => {
console.log(`Server ready at ${url}`);
});
schema.js
const { gql } = require("apollo-server");
const typeDefs = gql`
type User {
id: ID!
username: String
icon: String
score: Int
}
type Question {
id: ID!
lat: Float
lng: Float
question: String
}
type Guess {
id: ID!
lat: Float
lng: Float
question: Question
user: User
}
type Query {
guesses(question: Int!): [Guess]
}
`;
module.exports = typeDefs;
resolver.js
module.exports = {
Query: {
guesses: (_, { question }, { dataSources }) =>
dataSources.mapDatabase.getGuessesByQuestion(question),
},
};
datasources/map.js
const { SQLDataSource } = require("datasource-sql");
class MapDatabase extends SQLDataSource {
getGuessesByQuestion(question) {
return this.knex
.select("*")
.from("guess AS g")
.leftJoin("user AS u", "u.id", "g.user_id")
.leftJoin("question AS q", "q.id", "g.question_id")
.where("g.question_id", "=", question);
}
}
Just as a sanity check I ran the query on the database to make sure I got associated results and it bring back everything as expected.
SELECT * FROM guess g
LEFT JOIN user u ON u.id = g.user_id
LEFT JOIN question q ON a.id = g.question_id
WHERE g.question_id = 1;
The problem is that your query returns flat data, while your GraphQL implicit resolvers would expect a nested object strucuture. Eg knext would return something like this:
{
id: 1,
lat: 89.4,
lon: -12.6,
user_id: 2,
username: "user",
icon: "x",
score: 3
}
But you would need something like this for the resolver to "just work".
{
id: 1,
lat: 89.4,
lon: -12.6,
user: {
id: 2,
username: "user",
icon: "x",
score: 3
}
}
Remember: If you don't pass a resolver into a field, it will use the default resolver which tries to access the field name property of the object. So the user
field would have the following default resolver:
parent => parent.user
In the flat strucuture, there is no field, thus returning undefined
-> The query returns null for the field. In the nested strucuture it will return a valid user object.
You can either build the result by using SQL JSON functions or by implementing a custom resolver for Guess.user
:
module.exports = {
Query: {
guesses: (_, { question }, { dataSources }) =>
dataSources.mapDatabase.getGuessesByQuestion(question),
},
Guess: {
user: (parent) => ({
id: parent.user_id,
username: parent.username,
icon: parent.icon,
score: parent.score
}),
}
};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.