GraphQL, Apollo : Creating an efficient schema - schema

I recently started studying server development with GraphQL and Apollo.
In the code below, the formula for fetching each data is somewhat understandable.
schema.js
const { gql } = require('apollo-server');
const _ = require('lodash');
const onepieces = [
{
"id": "onepiece1",
"title": "원피스 1권",
"price": "1,360",
"desc": "동터오는 모험의 시대"
},
{
"id": "onepiece2",
"title": "원피스 2권",
"price": "1,360",
"desc": "대결 버기 해적단"
}
];
const narutos = [
{
"id": "naruto1",
"title": "나루토 1권",
"price": "1,360",
"desc": "나루토 모험의 시작"
},
{
"id": "naruto2",
"title": "나루토 2권",
"price": "1,360",
"desc": "나루토와 안개마을"
}
];
const typeDefs = gql`
type Onepiece { id: ID, title: String, price: String, desc: String }
type Naruto { id: ID, title: String, price: String, desc: String }
type Query {
onepiece(id: String!): Onepiece,
naruto(id: String!): Naruto,
getOnepieces: [Onepiece],
getNarutos: [Naruto]
}
`;
const resolvers = {
Query: {
onepiece: (parent, args) => _.find(onepieces, {id: args.id}),
naruto: (parent, args) => _.find(narutos, {id: args.id}),
getOnepieces: () => onepieces,
getNarutos: () => narutos
}
};
module.exports = { typeDefs, resolvers };
But It's inefficient code. If the category of comic book increases, I should continue to add the query. So I want to improve More convenient and readable.
For example, I would like to manage the Onepiece and Naruto categories in Comic Book.
How can I improve?

You might start by writing a GraphQL enum of the possible categories.
enum Category { ONEPIECE NARUTO }
Since both kinds of comic books have the same structure, you can have a single GraphQL type to represent them. We'll incorporate the category we just wrote so that you can tell which one is which.
type ComicBook implements Node {
id: ID!
category: Category!
title: String!
price: String!
desc: String!
}
There's a somewhat standard convention for retrieving arbitrary GraphQL objects by their ID; while it comes from Facebook's Relay Javascript client it's not specifically tied to that client, and I'd use it here.
interface Node {
id: ID!
}
type Query {
node(id: ID!): Node
}
This replaces your top-level queries to retrieve specific kinds of books by ID; you could write a query like
{
node(id: "naruto1") {
... on ComicBook { category title price desc }
}
}
Now that you have the category enum, you can also write a single top-level query to return comic books possibly filtered by category
type Query {
comicBooks(category: Category): [ComicBook!]!
}
{
comicBooks(category: ONEPIECE) { id title price desc }
}
There's some corresponding code changes to make this work; I'd probably start by combining the two lists of comic books into one and adding a similar category field there.
Once you've done this, if you add a third category, you need to add it to the enum and add it to the data set, but you should not need to make any other changes to either the code, the GraphQL schema, or the queries.

Related

GraphQL queries with multiple aliases and Apollo (Vue.js)

