- https://medium.com/@abhiaiyer/your-first-graphql-component-ad018e22972c#.exbhq5s48
165 lines
4.2 KiB
JavaScript
165 lines
4.2 KiB
JavaScript
import {
|
|
GraphQLBoolean,
|
|
GraphQLFloat,
|
|
GraphQLID,
|
|
GraphQLInt,
|
|
GraphQLList,
|
|
GraphQLNonNull,
|
|
GraphQLObjectType,
|
|
GraphQLSchema,
|
|
GraphQLString,
|
|
GraphQLInputObjectType,
|
|
} from 'graphql';
|
|
|
|
import {
|
|
mapValues,
|
|
mapKeys,
|
|
} from 'lodash';
|
|
|
|
import {
|
|
schema
|
|
} from './swapi.js';
|
|
|
|
import rp from 'request-promise';
|
|
|
|
import DataLoader from 'dataloader';
|
|
|
|
// Generate a GraphQL object type for every entry in the schema
|
|
const graphQLObjectTypes = mapValues(schema, (jsonSchema) => {
|
|
return new GraphQLObjectType({
|
|
name: jsonSchema.title,
|
|
description: jsonSchema.description,
|
|
fields: () => {
|
|
return mapValues(jsonSchema.properties, (propertySchema, propertyName) => {
|
|
const value = {
|
|
description: propertySchema.description,
|
|
type: jsonSchemaTypeToGraphQL(propertySchema.type, propertyName)
|
|
};
|
|
|
|
// All of the arrays in the schema happen to be references to other types
|
|
if(propertySchema.type === 'array') {
|
|
value.resolve = (root, args) => {
|
|
const arrayOfUrls = root[propertyName];
|
|
const arrayOfResults = arrayOfUrls.map(restLoader.load.bind(restLoader));
|
|
return arrayOfResults;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
});
|
|
}
|
|
})
|
|
});
|
|
|
|
// Convert the JSON Schema types to the actual GraphQL types in our schema
|
|
function jsonSchemaTypeToGraphQL(jsonSchemaType, schemaName) {
|
|
if (jsonSchemaType === "array") {
|
|
if (graphQLObjectTypes[schemaName]) {
|
|
return new GraphQLList(graphQLObjectTypes[schemaName]);
|
|
} else {
|
|
const translated = {
|
|
pilots: "people",
|
|
characters: "people",
|
|
residents: "people"
|
|
}[schemaName];
|
|
|
|
if (! translated) {
|
|
throw new Error(`no type ${schemaName}`);
|
|
}
|
|
|
|
const type = graphQLObjectTypes[translated];
|
|
|
|
if (! type) {
|
|
throw new Error(`no GraphQL type ${schemaName}`);
|
|
}
|
|
|
|
return new GraphQLList(type);
|
|
}
|
|
}
|
|
|
|
return {
|
|
string: GraphQLString,
|
|
date: GraphQLString,
|
|
integer: GraphQLInt,
|
|
}[jsonSchemaType];
|
|
}
|
|
|
|
const queryType = new GraphQLObjectType({
|
|
name: 'Query',
|
|
fields: () => {
|
|
// For each type, make a query to get a page of that type
|
|
const plural = mapValues(graphQLObjectTypes, (type, typePluralName) => {
|
|
return {
|
|
type: new GraphQLList(type),
|
|
description: `All ${typePluralName}.`,
|
|
args: {
|
|
page: { type: GraphQLInt }
|
|
},
|
|
resolve: (_, { page }) => {
|
|
// Simple pagination, where you just pass the page number through to the REST API
|
|
return fetchPageOfType(typePluralName, page);
|
|
},
|
|
};
|
|
});
|
|
|
|
|
|
// For each type, also make a query to get just one object of that type
|
|
const singular = mapValues(mapKeys(graphQLObjectTypes, (value, key) => {
|
|
// Name the query people_one vehicles_one etc. not sure what the standard should be
|
|
// here. We could also adopt the Relay node spec
|
|
return key + "_one";
|
|
}), (type, typeQueryName) => {
|
|
const restName = typeQueryName.split('_')[0];
|
|
|
|
return {
|
|
type: type,
|
|
description: `One ${restName}.`,
|
|
args: {
|
|
id: { type: GraphQLString }
|
|
},
|
|
resolve: (_, { id }) => {
|
|
return fetchOne(restName, id);
|
|
},
|
|
}
|
|
});
|
|
|
|
return {
|
|
...plural,
|
|
...singular
|
|
};
|
|
},
|
|
});
|
|
|
|
// A helper to unwrap the paginated object from SWAPI
|
|
function fetchPageOfType(typePluralName, pageNumber) {
|
|
let url = `http://swapi.co/api/${typePluralName}/`;
|
|
if (pageNumber) {
|
|
url += `?page=${pageNumber}`;
|
|
};
|
|
|
|
return restLoader.load(url).then((data) => {
|
|
// Paginated results have a different shape
|
|
return data.results;
|
|
});
|
|
}
|
|
|
|
// Constructs a URL from the endpoint name and object ID
|
|
function fetchOne(restName, id) {
|
|
return restLoader.load(`http://swapi.co/api/${restName}/${id}`);
|
|
}
|
|
|
|
// Use dataloader to batch URL requests for each layer of the query
|
|
const restLoader = new DataLoader((urls) => {
|
|
console.log("Fetching batch:", urls);
|
|
return Promise.all(urls.map((url) => {
|
|
return rp({
|
|
uri: url,
|
|
json: true
|
|
});
|
|
}));
|
|
});
|
|
|
|
// This schema has no mutations because you can't really write to the Star Wars API.
|
|
export const Schema = new GraphQLSchema({
|
|
query: queryType,
|
|
});
|