This repository is an example of using graphql-tools
schema-stiching
to stitching together multiple remote schemas which also have subscriptions.
We have 3 GraphQL Services
- Actor Service
- Movie Service
- Genre Service
enum Gender{
MALE
FEMALE
OTHER
}
type Actor {
id: ID! @unique
actorId: String! @unique
firstName: String!
lastName: String!
gender: Gender!
}
type Query {
actors(where: ActorWhereInput, orderBy: ActorOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Actor]!
}
type Mutation {
createActor(data: ActorCreateInput!): Actor!
updateActor(data: ActorUpdateInput!, where: ActorWhereUniqueInput!): Actor
deleteActor(where: ActorWhereUniqueInput!): Actor
}
type Subscription {
actor(where: ActorSubscriptionWhereInput): ActorSubscriptionPayload
}
type Movie {
id: ID! @unique
movieId: String! @unique
name: String!
actorIds: [String!]!
genreId: String
}
type Query {
movies(where: MovieWhereInput, orderBy: MovieOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Movie]!
}
type Mutation {
createMovie(data: MovieCreateInput!): Movie!
updateMovie(data: MovieUpdateInput!, where: MovieWhereUniqueInput!): Movie
deleteMovie(where: MovieWhereUniqueInput!): Movie
}
type Subscription {
movie(where: MovieSubscriptionWhereInput): MovieSubscriptionPayload
}
type Genre {
id: ID! @unique
genreId: String! @unique
name: String!
movieIds: [String!]!
}
type Query {
genres(where: GenreWhereInput, orderBy: GenreOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Genre]!
}
type Mutation {
createGenre(data: GenreCreateInput!): Genre!
updateGenre(data: GenreUpdateInput!, where: GenreWhereUniqueInput!): Genre
deleteGenre(where: GenreWhereUniqueInput!): Genre
}
type Subscription {
genre(where: GenreSubscriptionWhereInput): GenreSubscriptionPayload
}
enum Gender{
MALE
FEMALE
OTHER
}
type Actor {
id: ID! @unique
actorId: String! @unique
firstName: String!
lastName: String!
gender: Gender!
}
type Movie {
id: ID! @unique
movieId: String! @unique
name: String!
actorIds: [String!]!
genreId: String
actors: [Actor!]!
genre: Genre
}
type Genre {
id: ID! @unique
genreId: String! @unique
name: String!
movieIds: [String!]!
movies: [Movie!]!
}
type Query {
actors(where: ActorWhereInput, orderBy: ActorOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Actor]!
movies(where: MovieWhereInput, orderBy: MovieOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Movie]!
genres(where: GenreWhereInput, orderBy: GenreOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Genre]!
}
type Mutation {
createActor(data: ActorCreateInput!): Actor!
updateActor(data: ActorUpdateInput!, where: ActorWhereUniqueInput!): Actor
deleteActor(where: ActorWhereUniqueInput!): Actor
createMovie(data: MovieCreateInput!): Movie!
updateMovie(data: MovieUpdateInput!, where: MovieWhereUniqueInput!): Movie
deleteMovie(where: MovieWhereUniqueInput!): Movie
createGenre(data: GenreCreateInput!): Genre!
updateGenre(data: GenreUpdateInput!, where: GenreWhereUniqueInput!): Genre
deleteGenre(where: GenreWhereUniqueInput!): Genre
}
type Subscription {
actor(where: ActorSubscriptionWhereInput): ActorSubscriptionPayload
movie(where: MovieSubscriptionWhereInput): MovieSubscriptionPayload
genre(where: GenreSubscriptionWhereInput): GenreSubscriptionPayload
}
GraphQL Gateway Code
import {GraphQLServer, Options} from 'graphql-yoga'
import {mergeSchemas} from 'graphql-tools';
import {getRemoteSchema} from "./remote-schema/";
const start = async () => {
console.log('Starting...');
const actorSchema = await getRemoteSchema('http://localhost:4001/','ws://localhost:4001/');
const movieSchema = await getRemoteSchema('http://localhost:4002/','ws://localhost:4002/');
const genreSchema = await getRemoteSchema('http://localhost:4003/','ws://localhost:4003/');
const extendedSchema = `
extend type Movie {
actors: [Actor!]!
genre: [Genre!]!
}
extend type Genre {
movies: [Movie!]!
}
`;
const schema = mergeSchemas({
schemas: [actorSchema, movieSchema, genreSchema, extendedSchema],
resolvers: mergeInfo => ({
Genre: {
movies: {
fragment: `fragment GenreFragment on Genre { movieIds }`,
resolve(parent, args, context, info) {
const movieIds = parent.movieIds;
const whereClause = {
where: {movieId_in: movieIds}
};
if(movieIds && movieIds.length>0) {
return mergeInfo.delegate(
'query',
'movies',
whereClause,
context,
info,
);
}
else {
return [];
}
},
},
},
Movie: {
actors: {
fragment: `fragment MovieFragment on Movie { actorIds }`,
resolve(parent, args, context, info) {
const actorIds = parent.actorIds;
const whereClause = {
where: {actorId_in: actorIds}
};
if(actorIds && actorIds.length>0) {
return mergeInfo.delegate(
'query',
'actors',
whereClause,
context,
info,
);
}
else {
return [];
}
},
},
genre:{
fragment: `fragment MovieFragment on Movie { genreId }`,
resolve(parent, args, context, info) {
const genreId = parent.genreId;
const whereClause = {
where: {genreId: genreId}
};
if(genreId) {
return mergeInfo.delegate(
'query',
'genres',
whereClause,
context,
info,
);
}
else {
return null;
}
},
}
}
})
});
const server = new GraphQLServer({
schema
});
const options: Options = {
port: 4000
}
server.start((options) => console.log('Server is running on http://localhost:4000'))
}
start();
To make the Subscriptions works via a GraphQL API Gateway, we have to split between http and websocket in remote schema creation part
import { HttpLink } from 'apollo-link-http';
import {split} from 'apollo-client-preset'
import {WebSocketLink} from 'apollo-link-ws';
import {getMainDefinition} from 'apollo-utilities'
import * as ws from 'ws';
import { SubscriptionClient } from 'subscriptions-transport-ws/dist/client';
import fetch from 'node-fetch';
import {introspectSchema, makeRemoteExecutableSchema} from 'graphql-tools';
import {GraphQLSchema} from "graphql";
export const getRemoteSchema = async (uri: String, subUri: String): Promise<GraphQLSchema> => {
const httpLink = new HttpLink({ uri, fetch });
const wsLink = new SubscriptionClient(subUri,{
reconnect: true
}, ws);
const link = split(
({query}) => {
const {kind, operation} = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription'
},
wsLink,
httpLink,
);
const schema = await introspectSchema(httpLink);
const executableSchema = makeRemoteExecutableSchema({
schema,
link,
});
return executableSchema;
}
- Latest Node JS
- Prisma CLI 1.3
Terminal 1
$>cd service1
$>npm install
$>prisma deploy
$>npm start
Terminal 2
$>cd service2
$>npm install
$>prisma deploy
$>npm start
Terminal 3
$>cd service3
$>npm install
$>prisma deploy
$>npm start
Terminal 4
$>cd gateway
$>npm install
$>npm start
Browser
http://localhost:4000
GraphQL Playground Tab 1
{
genres{
name
movies{
name
actors{
firstName
lastName
gender
}
}
}
}
GraphQL Playground Tab 2
subscription {
genre {
node {
genreId
name
}
}
}
GraphQL Playground Tab 3
subscription {
movie {
node {
movieId
name
}
}
}
GraphQL Playground Tab 4
subscription {
actor {
node {
actorId
firstName
lastName
gender
}
}
}
GraphQL Playground Tab 5
mutation {
createGenre(data: {genreId: "scifi", name: "Sci-fi"}) {
id
name
}
createMovie(data: {movieId: "stargate", name: "StarGate"}) {
id
name
}
createActor(data: {actorId: "kurt-russell", firstName: "Kurt", lastName: "Russell", gender: MALE}) {
id
firstName
lastName
}
}