I'm trying to fetch data from a single collection type of my Strapi backend into a Vue.js project using Apollo. It works well with a single alias, but I'm having troubles making it work with multiple aliases.
I'm getting my data from a collection type of "campaigns" which has a boolean field of "archive". I want to create an array of "campaigns" that contains all of the campaigns that haven't been archived (archive = false) as well as an array of "archive" that contains all of the archived ones (archive = true).
This is my code:
import gql from "graphql-tag";
export default {
name: "Campaigns",
data() {
return {
campaigns: [],
archive: []
};
},
apollo: {
campaigns: gql`
query getCampaigns {
campaigns: campaigns(where: { archive: "false" }, sort: "order:DESC") {
name
url
}
archive: campaigns(where: { archive: "true" }, sort: "order:DESC") {
name
url
}
}
`
}
The query returns an array of "campaigns", but the array of "archive" is still empty.
I've tried switching things up (put the archive alias first, switched the boolean values to make sure I can generally access the data of the archived campaigns etc.). The problem apparently lies with the "archive"-alias.
When I use the same query with Strapi's GraphQL playground I get the desired result:
{
campaigns: campaigns(where: { archive: "false" }, sort: "order:DESC") {
name
}
archive: campaigns(where: { archive: "true" }, sort: "order:DESC") {
name
}
}
... returns ...
{
"data": {
"campaigns": [
{
"name": "2020"
},
{
"name": "2019"
},
{
"name": "2018"
},
{
"name": "2017"
}
],
"archive": [
{
"name": "2016"
},
{
"name": "2015"
}
]
}
}
How can I make the query work in Vue.js with Apollo?
I think I've found a solution. Technically speaking I guess these are separate queries (which sort of defeats the purpose of aliases if I'm correct) but it does what I want:
apollo: {
campaigns: {
query: gql`
query {
campaigns: campaigns(
where: { archive: "false" }
sort: "order:desc"
) {
name
url
}
}
`
},
archive: {
query: gql`
query {
archive: campaigns(where: { archive: "true" }, sort: "order:desc") {
name
url
}
}
`
}
}
Apparently under some circumstance the initialization "apollo: { XYZ:" and the alias "query { XYZ:" have to match. I've seen in the docs that they don't necessarily have to match, but I don't fully understand when and why.
I guess I can't really tell what the initial parameter does.
You're using campaigns as the key for your entire query, so you need to initialize your data like this:
data() {
return {
campaigns: {
campaigns: [],
archive: [],
},
};
},
Then you can access each list through the key (i.e. campaigns.campaigns and campaigns.archive).
I believe the best way to do this is to use the update property: https://apollo.vuejs.org/guide/apollo/queries.html#name-matching
apollo: {
campaigns: {
query: gql`
query {
campaigns: campaigns(
where: { archive: "false" }
sort: "order:desc"
) {
name
url
}
}
`
},
archive: {
update: data => data.campaigns,
query: gql`
query {
campaigns(where: { archive: "true" }, sort: "order:desc") {
name
url
}
}
`
}
}

Select from multiple tables in sequelize

I'm struggling in how to select from two tables using the Sequelize.
Actually I'm trying to do it:
SELECT * FROM users, clients WHERE user.id = clients.user_id
I have no idea how to user two tables as I described, the only thing I did that got some results were:
const clients = await Client.findAll({
attributes: ["user_id"],
});
const users = [];
for (const client of clients) {
let user = await User.findAll({
where: {
id: {
[Op.eq]: client.user_id
}
}
});
users.push(user);
}
Which return me something:
[
[
{
"id": 1,
"first_name": "Velda",
"middle_name": "Zboncak",
"last_name": "Kris",
"email": "vkris10#hotmail.com",
"created_at": "2020-02-07T20:09:29.484Z",
"updated_at": "2020-02-07T20:09:29.484Z"
}
]
];
Model and Assossiation
First of all, you need to create the correct associations in the model of your table. In this case for the User and the Client, it's supposed to be an Client.belongsTo(...)
Take a look at User model:
const { Model, DataTypes } = require("sequelize");
class User extends Model {
static init(sequelize) {
super.init({
first_name: DataTypes.STRING,
middle_name: DataTypes.STRING,
last_name: DataTypes.STRING,
email: DataTypes.STRING
}, { sequelize });
}
}
module.exports = User;
Take a look at Client model:
const { Model, DataTypes } = require("sequelize");
class Client extends Model {
static init(sequelize) {
super.init({
user_id: DataTypes.INTEGER // The foreign key
}, { sequelize });
}
static associate(models) {
Client.belongsTo(models.User, {
foreignKey: "id", // Column name of associated table
as: "user" // Alias for the table
});
}
}
module.exports = Client;
When associating tables you need to have in mind those values inside the associate method, being the foreignKey: "id" the column name inside the models.ModelName, which will be used to make the joins, and the as: "user" which are used as an alias for the table like SELECT t.column1 FROM table AS t;
Controller
Okay, now you have set your models, you need to set your controller, where the magic happens. As you said you want to fetch results using:
SELECT * FROM users, clients WHERE user.id = clients.user_id
But to achieve the same result you can follow the sql join method to fetch the results from db, so it will be something like this:
SELECT
"user"."first_name", "user"."middle_name", "user"."last_name", "user"."email"
FROM "clients" AS "client"
LEFT JOIN "users" AS "user"
ON "client"."id" = "user"."id";
Knowing that we can talk about including tables in sequelize, which is the same as associations
const Client = require("./path/to/models/Client");
module.exports = {
async fetchAll(req, res) {
const results = await Client.findAll({
limit: 25,
include: [
{
association: "user",
attributes: ["first_name", "middle_name", "last_name", "email"]
}
]
});
return res.json(results);
},
};
Now lets talk about what is going on in the code:
The Model.findAll({}) will fetch all the result inside the specified table, in this case clients table.
The limit: 25 will limit your results in only 25 rows, you are free to remove or edit as you need.
The include: [], it will do the joins through the tables you specify, as you need only the users table, we are going to use only one object, so the assossiation: "user" will make this connection between tables, you must use the same alias you set inside the model. And at least the attributes: ["columns"] is where you set all the fields you want to fetch.
And that's it, you make you request, and the result of this will be exactly the same join as I mentioned. And the results will be:
[
{
"id": 1,
"user_id": 1,
"user": {
"first_name": "John",
"middle_name": "Ironsight",
"last_name": "Doe",
"email": "johndoe#example.com"
}
}, {...}
]
Can use where in include. Find the document at here
let user_id = client.user_id;
users = await User.findAll({
include: [
{
model: Client,
as: 'client',
where: {
user_id: user_id
}
}
]
});

