Search code examples
node.jsgraphqlapolloapollo-server

Reuse resolver while working with union type in graphql


I have apollo-server setup which have:

  • different cars
  • cars contain different engines
  • engines can be diesel or petrol with different capacity and fuel type

To get cars (model and engine data), firstly I need to fetch cars models (hardcoded), and then, based on these models, fetch engine information.

I have separate queries for PetrolEngine and DieselEngine.
Is it possible to make apollo understand somehow to use these queries (Query.petrolEngine, Query.dieselEngine) and distinguish them while working with 'Car.engine' field that gets model name.

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const typeDefs = `#graphql

type Car {
    model: String
    engine: CarEngine
}

union CarEngine = DieselEngine | PetrolEngine

type DieselEngine {
    capacity: Float
    dieselType: String
}

type PetrolEngine {
    capacity: Float
    petrolType: String
}

type Query {
    cars: [Car]
    dieselEngine: DieselEngine
    petrolEngine: PetrolEngine
}
`;

const engines = {
    bmw: {capacity: 2.0, dieselType: 'L'},
    fiat: {capacity: 1.6, petrolType: '95'},
}

const resolvers = {
    Query: {
        cars: () => [{ model: 'bmw' }, { model: 'fiat' }],
        dieselEngine: (carModel) => { engines[carModel] },
        petrolEngine: (carModel) => { engines[carModel] },
    },
    Car: {
        engine: (parent) => {
        }
    },
    CarEngine: {
        __resolveType: (obj) => {
            console.log('resolving type for ', obj);
            if (obj?.dieselType) return 'DieselEngine';
            else return 'PetrolEngine';
        }
    }
};

const server = new ApolloServer({
    typeDefs,
    resolvers,
});

const { url } = await startStandaloneServer(server);


Solution

  • A useful pattern is just to define functions that can be applied to multiple resolvers and/or queries.

    const getEngine = ({model}) => engines[model]
    

    Then you can do something like:

    const resolvers = {
        Query: {
            cars: () => [{ model: 'bmw' }, { model: 'fiat' }],
            dieselEngine: getEngine,
            petrolEngine: getEngine,
        },
        Car: {
            engine: getEngine,
        },
        CarEngine: {
            __resolveType: (obj) => {
                console.log('resolving type for ', obj);
                if (obj?.dieselType) return 'DieselEngine';
                else return 'PetrolEngine';
            }
            dieselType: (parent) => parent.dieselType,
            petrolType: (parent) => parent.petrolType,
        }
    };
    

    I should add that your petrolEngine and dieselEngine queries make no sense as written. Firstly in your typeDefs these queries have no variables even though you have a carModel variable in your resolver code. Secondly, if you wanted to find out what engine a car model had you could just have a getEngine query that took the carModel as input and returned whatever engine it had, either diesel or petrol. Having a query specific to the engine type assumes that the carModel variable has that kind of engine.