How can I only call my data source on time if I have two resolvers that rely on it with Apollo? - node.js

I have a restaurant query that returns info on my restaurant. The general information that most of my consumers want comes back from restaurant-general-info.com. There are additional fields though that my consumer might want to know, restaurant-isopen.com, which provides whether or not the restaurant is currently open and the hours that it is open.
I have written two property specific resolvers to handle isOpen and hours as show below:
type Query {
restaurant(name: String): Restaurant
}
type Restaurant {
name: String!,
address: String!,
ownerName: String!,
isOpen: Boolean!,
hours: String!
}
Query: {
restaurant: async(parent, {name}) => {
const response = await axios.get(`https://restaurant-general-info.com/${name}`);
return {
address: response.address
ownerName: response.owner
};
}
}
Restaurant: {
isOpen: async(parent) => {
const response = await axios.get(`https://restaurant-isopen.com/${name}`);
return response.openNow;
},
hours: async(parent) => {
const response = await axios.get(`https://restaurant-isopen.com/${name}`);
return response.hoursOfOperation;
}
}
The problem is that isOpen and hours share the same data source. So I don't want to make the same call twice. I know I could make a property like "open-info" that contains two properties, isOpen and hours, but I don't want the consumers of my graph to need to know / think about how that info is separated differently.
Is there anyway I can have a resolver that could handle multiple fields?
ex.
isOpen && hours: async(parent) => {
const response = await axios.get(`https://restaurant-isopen.com/${name}`);
return {
isOpen: response.openNow,
hours: response.hoursOfOperation
}
},
or is there some smarter way of handling this?
Note: The APIs are not real

This is a classic situation where using DataLoader is going to help you out a lot.
Here is the JS library, along with some explanations: https://github.com/graphql/dataloader
In short, implementing the DataLoader pattern allows you to batch requests, helping you to avoid the classic N+1 problem in GraphQL and mitigate overfetching, whether that involves your database, querying other services/APIs (as you are here), etc.
Setting up DataLoader and batching the keys you're requesting (in this case, openNow and hoursOfOperation) will ensure that the axios GET request will only fire once.
Here is a great Medium article to help you visualize how this works behind the scenes: https://medium.com/#__xuorig__/the-graphql-dataloader-pattern-visualized-3064a00f319f

Related

GraphQL/Mongoose: How to prevent calling the database once per requested field