How to configure Typegoose with GraphQL to reference subdocument to part of another document? [duplicate]

I'm pretty new to Mongoose and MongoDB in general so I'm having a difficult time figuring out if something like this is possible:
Item = new Schema({
id: Schema.ObjectId,
dateCreated: { type: Date, default: Date.now },
title: { type: String, default: 'No Title' },
description: { type: String, default: 'No Description' },
tags: [ { type: Schema.ObjectId, ref: 'ItemTag' }]
});
ItemTag = new Schema({
id: Schema.ObjectId,
tagId: { type: Schema.ObjectId, ref: 'Tag' },
tagName: { type: String }
});
var query = Models.Item.find({});
query
.desc('dateCreated')
.populate('tags')
.where('tags.tagName').in(['funny', 'politics'])
.run(function(err, docs){
// docs is always empty
});
Is there a better way do this?
Edit
Apologies for any confusion. What I'm trying to do is get all Items that contain either the funny tag or politics tag.
Edit
Document without where clause:
[{
_id: 4fe90264e5caa33f04000012,
dislikes: 0,
likes: 0,
source: '/uploads/loldog.jpg',
comments: [],
tags: [{
itemId: 4fe90264e5caa33f04000012,
tagName: 'movies',
tagId: 4fe64219007e20e644000007,
_id: 4fe90270e5caa33f04000015,
dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
rating: 0,
dislikes: 0,
likes: 0
},
{
itemId: 4fe90264e5caa33f04000012,
tagName: 'funny',
tagId: 4fe64219007e20e644000002,
_id: 4fe90270e5caa33f04000017,
dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
rating: 0,
dislikes: 0,
likes: 0
}],
viewCount: 0,
rating: 0,
type: 'image',
description: null,
title: 'dogggg',
dateCreated: Tue, 26 Jun 2012 00:29:24 GMT
}, ... ]
With the where clause, I get an empty array.
With a modern MongoDB greater than 3.2 you can use $lookup as an alternate to .populate() in most cases. This also has the advantage of actually doing the join "on the server" as opposed to what .populate() does which is actually "multiple queries" to "emulate" a join.
So .populate() is not really a "join" in the sense of how a relational database does it. The $lookup operator on the other hand, actually does the work on the server, and is more or less analogous to a "LEFT JOIN":
Item.aggregate(
[
{ "$lookup": {
"from": ItemTags.collection.name,
"localField": "tags",
"foreignField": "_id",
"as": "tags"
}},
{ "$unwind": "$tags" },
{ "$match": { "tags.tagName": { "$in": [ "funny", "politics" ] } } },
{ "$group": {
"_id": "$_id",
"dateCreated": { "$first": "$dateCreated" },
"title": { "$first": "$title" },
"description": { "$first": "$description" },
"tags": { "$push": "$tags" }
}}
],
function(err, result) {
// "tags" is now filtered by condition and "joined"
}
)
N.B. The .collection.name here actually evaluates to the "string" that is the actual name of the MongoDB collection as assigned to the model. Since mongoose "pluralizes" collection names by default and $lookup needs the actual MongoDB collection name as an argument ( since it's a server operation ), then this is a handy trick to use in mongoose code, as opposed to "hard coding" the collection name directly.
Whilst we could also use $filter on arrays to remove the unwanted items, this is actually the most efficient form due to Aggregation Pipeline Optimization for the special condition of as $lookup followed by both an $unwind and a $match condition.
This actually results in the three pipeline stages being rolled into one:
{ "$lookup" : {
"from" : "itemtags",
"as" : "tags",
"localField" : "tags",
"foreignField" : "_id",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"tagName" : {
"$in" : [
"funny",
"politics"
]
}
}
}}
This is highly optimal as the actual operation "filters the collection to join first", then it returns the results and "unwinds" the array. Both methods are employed so the results do not break the BSON limit of 16MB, which is a constraint that the client does not have.
The only problem is that it seems "counter-intuitive" in some ways, particularly when you want the results in an array, but that is what the $group is for here, as it reconstructs to the original document form.
It's also unfortunate that we simply cannot at this time actually write $lookup in the same eventual syntax the server uses. IMHO, this is an oversight to be corrected. But for now, simply using the sequence will work and is the most viable option with the best performance and scalability.
Addendum - MongoDB 3.6 and upwards
Though the pattern shown here is fairly optimized due to how the other stages get rolled into the $lookup, it does have one failing in that the "LEFT JOIN" which is normally inherent to both $lookup and the actions of populate() is negated by the "optimal" usage of $unwind here which does not preserve empty arrays. You can add the preserveNullAndEmptyArrays option, but this negates the "optimized" sequence described above and essentially leaves all three stages intact which would normally be combined in the optimization.
MongoDB 3.6 expands with a "more expressive" form of $lookup allowing a "sub-pipeline" expression. Which not only meets the goal of retaining the "LEFT JOIN" but still allows an optimal query to reduce results returned and with a much simplified syntax:
Item.aggregate([
{ "$lookup": {
"from": ItemTags.collection.name,
"let": { "tags": "$tags" },
"pipeline": [
{ "$match": {
"tags": { "$in": [ "politics", "funny" ] },
"$expr": { "$in": [ "$_id", "$$tags" ] }
}}
]
}}
])
The $expr used in order to match the declared "local" value with the "foreign" value is actually what MongoDB does "internally" now with the original $lookup syntax. By expressing in this form we can tailor the initial $match expression within the "sub-pipeline" ourselves.
In fact, as a true "aggregation pipeline" you can do just about anything you can do with an aggregation pipeline within this "sub-pipeline" expression, including "nesting" the levels of $lookup to other related collections.
Further usage is a bit beyond the scope of what the question here asks, but in relation to even "nested population" then the new usage pattern of $lookup allows this to be much the same, and a "lot" more powerful in it's full usage.
Working Example
The following gives an example using a static method on the model. Once that static method is implemented the call simply becomes:
Item.lookup(
{
path: 'tags',
query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
},
callback
)
Or enhancing to be a bit more modern even becomes:
let results = await Item.lookup({
path: 'tags',
query: { 'tagName' : { '$in': [ 'funny', 'politics' ] } }
})
Making it very similar to .populate() in structure, but it's actually doing the join on the server instead. For completeness, the usage here casts the returned data back to mongoose document instances at according to both the parent and child cases.
It's fairly trivial and easy to adapt or just use as is for most common cases.
N.B The use of async here is just for brevity of running the enclosed example. The actual implementation is free of this dependency.
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.connect('mongodb://localhost/looktest');
const itemTagSchema = new Schema({
tagName: String
});
const itemSchema = new Schema({
dateCreated: { type: Date, default: Date.now },
title: String,
description: String,
tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
});
itemSchema.statics.lookup = function(opt,callback) {
let rel =
mongoose.model(this.schema.path(opt.path).caster.options.ref);
let group = { "$group": { } };
this.schema.eachPath(p =>
group.$group[p] = (p === "_id") ? "$_id" :
(p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });
let pipeline = [
{ "$lookup": {
"from": rel.collection.name,
"as": opt.path,
"localField": opt.path,
"foreignField": "_id"
}},
{ "$unwind": `$${opt.path}` },
{ "$match": opt.query },
group
];
this.aggregate(pipeline,(err,result) => {
if (err) callback(err);
result = result.map(m => {
m[opt.path] = m[opt.path].map(r => rel(r));
return this(m);
});
callback(err,result);
});
}
const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);
function log(body) {
console.log(JSON.stringify(body, undefined, 2))
}
async.series(
[
// Clean data
(callback) => async.each(mongoose.models,(model,callback) =>
model.remove({},callback),callback),
// Create tags and items
(callback) =>
async.waterfall(
[
(callback) =>
ItemTag.create([{ "tagName": "movies" }, { "tagName": "funny" }],
callback),
(tags, callback) =>
Item.create({ "title": "Something","description": "An item",
"tags": tags },callback)
],
callback
),
// Query with our static
(callback) =>
Item.lookup(
{
path: 'tags',
query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
},
callback
)
],
(err,results) => {
if (err) throw err;
let result = results.pop();
log(result);
mongoose.disconnect();
}
)
Or a little more modern for Node 8.x and above with async/await and no additional dependencies:
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/looktest';
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
const itemTagSchema = new Schema({
tagName: String
});
const itemSchema = new Schema({
dateCreated: { type: Date, default: Date.now },
title: String,
description: String,
tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
});
itemSchema.statics.lookup = function(opt) {
let rel =
mongoose.model(this.schema.path(opt.path).caster.options.ref);
let group = { "$group": { } };
this.schema.eachPath(p =>
group.$group[p] = (p === "_id") ? "$_id" :
(p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });
let pipeline = [
{ "$lookup": {
"from": rel.collection.name,
"as": opt.path,
"localField": opt.path,
"foreignField": "_id"
}},
{ "$unwind": `$${opt.path}` },
{ "$match": opt.query },
group
];
return this.aggregate(pipeline).exec().then(r => r.map(m =>
this({ ...m, [opt.path]: m[opt.path].map(r => rel(r)) })
));
}
const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);
const log = body => console.log(JSON.stringify(body, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
// Clean data
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
// Create tags and items
const tags = await ItemTag.create(
["movies", "funny"].map(tagName =>({ tagName }))
);
const item = await Item.create({
"title": "Something",
"description": "An item",
tags
});
// Query with our static
const result = (await Item.lookup({
path: 'tags',
query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
})).pop();
log(result);
mongoose.disconnect();
} catch (e) {
console.error(e);
} finally {
process.exit()
}
})()
And from MongoDB 3.6 and upward, even without the $unwind and $group building:
const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/looktest';
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
const itemTagSchema = new Schema({
tagName: String
});
const itemSchema = new Schema({
title: String,
description: String,
tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
},{ timestamps: true });
itemSchema.statics.lookup = function({ path, query }) {
let rel =
mongoose.model(this.schema.path(path).caster.options.ref);
// MongoDB 3.6 and up $lookup with sub-pipeline
let pipeline = [
{ "$lookup": {
"from": rel.collection.name,
"as": path,
"let": { [path]: `$${path}` },
"pipeline": [
{ "$match": {
...query,
"$expr": { "$in": [ "$_id", `$$${path}` ] }
}}
]
}}
];
return this.aggregate(pipeline).exec().then(r => r.map(m =>
this({ ...m, [path]: m[path].map(r => rel(r)) })
));
};
const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);
const log = body => console.log(JSON.stringify(body, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
// Clean data
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
// Create tags and items
const tags = await ItemTag.insertMany(
["movies", "funny"].map(tagName => ({ tagName }))
);
const item = await Item.create({
"title": "Something",
"description": "An item",
tags
});
// Query with our static
let result = (await Item.lookup({
path: 'tags',
query: { 'tagName': { '$in': [ 'funny', 'politics' ] } }
})).pop();
log(result);
await mongoose.disconnect();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
what you are asking for isn't directly supported but can be achieved by adding another filter step after the query returns.
first, .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } ) is definitely what you need to do to filter the tags documents. then, after the query returns you'll need to manually filter out documents that don't have any tags docs that matched the populate criteria. something like:
query....
.exec(function(err, docs){
docs = docs.filter(function(doc){
return doc.tags.length;
})
// do stuff with docs
});
Try replacing
.populate('tags').where('tags.tagName').in(['funny', 'politics'])
by
.populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } )
Update: Please take a look at the comments - this answer does not correctly match to the question, but maybe it answers other questions of users which came across (I think that because of the upvotes) so I will not delete this "answer":
First: I know this question is really outdated, but I searched for exactly this problem and this SO post was the Google entry #1. So I implemented the docs.filter version (accepted answer) but as I read in the mongoose v4.6.0 docs we can now simply use:
Item.find({}).populate({
path: 'tags',
match: { tagName: { $in: ['funny', 'politics'] }}
}).exec((err, items) => {
console.log(items.tags)
// contains only tags where tagName is 'funny' or 'politics'
})
Hope this helps future search machine users.
After having the same problem myself recently, I've come up with the following solution:
First, find all ItemTags where tagName is either 'funny' or 'politics' and return an array of ItemTag _ids.
Then, find Items which contain all ItemTag _ids in the tags array
ItemTag
.find({ tagName : { $in : ['funny','politics'] } })
.lean()
.distinct('_id')
.exec((err, itemTagIds) => {
if (err) { console.error(err); }
Item.find({ tag: { $all: itemTagIds} }, (err, items) => {
console.log(items); // Items filtered by tagName
});
});
#aaronheckmann 's answer worked for me but I had to replace return doc.tags.length; to return doc.tags != null; because that field contain null if it doesn't match with the conditions written inside populate.
So the final code:
query....
.exec(function(err, docs){
docs = docs.filter(function(doc){
return doc.tags != null;
})
// do stuff with docs
});

