Fetching Data
Outline
<> Step Code
Click on the link below to view the code for this step on GitHub.
GitHub - szymonuryga/optimizely-masterclass-step-by-step at 2-fetching-data
Contribute to szymonuryga/optimizely-masterclass-step-by-step development by creating an account on GitHub.
 
      This guide explains how to fetch data from Optimizely Graph. A very useful thing when working with Optimizely Graph is codegen for GraphQL Schema - @graphql-codegen/cli. With this tool, all we need to do is create the appropriate snippets or queries for our Optimizely Graph, and the CLI will generate a fully-typed SDK for us.
Setting up GraphQL Codegen
1. First, install the following dependencies:
npm i -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-generic-sdk @graphql-codegen/typescript-operations2. Create a config file named codegen.yaml in the root of your project:
schema: ${OPTIMIZELY_API_URL}?auth=${OPTIMIZELY_SINGLE_KEY}
documents: './lib/optimizely/queries/**/*.graphql'
generates:
  './lib/optimizely/types/generated.ts':
    plugins:
      - 'typescript'
      - 'typescript-operations'
      - 'typescript-generic-sdk'
    config:
      rawRequest: true
      avoidOptionals: true3. Add a gen-types script to your package.json to run Codegen:
{
  "scripts": {
    "gen-types": "graphql-codegen -r dotenv/config --config ./codegen.yaml"  
   }
}This configuration will ensure that our SDK and types will be generated in the ./lib/optimizely/types/generated.ts file based on the Optimizely Graph schema and fragments and queries defined in the ./lib/optimizely/queries/* directory.
Creating a Custom Fetcher
Let's create a custom fetcher that we will use to query data from the Optimizely Graph. Create a new file, for example, lib/optimizely/fetch.ts:
import { DocumentNode } from 'graphql'
import { print } from 'graphql/language/printer'
import { getSdk } from './types/generated'
import { isVercelError } from '/type-guards'
interface OptimizelyFetchOptions {
  headers?: Record<string, string>
  cache?: RequestCache
  preview?: boolean
  cacheTag?: string
}
interface OptimizelyFetch<Variables> extends OptimizelyFetchOptions {
  query: string
  variables?: Variables
}
interface GraphqlResponse<Response> {
  errors: unknown[]
  data: Response
}
const optimizelyFetch = async <Response, Variables = object>({
  query,
  variables,
  headers,
  cache = 'force-cache',
  preview,
  cacheTag
}: OptimizelyFetch<Variables>): Promise<
  GraphqlResponse<Response> & { headers: Headers }
> => {
  const configHeaders = headers ?? {}
  if (preview) {
    configHeaders.Authorization = `Basic ${process.env.OPTIMIZELY_PREVIEW_SECRET}`
    cache = 'no-store'
  }
  const cacheTags = ['optimizely-content']
  if (cacheTag) {
    cacheTags.push(cacheTag)
  }
  try {
    const endpoint = `${process.env.OPTIMIZELY_API_URL}?auth=${process.env.OPTIMIZELY_SINGLE_KEY}`
    const response = await fetch(endpoint, {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        ...configHeaders,
      },
      body: JSON.stringify({
        ...(query && { query }),
        ...(variables && { variables }),
      }),
      cache,
      next: { tags: cacheTags },
    })
    const result = await response.json()
    return {
      ...result,
      headers: response.headers,
    }
  } catch (e) {
    if (isVercelError(e)) {
      throw {
        status: e.status || 500,
        message: e.message,
        query,
      }
    }
    throw {
      error: e,
      query,
    }
  }
}    Note: The process.env.OPTIMIZELY_PREVIEW_SECRET is a generated base64 string based on your AppKey and AppSecret credentials.
Creating a Wrapper Function
Let's create a wrapper function around our optimizelyFetch that maps the values properly to the getSdk function, which is an auto-generated SDK. Add this to the same file or create a new one:
import { DocumentNode } from 'graphql'
import { print } from 'graphql/language/printer'
import { getSdk } from '/types/generated'
async function requester<R, V>(
  doc: DocumentNode,
  vars?: V,
  options?: OptimizelyFetchOptions
) {
  const request = await optimizelyFetch<R>({
    query: print(doc),
    variables: vars ?? {},
    ...options,
  })
  return {
    data: request.data,
    _headers: request.headers,
  }
}
export const optimizely = getSdk(requester)   
Type Guards
export interface VercelErrorLike {
  status: number
  message: Error
  cause?: Error
}
export const isObject = (
  object: unknown
): object is Record<string, unknown> => {
  return typeof object === 'object' && object !== null && !Array.isArray(object)
}
export const isVercelError = (error: unknown): error is VercelErrorLike => {
  if (!isObject(error)) return false
  if (error instanceof Error) return true
  return findError(error)
}
function findError<T extends object>(error: T): boolean {
  if (Object.prototype.toString.call(error) === '[object Error]') {
    return true
  }
  const prototype = Object.getPrototypeOf(error) as T | null
  return prototype === null ? false : findError(prototype)
}
Creating Your First Query
In the ./lib/optimizely/queries directory (as defined in the documents field in the codegen configuration file), create your first query:
# GetContentByGuid.graphql
query GetContentByGuid($guid: String) {
  _Content(where: { _metadata: { key: { eq: $guid } } }) {
    items {
      _metadata {
        displayName
        version
        key
        url {
          base
          internal
          hierarchical
          default
          type
        }
      }
    }
  }
}
After properly configuring codegen and running the command npm run gen-types that generates the SDK, you'll get a fully-typed client that you can use to fetch data from Optimizely Graph.
Usage of SDK
// page component
const { data } = await optimizely.GetContentByGuid( { guid: "" } );
Remember to set up your environment variables (OPTIMIZELY_API_URL, OPTIMIZELY_SINGLE_KEY, and OPTIMIZELY_PREVIEW_SECRET) in your .env file or your deployment platform.
