GraphQL field resolver needs contextual information - api

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
}
}

Related

Join two collection in mongodb

I'm new in mongodb. Could you please tell me how to perform join operation in this. I've two collection:
Collection 1 ("user")
{
_id: "d04d53dc-fb88-433e-a1c5-dd41a68d7655",
userName: "XYZ User",
age: 12
}
Collection 2 ("square")
{
_id: "ef6f6ac2-a08a-4f68-a63c-0b4a70285427",
userId: "d04d53dc-fb88-433e-a1c5-dd41a68d7655",
side: 4,
area: 16
}
Now I want to retrieve the data from collection 2 is like this.
Expected output:
{
_id: "ef6f6ac2-a08a-4f68-a63c-0b4a70285427",
userId: "d04d53dc-fb88-433e-a1c5-dd41a68d7655",
userName: "XYZ User",
side: 4,
area: 16
}
Thanks in advance :)
Here's one way to do it.
db.square.aggregate([
{
"$lookup": {
"from": "user",
"localField": "userId",
"foreignField": "_id",
"as": "userDoc"
}
},
{
"$set": {
"userName": {
"$first": "$userDoc.userName"
}
}
},
{ "$unset": "userDoc" }
])
Try it on mongoplayground.net.
You can keep the first documentid (_id) in the second document as userId for refrence and after that, you can use the join feature supported by MongoDB 3.2 and later versions. You can use joins by using an aggregate query.
You can do it using the below example :
db.user.aggregate([
// Join with square table
{
$lookup:{
from: "square", // other table name
localField: "_id", // name of user table field
foreignField: "userId", // name of square table field
as: "square" // alias for userinfo table
}
},
{ $unwind:"$user_info" }, // $unwind used for getting data in object or for one record only
// define some conditions here
{
$match:{
$and:[{"userName" : "XYZ User"}]
}
},
// define which fields are you want to fetch
{
$project:{
_id: 1,
userId: "$square.userId",
userName: 1,
side: "$square.side",
area: "$square.area"
}
}
]);
The Result will be
{
_id: "ef6f6ac2-a08a-4f68-a63c-0b4a70285427",
userId: "d04d53dc-fb88-433e-a1c5-dd41a68d7655",
userName: "XYZ User",
side: 4,
area: 16
}
Cheers

How to create or update many-to-many relation in Prisma?

I have the following models, and many-to-many relation between them:
model User {
id String #id #default(cuid())
name String?
email String? #unique
followings Artist[]
}
model Artist {
id String #id #default(cuid())
name String #unique
spotifyId String #unique
followers User[]
}
When a user logs into my app, I retrieve their current followed artists, and need to update my database.
I have managed to select artists data from database (for updating user <-> artist relation), sample data:
const followings = [
{
id: '...',
name: 'MARINA',
spotifyId: '6CwfuxIqcltXDGjfZsMd9A'
},
{
id: '...',
name: 'Dua Lipa',
spotifyId: '6M2wZ9GZgrQXHCFfjv46we'
},
]
Now, this is my user object:
const user = {
id: 'someId',
name: 'someName',
email: 'someEmail'
}
I tried to insert or update user <-> artist relation with this query but I'm getting Bad Request error:
await prisma.user.upsert({
where: {
email: user.email
},
create: {
name: user.name,
email: user.email,
followings: {
connectOrCreate: followings
}
},
update: {
followings: {
connectOrCreate: followings
}
}
})
Please advise what I need to do. Thanks in advance.
P.S. I took the idea of the query from Updating a many-to-many relationship in Prisma post, but it didn't work for me, so please don't mark duplicate.
connectOrCreate should specify where key with id (so Prisma could find this entity) and create key with all required model fields (so Prisma could create it if it not already present), but you just passing an array of models. Change your code to this one:
await prisma.user.upsert({
where: {
email: 'user.email',
},
create: {
name: 'user.name',
email: 'user.email',
followings: {
connectOrCreate: [
{
create: {
name: 'MARINA',
spotifyId: '6CwfuxIqcltXDGjfZsMd9A',
},
where: { id: '...' },
},
],
},
},
update: {
followings: {
connectOrCreate: [
{
create: {
name: 'MARINA',
spotifyId: '6CwfuxIqcltXDGjfZsMd9A',
},
where: { id: '...' },
},
],
},
},
});

How not to expose duplicated (normalize?) nodes via GraphQL?

Given "user has many links" (what means a link was created by a user) DB entities relations, I want to develop API to fetch links along with users so that the returned data does not contain duplicated users.
In other words, instead of this request:
query {
links {
id
user {
id email
}
}
}
that returns the following data:
{
"data": {
"links": [
{
"id": 1,
"user": {
"id": 2,
"email": "user2#example.com"
}
},
{
"id": 2,
"user": {
"id": 2,
"email": "user2#example.com"
}
}
]
}
}
I want to make a request like this (note the "references" column):
query {
links {
id
userId
}
references {
users {
id
email
}
}
}
that returns associated users without duplicates:
{
"data": {
"links": [
{
"id": 1,
"userId": 2
},
{
"id": 2,
"userId": 2
},
],
"references": {
"users": [
{
"id": 2,
"email": "user2#example.com"
}
]
}
}
}
That should reduce amount of data transferred between client and server that adds a bit of speed boost.
Is there ready common implementation on any language of that idea? (Ideally, seeking for Ruby)
It's not a query or server role to normalize data.
there are no such possibilities in GraphQL specs;
server must return all asked fields within queried [response] structure;
... but you can implement some:
standarized (commonly used) pagination (relay style edges/nodes, nodes only or better both);
query [complexity] weights to promote this optimized querying style - separate problem;
reference dictionary field within queried type;
links {
egdes {
node {
id
title
url
authorId
# possible but limited usage with heavy weights
# author {
# id
# email
# }
}
}
pageInfo {
hasNextPages
}
referencedUsers {
id
email
}
}
where:
User has id and email props;
referencedUsers is [User!] type;
node.author is User type;
Normalizing Graphql client, like Apollo, can easily access cached user fields without making separate requests.
You can render (react?) some <User/> component (within <Link /> component) passing node.authorId as an argument like <User id={authorId} />. User component can useQuery hook with cache-only policy to read user props/fields.
See Apollo docs for details. You should implement this for yourself and document this to help/guide API users.

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
}
}
]
});

GraphQL, Apollo : Creating an efficient 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.