简体   繁体   中英

GraphQL returning null from MySQL join query

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.

graphql_issue

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM