How to Implement caching on apollo-server-hapi graphql - caching

i have graphql with apollo-server-hapi. i try to add cache control like below:
const graphqlOptions = {
schema,
tracing: true,
cacheControl: true,
};
but when i try to add cache option on schema base:
type Author #cacheControl(maxAge: 60) {
id: Int
firstName: String
lastName: String
posts: [Post]
}
i got this error message:
Error: Unknown directive "cacheControl".
can you help, what is the correct way to apply cache control on schema?
i follow instruction from below, but it seem didn't work.
apollo-cache-control

After learn more about caching on apollo graphql, basically, the issue was with makeExecutableSchema from apollo-server-hapi, didn't include directive for #cacheControl, so to make it work, we just need to define our own #cacheControl directive into graphql file, as below:
enum CacheControlScope {
PUBLIC
PRIVATE
}
directive #cacheControl (
maxAge: Int
scope: CacheControlScope
) on FIELD_DEFINITION | OBJECT | INTERFACE
type Author #cacheControl(maxAge: 60) {
id: Int
firstName: String
lastName: String
posts: [Post]
}

The following worked for me in "apollo-server-express": "^2.9.12" :
1.- Set global maximum cache:
var graphqlServer = new ApolloServer({
cacheControl: {
defaultMaxAge: 1000,
},
...
2.- Define the following directive in your schema:
// Schema (root query)
const Query = gql`
directive #cacheControl(
maxAge: Int,
scope: CacheControlScope
) on OBJECT | FIELD | FIELD_DEFINITION
enum CacheControlScope {
PUBLIC
PRIVATE
}
type Query {
...
3.- Finally, call it:
module.exports = `
type ArticlePage #cacheControl(maxAge: 801){
article(id: String) : Article
author(key: String) : Author
}`;
The trick is that #cacheControl(maxAge: 801) cannot be higher than defaultMaxAge: 1000.
Good Luck!

I was also apollo-server-lambda and the main issue comes from using makeExecutableSchema. The docs mention this is cause by schema stitching.
Unfortunately if you use something like graphql-middleware there is no way around this except for what hinduni mentions. Also make sure you are on apollo-server > 2.6.6.

Related

GraphQL Codegen duplicates RegisterDocument with typescript-urql

The codegen.ts config below results in duplicating the RegisterDocument entries.
codegen.ts:
const config: CodegenConfig = {
overwrite: true,
schema: "http://localhost:4000/graphql",
documents: "src/graphql/**/*.graphql",
generates: {
"src/generated/graphql": {
preset: "client",
plugins: [
"typescript-urql"
],
config: {
documentVariableSuffix: 'test2'
}
}
}
};
the output:
export const RegisterDocument = {"kind":"Document", ...}
export const RegisterDocument = gql`
mutation Register($username: String!, $password: String!) {
register(options: {username: $username, password: $password}) {
errors {
field
message
}
user {
id
username
createdAt
}
}
}
`;
export function useRegisterMutation() {
return Urql.useMutation<RegisterMutation, RegisterMutationVariables>(RegisterDocument);
};
Seemingly either the documentVariableSuffix param didn't affect the output const naming or it was a wrong param. The use of the typescript-operations or/and typescript packages only led to more duplicates.
What is the way to have typescript-urql register the mutation differently?
UP. The register mutation I need typings for:
const registerMutationDocument = graphql(`
mutation Register($username: String!, $password: String!) {
register(options: { username: $username, password: $password }) {
errors {
field
message
}
user {
id
username
createdAt
}
}
}
`)
I'm Charly, from The Guild, working on GraphQL Code Generator.
The preset: "client" is not meant to be used in combination with other plugins.
You must use either the client-preset or typescript-urql-plugin which provides 2 different ways to get typed GraphQL Operations.
The typescript-urql-plugin generates React hooks while, the client-preset generated typed GraphQL documents that can be used with URQL's native useQuery() and useMutation().
We now recommend using the client-preset that provides a better developer experience and smaller generated code for the same result.
You will find a guide to setup the client-preset with URQL here: https://the-guild.dev/graphql/codegen/docs/guides/react-vue
After a few attempts with various code pieces it seems I've got it to work. Thank you mr. Poly for the hints.
Here's the take.
First, the present iteration of graphql-codegen watches for .ts/.tsx documents not *.graphql ones. Second the only plugins needed are the ones listed in the docs.
const config: CodegenConfig = {
overwrite: true,
schema: "http://localhost:4000/graphql",
documents: "src/graphql/mutations/*.ts",
generates: {
"src/generated/graphql/": {
preset: "client",
plugins: []
}
}
};
Third the way to put the mutations etc. to a dedicated folder that I used was to create one at src/graphql/mutations. The register.ts holding the mutation had the following code:
import { graphql } from '../../generated/graphql';
export const registerMutationDocument = graphql(`
mutation Register($username: String!, $password: String!) {
register(options: { username: $username, password: $password }) {
errors {
field
message
}
user {
id
username
createdAt
}
}
}
`);
The only problem for me was if I tried to add a field to it that didn't exist on the model the editor froze.
The usage within the component:
import { registerMutationDocument } from '../graphql/mutations/register';
...
const [, register] = useMutation(registerMutationDocument);

Custom schema, interface, #fileByRelativePath and gatsby-image

I'm trying to get an interface working with the new #fileByRelativePath resolver extension, to keep compatible with v3.
I'm using Prismic for my content, and gatsby-source-prismic v2. I have two content types in Prismic, and created the interface to be able to more easily query and map over both for a home page index.
Here's the functioning (but with deprecated inferred resolvers) schema:
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
interface indexPosts #nodeInterface {
id: ID!
uid: String!
data: Data!
type: String!
}
type Data {
title: Title!
date: Date!
featured: String!
featured_image: Featured_image!
body: Body!
}
type Title {
text: String!
}
type Featured_image {
localFile: File!
}
type Body {
html: String!
}
type PrismicGallery implements Node & indexPosts {
uid: String!
data: Data!
type: String!
}
type PrismicEssay implements Node & indexPosts {
uid: String!
data: Data!
type: String!
}
`
createTypes(typeDefs)
}
The problem comes after adding #fileByRelativePath to the Featured_image type definition. Doing so gives me an error during build:
"The "path" argument must be of type string. Received type undefined"
I'm unsure how to provide the necessary path argument, considering my images are third-party hosted. I'm trying to follow the brief guide at the end of this page and suspect the way to do it might be with a resolver or type builder and using 'source' to access the url field provided by both localFile and its parent, featured_image, but I can't figure it out!
I'm using gatsby-image and the childImageSharp convenience field to present the images, if that makes a difference at all!
I had exactly the same problem when I tried to use #fileByRelativePath. I managed to solve my problem by using #infer on the type that contained the File.
Try this:
type Featured_image #infer {
localFile: File!
}