GraphQL field resolver needs contextual information

Maybe my terminology is not accurate. I'm using AWS AppSync. My schema:
type Book {
title: String
author: Author
}
type Author {
name: String
}
type Query {
getBook(title:String!): Book
}
The resolver for getBook returns an object shaped as:
{
title: <string>
authorId: <number>
}
Where authorId is always returned.
What I'd like to do is specify a resolver for the field Book.author that will receive authorId and fetch that object from its own data store. Is this possible?
If what I'm trying to do is not possible, what is the proper way of doing this, where one data store is a table with two columns - { title, authorId }, and a separate store has a table with a list of authors, where the primary key is a column authorId. Since these are two different services, I can't just join the two like a SQL query.
As long as authorId is returned from the getBook resolver, it will be accessible via $ctx.source.authorId when resolving Book.author.
I reproduced your API with local resolvers using your schema:
Query.getBook request mapping template:
{
"version": "2018-05-29",
"payload": {
"title": "$context.arguments.title",
"authorId": "2" ## returned in addition to other fields. It will be used by Book.author resolver.
}
}
Query.getBook response mapping template:
$util.toJson($context.result)
Book.author request mapping template:
{
"version": "2018-05-29",
"payload": {
"name": "author name with authorId: $context.source.authorId"
}
}
Book.author response mapping template:
$util.toJson($context.result)
The following query:
query {
getBook(title:"AWS AppSync") {
title
author {
name
}
}
}
will yield the results:
{
"data": {
"getBook": {
"title": "AWS AppSync",
"author": {
"name": "author name with authorId: 2"
}
}
}
}
You might need to have bookID as parent's ID inside Author:
type Author {
# parent's id
bookID: ID!
# author id
id: ID!
name: String!
}
type Book {
id: ID!
title: String!
author: Author!
}
When Create Resource, just make:
- Book.id as primary key of BookTable
- Author.bookID as primary key and Author.id as sort key of
AuthorTable
You also need to attach resolver for Book.author using $ctx.source.id
After you attach Book.author resolver, you are good to go. You can get result something like below:
getBook(title: "xx") {
id
title
author {
id
name
}
}

