withGQFnClient
You can set up your favorite client with @gqfn/nuxt.
Setup Schema
const endpoint = 'https://your-graphql-endpoint'
const schema = useGQFnSchema(endpoint)
Setup Client
We provide a simple client by default. But most cases you need to configure your client for advanced features.
Build-in Client
The build-in client use $fetch
to send request.
const endpoint = 'https://your-graphql-endpoint'
const schema = useGQFnSchema(endpoint)
const {
defineOperation,
defineAsyncQuery,
defineLazyAsyncQuery,
defineSubscription,
} = withGQFnClient(schema, {
fetchOptions: () => ({
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
})
// define your operations
graphql-request
graphql-request
is a minimal GraphQL client supporting Node and browsers for scripts or simple apps.
# ✨ Auto-detect
npx nypm install graphql-request graphql
npm install graphql-request graphql
yarn add graphql-request graphql
pnpm install graphql-request graphql
bun install graphql-request graphql
Then setup your client.
import { GraphQLClient } from 'graphql-request'
const endpoint = 'https://your-graphql-endpoint'
const schema = useGQFnSchema(endpoint)
const client = new GraphQLClient(endpoint, {
// we suggest to use build-in fetch if have a internal graphql server
// so that you can save network cost
fetch: (input, init) => $fetch.raw(
input.toString(),
{
...init as any,
responseType: 'stream',
},
),
// custom options
})
const {
defineOperation,
defineAsyncQuery,
defineLazyAsyncQuery,
defineSubscription,
} = withGQFnClient(
schema,
({ url, document, variables }, _context) => {
if (type === 'subscription') {
return // use other library for subscription
}
// @ts-expect-error ignore var not match error
return client.request({ document, variables })
},
)
urql
urql
is a highly customisable and versatile GraphQL client.
# ✨ Auto-detect
npx nypm install @urql/core
npm install @urql/core
yarn add @urql/core
pnpm install @urql/core
bun install @urql/core
Then setup your client.
import type { OperationContext } from '@urql/core'
import { cacheExchange, Client, fetchExchange } from '@urql/core'
const endpoint = 'https://your-graphql-endpoint'
const client = new Client({
url: endpoint,
exchanges: [cacheExchange, fetchExchange],
// we suggest to use build-in fetch if have a internal graphql server
// so that you can save network cost
fetch: (input, init) => $fetch.raw(
input.toString(),
{
...init as any,
responseType: 'stream',
},
),
})
const schema = useGQFnSchema(endpoint)
const {
defineOperation,
defineAsyncQuery,
defineLazyAsyncQuery,
} = withGQFnClient<Partial<OperationContext>>(
schema,
({ document, variables, type }, context) => {
if (type === 'query') {
return client.query(document, variables, context)
.toPromise()
}
// type === 'mutation'
return client.mutation(document, variables, context)
.toPromise()
},
)
Other Clients
You can use any other GraphQL client with @gqfn/nuxt
.
Setup Subscription Client
By default we provide two kinds of subscription client: websocket
by graphql-ws
and a simple event-source
client for sse
. For the vast majority of cases, they are enough. But if you need to customize them, you can use the subscription
option in withGQFnClient
Build-in Subscription Client
event-source
is the default subscription client. You can switch to graphql-ws
by setting handler: 'ws'
.
const schema = useGQFnSchema(endpoint)
const {
defineSubscription,
} = withGQFnClient(schema, {
subscription: { handler: 'ws' }
})
event-source
The only option of event-source
is withCredentials
.
graphql-ws
Configuration Options of graphql-ws
Other Subscription Clients
See Type Definitions to implement your own subscription client.
Define Operation
withGQFnClient
provides some methods for you to define operations composables.
defineOperation
export const useUser = defineOperation(
gqfn => gqfn('query QueryUser', {
id: 'ID',
}, [{
user: $ => $({ id: $.id }, ['id', 'name']),
}]),
)
const { user } = await useUser({ id: '1' })
defineAsyncQuery
Create a composable around useAsyncData
and defineOperation
. It automatically generates a key base on the query and first time variables.
TIP
It only works on query
and will throw an error if received a mutation
or subscription
operation.
export const useAsyncUser = defineAsyncQuery(
gqfn => gqfn('query QueryUser', {
id: 'ID',
}, [{
user: $ => $({ id: $.id }, ['id', 'name']),
}]),
)
const { data, pending, error, refresh } = await useAsyncUser({ id: '1' })
You can set the watch
option to refresh when the variables changed. You need to provide ref
, computed
or a getter (() => T
) as the variables, so that we can always use the latest value to refresh.
const id = ref('1')
const { data, pending, error, refresh } = await useAsyncUser(
() => { id: id.value },
{ watch: [id] },
)
defineLazyAsyncQuery
Same to defineAsyncQuery
but with lazy:true
by default.
export const useLazyAsyncUser = defineLazyAsyncQuery(
gqfn => gqfn('query QueryUser', {
id: 'ID',
}, [{
user: $ => $({ id: $.id }, ['id', 'name']),
}]),
)
const { data, pending, error, refresh } = await useLazyAsyncUser({ id: '1' })
defineSubscription
Create a composable for your subscription. This composable only works in client-side and will do nothing and throw a warning if used in server-side.
const useCountdown = defineSubscription(
gqfn => gqfn('subscription', [{
countdown: $ => $({ from: 3 }, true),
}]),
)
const num = ref(0)
let cancel: () => void | undefined
async function reset() {
if (cancel) {
cancel()
}
const countdown = await useCountdown()
cancel = countdown.unsubscribe
num.value = countdown.data.value?.countdown ?? 0
const unwatch = watch(countdown.data, (val) => {
if (val) {
num.value = val.countdown
}
if (countdown.state.value === 'closed') {
unwatch()
}
})
}
Type Definitions
export function withGQFnClient<
Context = DefaultHandlerOptions,
SubscriptionContext = DefaultSubscriptionHandlerOptions,
Endpoint extends Endpoints = string,
>(
schema: UseGQFnSchema<Endpoint>,
options?: WithGQFnClientOptions<Context, SubscriptionContext>,
): WithGQFnClient<Context, Endpoint>
More
export interface WithGQFnClientOptions<
Context,
SubscriptionContext,
> {
handler?: RequestHandler<Context> | DefaultHandlerOptions
subscription?:
| ({
/**
* Override the default subscription handler.
* By default it use EventSource
* - `SubscriptionHandler`: Custom subscription handler.
* - `ws`: Use websocket handler from package `graphql-ws`.
* - `sse`: Use default sse handler.
*/
handler?: 'sse'
} & SSEOptions)
| ({
/**
* Override the default subscription handler.
* By default it use EventSource
* - `SubscriptionHandler`: Custom subscription handler.
* - `ws`: Use websocket handler from package `graphql-ws`.
* - `sse`: Use default sse handler.
*/
handler: 'ws'
} & WSOptions)
| {
/**
* Override the default subscription handler.
* By default it use EventSource
* - `SubscriptionHandler`: Custom subscription handler.
* - `ws`: Use websocket handler from package `graphql-ws`.
* - `sse`: Use default sse handler.
*/
handler: SubscriptionHandler<SubscriptionContext>
}
}
export type RequestHandler<Context> = <
TData,
TVars extends Record<string, unknown>,
> (
query: {
document: TypedQueryDocumentNode<TData, TVars>
variables: NoInfer<TVars>
type: 'query' | 'mutation'
url: string
},
context?: Context
) => Promise<TData>
export type SubscriptionHandler<Context> = <
TData,
TVars extends Record<string, unknown>,
> (
func: {
update: (data: TData, isFinal?: boolean) => void
close: (error?: any) => void
onUnsubscribe: (fn: () => void) => void
},
query: {
document: TypedQueryDocumentNode<TData, TVars>
variables: NoInfer<TVars>
type: 'subscription'
url: string
},
context?: Context
) => void
export interface WithGQFnClient<
Context,
Endpoint extends Endpoints = string,
> {
defineOperation: DefineOperation<Context, Endpoint>
defineAsyncQuery: DefineAsyncQuery<Context, Endpoint>
defineLazyAsyncQuery: DefineAsyncQuery<Context, Endpoint>
defineSubscription: DefineSubscription<Context, Endpoint>
}
export interface DefineOperation<
Context,
Endpoint extends Endpoints = string,
> {
<TData, TVars extends Record<string, unknown>>(
def: (
| ((
gqfn: UseGQFnSchema<Endpoint>['gqfn'],
$enum: UseGQFnSchema<Endpoint>['$enum'],
) => TypedQueryDocumentNode<TData, TVars>)
| TypedQueryDocumentNode<TData, TVars>
),
context?: Context,
): DefineOperationReturn<Promise<TData>, TVars, Context>
}
export interface DefineAsyncQuery<
Context,
Endpoint extends Endpoints = string,
> {
<
TData,
TVars extends Record<string, unknown>,
DataT = TData | undefined,
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
DefaultT = null,
> (
def: (
| ((
gqfn: UseGQFnSchema<Endpoint>['gqfn'],
$enum: UseGQFnSchema<Endpoint>['$enum'],
) => TypedQueryDocumentNode<TData, TVars>)
| TypedQueryDocumentNode<TData, TVars>
),
context?: Context,
): DefineAsyncQueryReturn<
AsyncData<PickFrom<DataT, PickKeys> | DefaultT, Error | null>,
TVars,
AsyncDataOptions<TData | undefined, DataT, PickKeys, DefaultT> & { context?: Context }
>
<
TData,
TVars extends Record<string, unknown>,
DataT = TData | undefined,
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
DefaultT = DataT,
> (
def: (
| ((
gqfn: UseGQFnSchema<Endpoint>['gqfn'],
$enum: UseGQFnSchema<Endpoint>['$enum'],
) => TypedQueryDocumentNode<TData, TVars>)
| TypedQueryDocumentNode<TData, TVars>
),
context?: Context,
): DefineAsyncQueryReturn<
AsyncData<PickFrom<DataT, PickKeys> | DefaultT, Error | null>,
TVars,
AsyncDataOptions<TData | undefined, DataT, PickKeys, DefaultT> & { context?: Context }
>
}
export interface DefineSubscription<
Context,
Endpoint extends Endpoints = string,
> {
<TData, TVars extends Record<string, unknown>>(
def: (
| ((
gqfn: UseGQFnSchema<Endpoint>['gqfn'],
$enum: UseGQFnSchema<Endpoint>['$enum'],
) => TypedQueryDocumentNode<TData, TVars>)
| TypedQueryDocumentNode<TData, TVars>
),
context?: Context,
): DefineSubscriptionReturn<TData, TVars, Context>
}
export type DefineOperationReturn<Ret, TVars, Context> =
Record<string, never> extends TVars
? (variables?: TVars, context?: Context) => Ret
: (variables: TVars, context?: Context) => Ret
export type DefineAsyncQueryReturn<Ret, TVars, Options> =
Record<string, never> extends TVars
? DefineAsyncQueryReturnFnE<Ret, TVars, Options>
: DefineAsyncQueryReturnFn<Ret, TVars, Options>
export interface DefineAsyncQueryReturnFn<Ret, TVars, Options> {
(
variables: TVars,
options?: Omit<Options, 'watch'> // Force to use getter if watched.
): Ret
(
variables: Ref<TVars> | (() => TVars),
options?: Options
): Ret
}
export interface DefineAsyncQueryReturnFnE<Ret, TVars, Options> {
(
variables?: TVars,
options?: Omit<Options, 'watch'> // Force to use getter if watched.
): Ret
(
variables?: Ref<TVars> | (() => TVars),
options?: Options
): Ret
}
export type DefineSubscriptionReturn<Ret, TVars, Context> =
Record<string, never> extends TVars
? (variables?: TVars, options?: Context) => Promise<SubscriptionReturn<Ret>>
: (variables: TVars, options?: Context) => Promise<SubscriptionReturn<Ret>>
export interface SubscriptionReturn<TData> {
state: ComputedRef<'pending' | 'connected' | 'closed'>
data: ComputedRef<TData | undefined>
error: ComputedRef<Error | null>
unsubscribe: () => void
/**
* Close the current subscription and reconnect.
*/
restart: () => Promise<void>
/**
* Keep the current subscription alive and seamless switch to a new one.
* This is useful when you have a connection time limit.
*/
refresh: () => Promise<void>
}