In Deeper
models - Database Model
processes - Behavior Functions
types - GraphQL Types
Controller Logic
Endpoint Define
DB Mixins
/* Import queries and mutations from scoped models */
import {
orderQueries,
orderMutations,
} from './Order.js';
/* Spread into root schema */
const queryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
...orderQueries,
/* ... other model queries */
}),
});
const mutationType = new GraphQLObjectType({
name: 'Mutation',
fields: () => ({
...orderMutations,
/* ... other models mutations */
}),
});
export const graphqlSchema = new GraphQLSchema({
query: queryType,
mutation: mutationType,
});
/* import query, mutation and helper functions in scoped process */
import {
makeOrder,
makeOrderMutation,
getCheckoutToken,
} from '../processes/Order/makeOrder.js';
import {
listOrdersQuery,
findOrdersWithConditions,
singleOrderQuery,
findOrderWithConditions,
} from '../processes/Order/list.js';
/* Define Database Model (MongoDB) */
const OrderSchema = new Schema({
clearId: {
type: String,
required: true,
trim: true,
maxlength: 14,
},
receiver: OrderReceiverSchema,
buyer: OrderBuyerSchema,
totalPrice: Number,
paymentInfo: OrderPaymentInfoSchema,
subOrders: [SubOrderSchema],
});
/* Define class and instance methods */
OrderSchema.virtual('favoriteType').get(() => 'Product');
OrderSchema.static('makeOrder', makeOrder);
OrderSchema.static('findAllWithConditions', findOrdersWithConditions);
OrderSchema.static('findOneWithConditions', findOrderWithConditions);
OrderSchema.method('getCheckoutToken', getCheckoutToken);
/* Initialize model */
const Order = mongoose.model('Order', OrderSchema);
/* Export query and mutation endpoints */
export const orderQueries = {
orders: listOrdersQuery,
order: singleOrderQuery,
};
export const orderMutations = {
makeOrder: makeOrderMutation,
};
/* Define class and instance methods to export */
export function findOrdersWithConditions({
limit,
after,
status,
}: findOrdersWithConditionsArgsType) {
const {
Order,
} = mongoose.models;
const whereClause = {};
if (after) {
if (!mongoose.Types.ObjectId.isValid(after)) {
return [];
}
whereClause._id = {
$gt: after,
};
}
if (status) {
whereClause['subOrders.status'] = status;
}
return Order
.find(whereClause)
.limit(limit)
.sort('_id');
}
/* Endpoint definition */
export const listOrdersQuery = {
type: new GraphQLList(orderType),
args: {
limit: {
type: GraphQLInt,
defaultValue: 5,
},
after: {
type: GraphQLString,
},
status: {
type: GraphQLString,
},
},
resolve: (n: null, args: findOrdersWithConditionsArgsType) =>
mongoose.models.Order.findAllWithConditions(args),
};
export const singleOrderQuery = {
type: orderType,
args: {
id: {
type: new GraphQLNonNull(GraphQLString),
},
},
resolve: (n: null, args: findOrderWithConditionsArgsType) =>
mongoose.models.Order.findOneWithConditions(args),
};
resolve(parent, args, req?, context)
rootValue
orders
members
name
avatar
totalPrice
id
resolve(parent, args, req?, context)
export const listOrdersQuery = {
type: new GraphQLList(orderType),
args: {
limit: {
type: GraphQLInt,
defaultValue: 5,
},
after: {
type: GraphQLString,
},
status: {
type: GraphQLString,
},
},
resolve: (n: null, args: findOrdersWithConditionsArgsType) =>
mongoose.models.Order.findAllWithConditions(args),
};
resolve(parent, args, req?, context)
export type GraphQLResolveInfo = {
fieldName: string,
fieldASTs: Array<Field>,
returnType: GraphQLOutputType,
parentType: GraphQLCompositeType,
schema: GraphQLSchema,
fragments: { [fragmentName: string]: FragmentDefinition },
rootValue: any,
operation: OperationDefinition,
variableValues: { [variableName: string]: any },
}
app.use('/graphql', graphqlHTTP({
schema,
rootValue: {
...models,
},
graphiql: process.env.NODE_ENV !== 'production',
}));
Client
Query
Mutation
Process
Process
Read
Update
Delete
Create
Client
Query
Mutation
Process
Process
Read
Update
Delete
Create
Queue
enqueue
Client
Query
Mutation
Process
Process
Read
Update
Delete
Create
Queue
enqueue
Subscription
Client
Server
Text
Keep-Connection
Push Event
EmitEmitter.emit(name, data);
class EventDistributor extends EventEmitter;
const distributor = new EventDistributor():
app.get('/updateStream', (req, res) => {
let counter = 0;
distributor.on(RECORDS_CHANGE, (data) => {
counter += 1;
res.write(`id: ${counter}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
});
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
});
res.write('\n');
});
const source = new EventSource(`${API_HOST}/updateStream`);
source.addEventListener('message', (e) => {
if (!e.name || e.name !== RECORDS_CHANGE) return;
try {
const ranking = JSON.parse(e.data);
this.setState({
ranking,
});
} catch (ex) {
console.error(ex);
}
});
Server
Client
subscription MyDroidEvents {
members(limit: 5) {
tpe: __typename
eventId
... on NameChanged {
oldName
newName
}
}
}
POST /graphql
...
HTTP/1.1 200 OK
Content-Type: text/event-stream
...
id: 6
data: {"data": {"members": {"tpe": "NameChanged", "eventId": 6, "oldName": "foo", "newName": "bar"}}}
id: 7
data: {"data": {"members": {"tpe": "FriendAdded", "eventId": 7}}}
id: 8
data: {"data": {"members": {"tpe": "NameChanged", "eventId": 8, "oldName": "bar", "newName": "baz"}}}
import { PubSub, SubscriptionManager } from 'graphql-subscriptions';
const pubsub = new PubSub();
const subscriptionType = new GraphQLObject({
name: "Subscription",
fields: {
members: {
type: memberSubscriptionType,
},
},
});
const schema = new GraphQLSchama({
subscription: subscriptionType
});
const subscriptionManager = new SubscriptionManager({
schema,
pubsub,
});
/* ... event emit */
pubsub.publish('members', {
id: 123,
oldName: 'Tony',
newName: 'Jack',
});
import {
SubscriptionClient,
addGraphQLSubscriptions,
} from 'subscriptions-transport-ws';
import ApolloClient, { createNetworkInterface } from 'apollo-client';
const networkInterface = createNetworkInterface({
uri: `http://${API_HOST}/graphql`,
});
const webSocketInterface = new SubscriptionClient(`ws://${API_HOST}/`, {
reconnect: true,
});
const networkInterfaceWithSubscriptions = addGraphQLSubscriptions(
networkInterface,
webSocketInterface,
);
const apolloClient = new ApolloClient({
networkInterface: networkInterfaceWithSubscriptions
});