I'm new to GraphQL but the way I understand is, if I got a User type like:
type User {
email: String
userId: String
firstName: String
lastName: String
}
and a query such as this:
type Query {
currentUser: User
}
implemeting the resolver like this:
Query: {
currentUser: {
email: async (_: any, __: any, ctx: any, ___: any) => {
const provider = getAuthenticationProvider()
const userId = await provider.getUserId(ctx.req.headers.authorization)
const { email } = await UserService.getUserByFirebaseId(userId)
return email;
},
firstName: async (_: any, __: any, ctx: any, ___: any) => {
const provider = getAuthenticationProvider()
const userId = await provider.getUserId(ctx.req.headers.authorization)
const { firstName } = await UserService.getUserByFirebaseId(userId)
return firstName;
}
}
// same for other fields
},
It's clear that something's wrong, since I'm duplicating the code and also the database's being queried once per field requested. Is there a way to prevent code-duplication and/or caching the database call?
How about the case where I need to populate a MongoDB field? Thanks!
I would rewrite your resolver like this:
// import ...;
type User {
email: String
userId: String
firstName: String
lastName: String
}
type Query {
currentUser: User
}
const resolvers = {
Query: {
currentUser: async (parent, args, ctx, info) {
const provider = getAuthenticationProvider()
const userId = await provider.getUserId(ctx.req.headers.authorization)
return UserService.getUserByFirebaseId(userId);
}
}
};
This should work, but... With more information the code could be better as well (see my comment).
More about resolvers you can read here: https://www.apollographql.com/docs/apollo-server/data/resolvers/
A few things:
1a. Parent Resolvers
As a general rule, any given resolver should return enough information to either resolve the value of the child or enough information for the child to resolve it on their own. Take the answer by Ruslan Zhomir. This does the database lookup once and returns those values for the children. The upside is that you don't have to replicate any code. The downside is that the database has to fetch all of the fields and return those. There's a balance act there with trade-offs. Most of the time, you're better off using one resolver per object. If you start having to massage data or pull fields from other locations, that's when I generally start adding field-level resolvers like you have.
1b. Field Level Resolvers
The pattern you're showing of ONLY field-level resolvers (no parent object resolvers) can be awkward. Take your example. What "is expected" to happen if the user isn't logged in?
I would expect a result of:
{
currentUser: null
}
However, if you build ONLY field-level resolvers (no parent resolver that actually looks in the database), your response will look like this:
{
currentUser: {
email: null,
userId: null,
firstName: null,
lastName: null
}
}
If on the other hand, you actually look in the database far enough to verify that the user exists, why not return that object? It's another reason why I recommend a single parent resolver. Again, once you start dealing with OTHER datasources or expensive actions for other properties, that's where you want to start adding child resolvers:
const resolvers = {
Query: {
currentUser: async (parent, args, ctx, info) {
const provider = getAuthenticationProvider()
const userId = await provider.getUserId(ctx.req.headers.authorization)
return UserService.getUserByFirebaseId(userId);
}
},
User: {
avatarUrl(parent) {
const hash = md5(parent.email)
return `https://www.gravatar.com/avatar/${hash}`;
},
friends(parent, args, ctx) {
return UsersService.findFriends(parent.id);
}
}
}
2a. DataLoaders
If you really like the child property resolvers pattern (there's a director at PayPal who EATS IT UP, the DataLoader pattern (and library) uses memoization with cache keys to do a lookup to the database once and cache that result. Each resolver asks the service to fetch the user ("here's the firebaseId"), and that service caches the response. The resolver code you have would be the same, but the functionality on the backend that does the database lookup would only happen once, while the others returned from cache. The pattern you're showing here is one that I've seen people do, and while it's often a premature optimization, it may be what you want. If so, DataLoaders are an answer. If you don't want to go the route of duplicated code or "magic resolver objects", you're probably better off using just a single resolver.
Also, make sure you're not falling victim to the "object of nulls" problem described above. If the parent doesn't exist, the parent should be null, not just all of the children.
2b. DataLoaders and Context
Be careful with DataLoaders. That cache might live too long or return values for people who didn't have access. It is generally, therefore, recommended that the dataLoaders get created for every request. If you look at DataSources (Apollo), it follows this same pattern. The class is instantiated on each request and the object is added to the Context (ctx in your example). There are other dataLoaders that you would create outside of the scope of the request, but you have to solve Least-Used and Expiration and all of that if you go that route. That's also an optimization you need much further down the road.
Is there a way to prevent code-duplication and/or caching the database call?
So first, this
const provider = getAuthenticationProvider()
should actually be injected into the graphql server request's context, such that you would use it in resolver for example as:
ctx.authProvider
The rest follows Dan Crews' answer. Parent resolvers, preferably with dataloaders. In that case you actually won't need authProvider and will use only dataloaders, depending on the entity type & by passing extra variables from context (like user Id)

Prisma client type system creates strong coupling with service methods

I am using prisma ORM with nestjs and it is awesome. Can you please help me understand how can I separate my database layer from my service methods since results produced by prisma client queries are of types generated by prisma client itself ( so i wont be having those types when i shift to lets say typeorm ). how can i prevent such coupling of my service methods returning results of types generated by prisma client and not my custom entities. Hope it makes sense.
The generated #prisma/client library is responsible for generating both the types as well as the custom entity classes. As a result, if you replace Prisma you end up losing both.
Here are two possible workarounds that can decouple the types of your service methods from the Prisma ORM.
Workaround 1: Generate types indepedently of Prisma
With this approach you can get rid of Prisma altogether in the future by manually defining the types for your functions. You can use the types generated by Prisma as reference (or just copy paste them directly). Let me show you an example.
Imagine this is your Prisma Schema.
model Post {
id Int #default(autoincrement()) #id
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
title String #db.VarChar(255)
author User #relation(fields: [authorId], references: [id])
authorId Int
}
model User {
id Int #default(autoincrement()) #id
name String?
posts Post[]
}
You could define a getUserWithPosts function as follows:
// Copied over from '#prisma/client'. Modify as necessary.
type User = {
id: number
name: string | null
}
// Copied over from '#prisma/client'. Modify as necessary.
type Post = {
id: number
createdAt: Date
updatedAt: Date
title: string
authorId: number
}
type UserWithPosts = User & {posts: Post[]}
const prisma = new PrismaClient()
async function getUserWithPosts(userId: number) : Promise<UserWithPosts> {
let user = await prisma.user.findUnique({
where: {
id: userId,
},
include: {
posts: true
}
})
return user;
}
This way, you should be able to get rid of Prisma altogether and replace it with an ORM of your choice. One notable drawback is this approach increases the maintenance burden upon changes to the Prisma schema as you need to manually maintain the types.
Workaround 2: Generate types using Prisma
You could keep Prisma in your codebase simply to generate the #prisma/client and use it for your types. This is possible with the Prisma.validator type that is exposed by the #prisma/client. Code snippet to demonstrate this for the exact same function:
// 1: Define the validator
const userWithPosts = Prisma.validator<Prisma.UserArgs>()({
include: { posts: true },
})
// 2: This type will include a user and all their posts
type UserWithPosts = Prisma.UserGetPayload<typeof userWithPosts>
// function is same as before
async function getUserWithPosts(userId: number): Promise<UserWithPosts> {
let user = await prisma.user.findUnique({
where: {
id: userId,
},
include: {
posts: true
}
})
return user;
}
Additionally, you can always keep the Prisma types updated to your current database state using the Introspect feature. This will work even for changes you have made with other ORMS/Query Builders/SQL.
If you want more details, a lot of what I've mentioned here is touched opon in the Operating against partial structures of your model types concept guide in the Prisma Docs.
Finally, if this dosen't solve your problem, I would request that you open a new issue with the problem and your use case. This really helps us to track and prioritize problems that people are facing.

MongoDB best practice for sorting by non-indexed fields

I have an app that allows users to use their own custom data, so I can't know what the data is. However, I do want to allow them to sort the data.
This can be a significant amount of data, and mongodb ends up giving me memory errors (32MB limit)
What would be the best way to approach this? How can I allow the user to sort a large amount of data by an unknown field?
MongoDB allows you to design the schema in such a way that it can store Objects and Object relation in a schema, So you can allow the user to store any kind of information. As #kevinadi said, there is a limit of 32MB. As of sorting is concerned it can be done on your serverside.
This is an example I tried when storing objects in MongoDB and Mongoose ORM
var mongoose = require("mongoose");
var userSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true,
lowercase: true,
trim: true,
match: [/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/, "Please fill a valid email address"]
},
custInfo:{
type:Object,
required: true
}
isConfirmed: {
type: Boolean,
required: true,
default: false
},
confirmedOn:{
type: Date,
required: true,
default: Date.now()
}
});
module.exports = mongoose.model("user",userSchema);
Since you have tagged this question Meteor I assume, you have the default Meteor environment, where you can use the client-side lightweight Mongo collections.
This gives you the opportunity to publish (Publication) / return (Method) your data mostly unsorted and let the client's handle this task.
Think this: just 100 clients asking for a publication that updates on every sort action (because the subscription parameters change, so the publication changes, too).
This causes already your server to consume a high amount of RAM to keep the observers (OPLOG etc.) running for 100 publications, each querying huge amounts of documents.
Possible performant solutions are described below. Please keep in mind, that they are not bound to any front-end and rather a conceptual description. You will have to include reactivity etc., based on your frontend environment.
Option A - Publish unsorted, let clients sort
server
Meteor.publish('hugeData', function () {
return MyCollection.find({ ...})
})
client
const handle = Meteor.subscribe('hugeData')
if (handle.ready()) {
const sortedData = MyCollection.find({ ... }, {sort: { someField: -1 } })
}
A big plus is here, that you can inform the clients about the completeness status, if using cursor.observeChanges.
Note, that if you want to scan backwards (return docs, with the newest) you can use the hint option on find:
Meteor.publish('hugeData', function () {
return MyCollection.find({ ...}, { hint: { $natural : -1 })
})
This is way more performant than { sort: { fieldName: -1} }.
Option B - return unsorted from Method, let clients sort
Now there may still be a problem with solution A, since it still has a lot of RAM to consume if there are lots of subscribers. An alternative (especially if live-data changes are not so relevant) is to use the Meteor Methods:
server
Meteor.method('hugeData', function () {
return MyCollection.find({ ...}).fetch()
})
Note that this requires to fetch the docs, otherwise and unhandledPromiseRejection is thrown.
client
This requires a LocalCollection on the client, that is not in sync with your server side collection, or you will get problems with document syncing:
const HugeData = new LocalCollection(null) // note the null as collection name!
const insertUpdate = document => {
if (LocalCollection.findOne(document._id)) {
delete document._id
return LocalCollection.update(document._id, document)
} else {
return LocalCollection.insert(document)
}
}
Meteor.call('hudeData', (err, data) => {
data.forEach(insertUpdate)
})
Then you can use the LocalCollection on the client for any projection of the received data.
All in all it is a good tradeoff to move the load to the clients. As long as you keep them informed when projections take a while it should be okay.
My current thought is an additional indexed collection holding 1.entity id, 2 fields name 3.field value.
Have that collection indexed, and then pull ordered entity ids from there, later on loading the full relevant documets by ID.

How to fetch the Limited records(pagination) from the database using GraphQL

I am recently started working with GraphQL. I am able to fetch all the records from the database, but I want to get only limited records (like 10 at a time).
Any one please guide me to achieve this. Thanks
You'd pass the number of items to be returned as a parameter, and then use limit on MongoDB.
Type definition:
fields: () => {
posts: {
resolve: (root, params) => Post.find().limit(params.first)
}
}
Example query:
{
posts(first: 10) {
id
text
date
}
}

LoopBack Remote Methods and Access to Model Data

I've been working on this for hours and I'm completely lost, because the loopback documentation is not helpful.
I'm trying to write application logic into a model. The documentation for that is here. Unfortunately, the example doesn't demonstrate anything useful other than passing an external value into the remote method and returning it again. I'd like to understand how to run a query in this context and access model data, but I have searched for hours and not been able to find documentation on even these simple tasks. Maybe I'm just looking in the wrong places. Can anyone help?
Typically, you can accomplish most things you'd want to do such as querying and accessing model data (CRUD operations) through the built-in methods that all models get; see http://docs.strongloop.com/display/LB/Working+with+data. Defining a remote method (custom REST endpoint) for these would be redundant.
You access the standard model CRUD Node APIs (e.g. myModel.create(), myModel.find(), myModel.updateAll() ) in the remote method code if you want to.
You may also find further related examples in https://github.com/strongloop/loopback-example-app-logic
Here's an example using the Getting Started app https://github.com/strongloop/loopback-getting-started app. It defines a remote method that takes a number arg and prints the name of the coffeeshop with that ID to the console:
This code is in common/models/coffeeshop.js:
module.exports = function(CoffeeShop) {
...
// Return Coffee Shop name given an ID.
CoffeeShop.getName = function(shopId, cb) {
CoffeeShop.findById( shopId, function (err, instance) {
response = "Name of coffee shop is " + instance.name;
cb(null, response);
console.log(response);
});
}
...
CoffeeShop.remoteMethod (
'getName',
{
http: {path: '/getname', verb: 'get'},
accepts: {arg: 'id', type: 'number', http: { source: 'query' } },
returns: {arg: 'name', type: 'string'}
}
);
};
You can use the API Explorer to load http://0.0.0.0:3000/explorer/#!/CoffeeShops/getName then enter a number (there are only three coffee shops in the app initially) as the query param and hit "Try It Out!"
Or just GET a URL like http://0.0.0.0:3000/api/CoffeeShops/getid?id=1
Rand
I finally discovered my problem. Object properties must be loaded in a callback of the function calling the CRUD operation. The following syntax worked for me:
module.exports = function (TestModel) {
TestModel.testRemoteMethod = function (id, name, cb) {
TestModel.findOne({where: {id: id}}, function(err, modelInstance) {
//modelInstance has properties here and can be returned to
//the API call using the callback, for example:
cb(null, {"name": modelInstance.name});
}
}
TestModel.remoteMethod('testRemoteMethod',
//..rest of config

Resources