Parse Out ES _source

I've created my first GET API call that does a fuzzy search against an Elastic Search index and so far everything works well (w00t w00t). I'm stuck on one piece though; I'd like to parse out some of the information returned from ElasticSearch, specifically in the _source section. I've done some recon and discovered that you can use a mapping function to do this, but I can't get the results to display on the page or console. So the question is, let's say I just wanted to pull out the name field from _source is that possible and is there a preferred method to do it?
router.get('/suggest/:search', function(req, res, next) {
const searchTerm = req.params.search;
client.search({
index: 'product_pipeline_import',
type: 'jdbc-demo',
body: {
suggest: {
productSuggest: {
prefix: searchTerm,
completion: {
field: 'name',
fuzzy: {
fuzziness: 2
}
}
}
}
}
}).then(function(resp) {
var hits = resp.hits.hits.map(function(hit){
console.log(hit._source)
});
}, function(err) {
console.trace(err.message);
});
})
What I'm trying to parse from:
{
"text": "Philips - Brilliance 27\" IPS LED HD Monitor - Text",
"_index": "test-index",
"_type": "products",
"_id": "Philips - Brilliance 27\" IPS LED HD Monitor - Textured Black",
"_score": 5,
"_source": {
"name": "Philips - Brilliance 27\" IPS LED HD Monitor - Textured Black",
"#version": "1",
"model": "272P4APJKEB",
"#timestamp": "2017-11-19T20:56:11.537Z",
"type": "products",
"manufacturer": "Philips"
}