documentation/docs/data/packages/nestjs-query/index.md
Refine provides a data provider for APIs powered with Nest.js Query, a module for Nest.js that provides easier ways to build CRUD graphql APIs.
:::simple Good to know
graphql-request@5 to handle the requests.graphql-ws to handle the subscriptions.graphql-tag to write your queries and mutations.:::
npm i @refinedev/nestjs-query graphql-tag
If you're going to use LiveProvider you'll also need to install the graphql-ws package to handle graphql subscriptions.
npm i graphql-ws
We'll create a GraphQL Client with our API url and pass it to the dataProvider function to create a data provider.
import Refine from "@refinedev/core";
import dataProvider, { GraphQLClient } from "@refinedev/nestjs-query";
// highlight-next-line
const client = new GraphQLClient("https://api.nestjs-query.refine.dev/graphql");
const App = () => (
<Refine
// highlight-next-line
dataProvider={dataProvider(client)}
>
</Refine>
);
We suggest using GraphQL Code Generator to generate types for your queries and mutations. You can check out the GraphQL Code Generator Documentation to learn more about it.
It simplifies the process of writing queries and mutations and provides a better developer experience with auto-completions.
Make sure you install GraphQL Language Service for your code editor to get the best experience.
VSCode: https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql
npm i -D @graphql-codegen/cli@5 @graphql-codegen/typescript@4 @graphql-codegen/import-types-preset@3
Add a graphql.config.ts file to the root of your project.
import type { IGraphQLConfig } from "graphql-config";
const config: IGraphQLConfig = {
schema: "https://api.nestjs-query.refine.dev/graphql",
extensions: {
codegen: {
// Optional, you can use this to format your generated files.
hooks: {
afterOneFileWrite: ["eslint --fix", "prettier --write"],
},
generates: {
"src/graphql/schema.types.ts": {
plugins: ["typescript"],
config: {
skipTypename: true,
enumsAsTypes: true,
},
},
"src/graphql/types.ts": {
preset: "import-types",
documents: ["src/**/*.{ts,tsx}"],
plugins: ["typescript-operations"],
config: {
skipTypename: true,
enumsAsTypes: true,
preResolveTypes: false,
useTypeImports: true,
},
presetConfig: {
typesPath: "./schema.types",
},
},
},
},
},
};
export default config;
Add the following script to your package.json file.
{
"scripts": {
"codegen": "graphql-codegen --config ./graphql.config.ts"
}
}
Now you can run the following command to generate your types.
npm run codegen
It will generate the following files:
src/graphql/schema.types.ts: This file contains the types for your schema.src/graphql/types.ts: This file contains the types for your queries and mutations.graphql-tagYou can use graphql-tag to write your queries and mutations.
Refine hooks' meta object has optional gqlQuery and gqlMutation properties, you can use them to write your queries and mutations.
As a best-practice, we suggest writing your queries/mutations in a separate file, next to the component that uses it.
import gql from "graphql-tag";
export const POSTS_LIST_QUERY = gql`
query PostsList(
$paging: OffsetPaging!
$filter: BlogPostFilter
$sorting: [BlogPostSort!]!
) {
blogPosts(paging: $paging, filter: $filter, sorting: $sorting) {
nodes {
id
title
category {
title
}
content
createdAt
}
totalCount
}
}
`;
export const POST_EDIT_MUTATION = gql`
mutation PostEdit($input: UpdateOneBlogPostInput!) {
updateOneBlogPost(input: $input) {
id
title
status
category {
id
title
}
categoryId
content
}
}
`;
Once you write your query, you can generate types for it with GraphQL Code Generator.
npm run codegen
:::simple Good to know
@refinedev/nestjs-query also exports 2 utility types:
You can use these types to extract the type of selected fields of your queries/mutations.
See the Utility Types section for more information. :::
And then you can use it with hooks:
import { useList, useTable, useForm } from "@refinedev/core";
import { GetFields, GetFieldsFromList } from "@refinedev/nestjs-query";
import { PostsListQuery, PostEditMutation } from "src/graphql/types";
import { POSTS_LIST_QUERY, POST_EDIT_MUTATION } from "./queries";
const { result, query } = useList<GetFieldsFromList<PostsListQuery>>({
meta: { gqlQuery: POSTS_LIST_QUERY },
});
const { tableProps } = useTable<GetFieldsFromList<PostsListQuery>>({
meta: { gqlQuery: POSTS_LIST_QUERY },
});
const { formProps } = useForm<GetFields<PostEditMutation>>({
meta: { gqlMutation: POST_EDIT_MUTATION },
});
On initial render, useForm hook, will make getOne query to your API to get the initial values of the form. Notice, we didn't pass a separate gqlQuery field for this. It's because @refinedev/nestjs-query package will automatically detect the gqlMutation, extract selected fields from it and make a getOne query to your API.
If you want to customize the getOne query, you can also pass a gqlQuery field to the meta object.
const POST_EDIT_QUERY = gql`
query PostEdit($id: ID!) {
blogPost(id: $id) {
id
title
status
category {
id
title
}
categoryId
content
}
}
`;
const { formProps } = useForm<GetFields<PostEditMutation>>({
meta: {
gqlMutation: POST_EDIT_MUTATION,
// highlight-next-line
gqlQuery: POST_EDIT_QUERY,
},
});
@refinedev/nestjs-query exports 2 utility types, GetFields and GetFieldsFromList.
These types can be used to extract selection set from your queries mutations.
Let's say we have the following queries and mutations:
query PostShow($id: ID!) {
post(id: $id) {
id
}
}
mutation PostCreate($input: CreateOneBlogPostInput!) {
createOneBlogPost(input: $input) {
id
}
}
While you can use this type directly, it's not very useful, because you would need to extract fields manually, by passing your query/mutation name.
import { GetFields } from "@refinedev/nestjs-query";
import { PostShowQuery, PostCreateMutation } from "src/graphql/types";
PostShowQuery; // { blogPost: { id: string }; }
GetFields<PostShowQuery>; // { id: string; }
PostCreateMutation; // { createOneBlogPost: { id: string; } }
GetFields<PostCreateMutation>; // { id: string; }
Let's say you have the following query:
query PostsList(
$paging: OffsetPaging!
$filter: BlogPostFilter
$sorting: [BlogPostSort!]!
) {
blogPosts(paging: $paging, filter: $filter, sorting: $sorting) {
nodes {
id
}
totalCount
}
}
This query will generate the following type:
export type BlogPostsListQuery = {
blogPosts: Pick<Types.BlogPostConnection, "totalCount"> & {
nodes: Array<Pick<Types.BlogPost, "id">>;
};
};
As you can see, the selectionSet is under nodes, and it's not very useful, because data provider already returns normalized result.
GetFieldsFromList will convert it to:
import { GetFieldsFromList } from "@refinedev/nestjs-query";
type PostFields = GetFieldsFromList<BlogPostListQuery>;
PostFields; // { id: string }
@refinedev/nestjs-query also provides a liveProvider to enable realtime features of Refine. These features are powered by GraphQL subscriptions and uses graphql-ws to handle the connections.
import Refine from "@refinedev/core";
// highlight-next-line
import dataProvider, {
GraphQLClient,
liveProvider,
} from "@refinedev/nestjs-query";
// highlight-next-line
import { createClient } from "graphql-ws";
const client = new GraphQLClient("https://api.nestjs-query.refine.dev/graphql");
// highlight-next-line
const wsClient = createClient({
url: "wss://api.nestjs-query.refine.dev/graphql",
});
const App = () => (
<Refine
dataProvider={dataProvider(client)}
// highlight-next-line
liveProvider={liveProvider(wsClient)}
options={{ liveMode: "auto" }}
>
</Refine>
);
Currently @refinedev/nestjs-query supports OffsetPagingStrategy only. See their offset pagination example
You can configure resolvers in your app.module.ts file as follows:
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
// ...other config
// Enable for live provider.
installSubscriptionHandlers: true,
subscriptions: {
"graphql-ws": true,
},
}),
NestjsQueryGraphQLModule.forFeature({
imports: [NestjsQueryTypeOrmModule.forFeature([BlogPostEntity])],
resolvers: [
{
// Only OFFSET paging strategy is supported.
pagingStrategy: PagingStrategies.OFFSET,
// Needed for dataprovider to build pagination.
enableTotalCount: true,
DTOClass: BlogPostDTO,
// enable for live provider
enableSubscriptions: true,
},
],
}),
],
})
export class AppModule {}
If your API uses authentication, you can easily provide a custom fetcher for the requests and handle the authentication logic there. When creating a GraphQL Client, you can pass a fetch function to the client options. This function will be used to append the authentication headers to the requests.
import graphqlDataProvider, {
GraphQLClient,
liveProvider as graphqlLiveProvider,
} from "@refinedev/nestjs-query";
import { createClient } from "graphql-ws";
const client = new GraphQLClient(API_URL, {
fetch: (url: string, options: RequestInit) => {
return fetch(url, {
...options,
headers: {
...options.headers,
/**
* For demo purposes, we're using `localStorage` to access the token.
* You can use your own authentication logic here.
* In real world applications, you'll need to handle it in sync with your `authProvider`.
*/
// highlight-next-line
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});
},
});
const wsClient = createClient({
url: WS_URL,
connectionParams: () => ({
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
}),
});
export const dataProvider = graphqlDataProvider(client);
export const liveProvider = graphqlLiveProvider(wsClient);