KeystoneJS Frontend Integration via GraphQL API
KeystoneJS automatically generates full-featured GraphQL API from Lists description. For each List, queries (query/queries/count), mutations (create/update/delete), and filter types are created. Integration task — organizing client-side properly.
What Gets Generated Automatically
For List Post, KeystoneJS creates:
-
post(where: PostWhereUniqueInput!): Post -
posts(where: PostWhereInput, orderBy: [...], take: Int, skip: Int): [Post!] -
postsCount(where: PostWhereInput): Int -
createPost(data: PostCreateInput!): Post -
createPosts(data: [PostCreateInput!]!): [Post] -
updatePost(where: PostWhereUniqueInput!, data: PostUpdateInput!): Post -
deletePost(where: PostWhereUniqueInput!): Post
Apollo Client Setup
// lib/apollo.ts
import { ApolloClient, InMemoryCache, createHttpLink, from } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
const httpLink = createHttpLink({
uri: process.env.NEXT_PUBLIC_KEYSTONE_URL + '/api/graphql',
credentials: 'include', // for cookie sessions
});
const authLink = setContext((_, { headers }) => ({
headers: {
...headers,
// If using JWT instead of cookie
authorization: getToken() ? `Bearer ${getToken()}` : '',
},
}));
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message, extensions }) => {
if (extensions?.code === 'UNAUTHENTICATED') {
window.location.href = '/login';
}
});
}
});
export const apolloClient = new ApolloClient({
link: from([errorLink, authLink, httpLink]),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
posts: {
keyArgs: ['where', 'orderBy'],
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
},
},
},
}),
});
Typed Queries with Codegen
npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo
# codegen.yml
schema: http://localhost:3000/api/graphql
documents: src/**/*.graphql
generates:
src/generated/graphql.ts:
plugins:
- typescript
- typescript-operations
- typescript-react-apollo
# src/queries/posts.graphql
query GetPosts($where: PostWhereInput, $take: Int, $skip: Int) {
posts(where: $where, take: $take, skip: $skip, orderBy: [{ publishedAt: desc }]) {
id
title
slug
publishedAt
status
author {
id
name
}
tags {
id
name
}
}
postsCount(where: $where)
}
mutation CreatePost($data: PostCreateInput!) {
createPost(data: $data) {
id
slug
}
}
Usage in Next.js (Server Components)
// app/blog/page.tsx
import { getClient } from '@/lib/apollo-server';
import { GetPostsDocument } from '@/generated/graphql';
export default async function BlogPage({ searchParams }) {
const page = Number(searchParams.page) || 1;
const { data } = await getClient().query({
query: GetPostsDocument,
variables: {
where: { status: { equals: 'published' } },
take: 10,
skip: (page - 1) * 10,
},
});
return <PostGrid posts={data.posts} total={data.postsCount} page={page} />;
}
Mutations in Client Components
'use client';
import { useMutation } from '@apollo/client';
import { CreatePostDocument } from '@/generated/graphql';
export function NewPostForm() {
const [createPost, { loading, error }] = useMutation(CreatePostDocument, {
update(cache, { data }) {
// Invalidate posts list cache
cache.evict({ fieldName: 'posts' });
},
});
const handleSubmit = async (formData: PostFormData) => {
const { data } = await createPost({
variables: {
data: {
title: formData.title,
slug: formData.slug,
content: { document: formData.content },
author: { connect: { id: currentUserId } },
status: 'draft',
},
},
});
router.push(`/admin/posts/${data?.createPost?.id}`);
};
}
Authentication via GraphQL
KeystoneJS creates auth mutations automatically:
const LOGIN = gql`
mutation Login($email: String!, $password: String!) {
authenticateUserWithPassword(email: $email, password: $password) {
... on UserAuthenticationWithPasswordSuccess {
sessionToken
item { id name email role }
}
... on UserAuthenticationWithPasswordFailure {
message
}
}
}
`;
const WHOAMI = gql`
query WhoAmI {
authenticatedItem {
... on User { id name email role }
}
}
`;
Pagination and Infinite Scroll
const { data, fetchMore, loading } = useQuery(GetPostsDocument, {
variables: { take: 10, skip: 0 },
});
const loadMore = () => {
fetchMore({
variables: { skip: data.posts.length },
updateQuery: (prev, { fetchMoreResult }) => ({
postsCount: fetchMoreResult.postsCount,
posts: [...prev.posts, ...fetchMoreResult.posts],
}),
});
};
Typical Next.js + KeystoneJS GraphQL integration with codegen, authentication, and basic CRUD — 3–5 days.