Unable to use Fragments on GraphQL-yoga with Primsa

I am using graphql-yoga with Prisma and Prisma-Bindings
I'm trying to add a fragment to my resolver so that a specific field (id in this situation) is always fetched when the user asks for a custom field, costsToDate.
This is so i can make some additional queries needed to build the result for that field, and i need the ID of the object for that.
Unfortunatley i can't seem to get it to work, and the documentations seems a little lacking on the specifics with graphql-yoga and Prisma.
Here is the definition of the type:
type Job {
id: ID!
projectNumber: String!
client: Client!
name: String!
description: String
quoteNumber: String
workshopDaysQuoted: String!
quoted: String!
targetSpend: String!
costs: [JobCost!]!
estimatedCompletion: DateTime
completed: Boolean!
costTotal: String
workshopDaysUsed: String
costsToDate: String
}
And here is the resolver for the query:
const jobs = {
fragment: `fragment description on Jobs { id }`,
resolve: jobsResolver
}
async function jobsResolver(root, args, context, info) {
await validatePermission(args,context,info,['admin','user','appAuth'])
const {showCompleted} = args
const completedFilter = (typeof showCompleted === 'boolean') ? { completed: showCompleted } : {}
const jobIDS = await context.db.query.jobs({ where: completedFilter }, `{ id }`)
//console.log(jobIDS);
let jobs = await context.db.query.jobs({
where: completedFilter
}, info)
return await getAllJobCostsToDateList(jobs)
}
I am applying the the fragmentReplacements as per below.
const fragmentReplacements = extractFragmentReplacements(resolvers)
console.log(fragmentReplacements)
const port = process.env.PORT || 3010
const graphQLServer = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
resolverValidationOptions: {
requireResolversForResolveType: false
},
context: req => ({
...req,
db: new Prisma({
typeDefs: `src/generated/prisma.graphql`,
fragmentReplacements,
endpoint: PRISMA_ENDPOINT,
secret: PRISMA_KEY,
debug: false
})
})
})
If i console.log the fragmentReplacements object i get the following, so it does seem to be picking up the fragments.
[ { field: 'job', fragment: 'fragment costsToDate on Job { id }' },
{ field: 'jobs',
fragment: 'fragment costsToDate on Jobs { id }' } ]
So my expectation here is that if i make a query against jobs or job that asks for the costsToDate field that it will also fetch the id for the job/each job.
However if i make the following query.
query{
jobs{
description
costsToDate
}
}
But i see no id fetched, and nothing in the root parameter on the resolver function.
Apologies as i am probably barking up completely the wrong tree here, seems like a somewhat simple requirement, but i can't quite work it out. Sure i'm missing something fundamental.
Thanks!
Gareth
A fragment is used to always retrieve given fields on a given type.
It follows the following format:
fragment NameOfYourFragment on YourType { ... }
You currently can't apply a given fragment conditionally as it is always applied.
Moreover, you specified a fragment on Jobs, but the type name used by Prisma is Job (even if you have the job and jobs resolvers)
You probably only need the following resolver:
const job = {
fragment: `fragment JobId on Job { id }`,
resolve: jobsResolver
}

