Apollo GraphQL server for Moleculer API Gateway
npm install moleculer-apollo-serverApollo GraphQL server 5 mixin for Moleculer API Gateway
npm i moleculer-apollo-server moleculer-web graphql
`Usage
This example demonstrates how to setup a Moleculer API Gateway with GraphQL mixin in order to handle incoming GraphQL requests via the default /graphql endpoint.`js
"use strict";const ApiGateway = require("moleculer-web");
const { ApolloService } = require("moleculer-apollo-server");
module.exports = {
name: "api",
mixins: [
// Gateway
ApiGateway,
// GraphQL Apollo Server
ApolloService({
// Global GraphQL typeDefs
typeDefs:
, // Global resolvers
resolvers: {},
// API Gateway route options
routeOptions: {
path: "/graphql",
cors: true,
mappingPolicy: "restrict"
},
// https://www.apollographql.com/docs/apollo-server/api/apollo-server#options
serverOptions: {}
})
]
};
`Start your Moleculer project, open http://localhost:3000/graphql in your browser to run queries using Apollo Sandbox or send GraphQL requests directly to the same URL.
Define queries & mutations in service action definitions
`js
module.exports = {
name: "greeter", actions: {
hello: {
graphql: {
query: "hello: String"
},
handler(ctx) {
return "Hello Moleculer!"
}
},
welcome: {
params: {
name: "string"
},
graphql: {
mutation: "welcome(name: String!): String"
},
handler(ctx) {
return
Hello ${ctx.params.name};
}
}
}
};
`Generated schema
`gql
type Mutation {
welcome(name: String!): String
}type Query {
hello: String
}
`$3
posts.service.js
`js
module.exports = {
name: "posts",
settings: {
graphql: {
type: ,
resolvers: {
Post: {
author: {
// Call the users.resolve action with id params
action: "users.resolve",
rootParams: {
"author": "id"
}
},
voters: {
// Call the users.resolve action with id params
action: "users.resolve",
rootParams: {
"voters": "id"
}
}
}
}
}
},
actions: {
find: {
//cache: true,
params: {
limit: { type: "number", optional: true }
},
graphql: {
query: posts(limit: Int): [Post]
},
handler(ctx) {
let result = _.cloneDeep(posts);
if (ctx.params.limit)
result = posts.slice(0, ctx.params.limit);
else
result = posts; return _.cloneDeep(result);
}
},
findByUser: {
params: {
userID: "number"
},
handler(ctx) {
return _.cloneDeep(posts.filter(post => post.author == ctx.params.userID));
}
},
}
};
`users.service.js
`js
module.exports = {
name: "users",
settings: {
graphql: {
type: ,
resolvers: {
User: {
posts: {
// Call the posts.findByUser action with userID param.
action: "posts.findByUser",
rootParams: {
"id": "userID"
}
},
postCount: {
// Call the "posts.count" action
action: "posts.count",
// Get id value from root and put it into ctx.params.query.author
rootParams: {
"id": "query.author"
}
}
}
}
}
},
actions: {
find: {
//cache: true,
params: {
limit: { type: "number", optional: true }
},
graphql: {
query: "users(limit: Int): [User]"
},
handler(ctx) {
let result = _.cloneDeep(users);
if (ctx.params.limit)
result = users.slice(0, ctx.params.limit);
else
result = users; return _.cloneDeep(result);
}
},
resolve: {
params: {
id: [
{ type: "number" },
{ type: "array", items: "number" }
]
},
handler(ctx) {
if (Array.isArray(ctx.params.id)) {
return _.cloneDeep(ctx.params.id.map(id => this.findByID(id)));
} else {
return _.cloneDeep(this.findByID(ctx.params.id));
}
}
}
}
};
`$3
moleculer-apollo-server supports DataLoader via configuration in the resolver definition.
The called action must be compatible with DataLoader semantics -- that is, it must accept params with an array property and return an array of the same size,
with the results in the same order as they were provided.To activate DataLoader for a resolver, simply add
dataLoader: true to the resolver's property object in the resolvers property of the service's graphql property:`js
module.exports = {
settings: {
graphql: {
resolvers: {
Post: {
author: {
action: "users.resolve",
dataLoader: true,
rootParams: {
author: "id",
},
},
voters: {
action: "users.resolve",
dataLoader: true,
rootParams: {
voters: "id",
},
},
// ...
}
}
}
}
};
`
Since DataLoader only expects a single value to be loaded at a time, only one rootParams key/value pairing will be utilized, but params and GraphQL child arguments work properly.You can also specify options for construction of the DataLoader in the called action definition's
graphql property. This is useful for setting things like maxBatchSize'.``js`
resolve: {
params: {
id: [{ type: "number" }, { type: "array", items: "number" }],
graphql: { dataLoaderOptions: { maxBatchSize: 100 } },
},
handler(ctx) {
this.logger.debug("resolve action called.", { params: ctx.params });
if (Array.isArray(ctx.params.id)) {
return _.cloneDeep(ctx.params.id.map(id => this.findByID(id)));
} else {
return _.cloneDeep(this.findByID(ctx.params.id));
}
},
},
It is unlikely that setting any of the options which accept a function will work properly unless you are running moleculer in a single-node environment. This is because the functions will not serialize and be run by the moleculer-web Api Gateway.
- Simple
- npm run devnpm run dev full
- Full
- DATALOADER
- Full With Dataloader
- set environment variable to "true"npm run dev full
- npm run test:ts
- Typescript
-
$ npm test
`In development with watching
`
$ npm run ci
`` 