Graphql Object that implements an Interface is not not inheriting resolver from Interface

I am using graphql-tools to build a GraphQL Schema, esentially I have this structure
const typeDefs = `
type Query {
author(name: String): Author
}
interface Person {
id: Int
name: String
citizenship: String
type Author implements Person {
id: Int
name: String
citizenship: String
`
and I have the following resolvers
const resolvers = {
Query: {
author(_,args) {
return Author.find({where: args});
}
}
Person: {
citizenship() {
return "Example Citizenship";
}
}
}
I make the schema executable
const schema = makeExecutableSchema({
typeDefs,
resolvers,
inheritResolversFromInterfaces: true
});
and the optional argument inheritResolversFromInterfaces: true its supposed to allow me to inherit the citizenship resolver from Person to Author, based on the apollo graphql-tools documentation (link). That way when an Author is queried, the "Example Citizenship" string will appear.
However it does not, the query returns with
"author": {
"id": 1,
"name": "Peter",
"citizenship": null
}
Resolved, the feature inheritResolversFromInterfaces: true was added in version v2.24.0, need to have that version in order to use that feature.

Defining JSON/Object type in graphql-tag

I'm kinda new to Apollo gql, just wondering if anyone know if its possible to define Object class in graphql-tag?
export const CREATE_STYLE = gql`
mutation styleCreate(
$formID: String!
$fontFamily: Object //how do you define object/JSON object?
) {
styleCreate(
formID: $formID
fontFamily: $fontFamily
) {
styleID
}
}
`;
First, if the input type is an object I would recommend to define that on the server as a input type.
In my setup I'm using:
export const createUser = gql`
mutation createUser($user: UserCreate) {
create(input: $user) {
name
email
}
}
where "UserCreate" is an interface that looks like:
export interface UserCreate {
// The user name.
name: string,
// The user email address.
email: string,
};
You can manually create the interface, but I would suggest to use apollo codegen that gives you all the input types you need.

Resources