Build type-safe GraphQL APIs with Apollo Server and Next.js. Learn schema design, resolvers, dataloaders, subscriptions, authentication, and client integration with Apollo Client.
# GraphQL with Apollo Server Complete Guide
Build powerful GraphQL APIs with Apollo Server integrated into Next.js, featuring type safety, efficient data loading, and real-time subscriptions.
## Apollo Server Setup
### Next.js App Router Integration
```typescript
// app/api/graphql/route.ts
import { ApolloServer } from "@apollo/server";
import { startServerAndCreateNextHandler } from "@as-integrations/next";
import { prisma } from "@/lib/prisma";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { typeDefs } from "@/graphql/schema";
import { resolvers } from "@/graphql/resolvers";
import { createLoaders } from "@/graphql/loaders";
export interface Context {
prisma: typeof prisma;
session: Awaited<ReturnType<typeof getServerSession>>;
loaders: ReturnType<typeof createLoaders>;
}
const server = new ApolloServer<Context>({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== "production",
});
const handler = startServerAndCreateNextHandler(server, {
context: async (req) => {
const session = await getServerSession(authOptions);
return {
prisma,
session,
loaders: createLoaders(prisma),
};
},
});
export { handler as GET, handler as POST };
```
### Schema Definition
```typescript
// graphql/schema.ts
export const typeDefs = `#graphql
scalar DateTime
type User {
id: ID!
email: String!
name: String
avatar: String
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
comments: [Comment!]!
createdAt: DateTime!
updatedAt: DateTime!
}
type Comment {
id: ID!
content: String!
author: User!
post: Post!
createdAt: DateTime!
}
type Query {
me: User
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
post(id: ID!): Post
posts(
limit: Int
offset: Int
published: Boolean
search: String
): PostConnection!
}
type PostConnection {
nodes: [Post!]!
totalCount: Int!
hasNextPage: Boolean!
}
input CreatePostInput {
title: String!
content: String!
published: Boolean
}
input UpdatePostInput {
title: String
content: String
published: Boolean
}
type Mutation {
createPost(input: CreatePostInput!): Post!
updatePost(id: ID!, input: UpdatePostInput!): Post!
deletePost(id: ID!): Boolean!
createComment(postId: ID!, content: String!): Comment!
}
type Subscription {
postCreated: Post!
commentAdded(postId: ID!): Comment!
}
`;
```
### Resolvers
```typescript
// graphql/resolvers.ts
import { GraphQLError } from "graphql";
import type { Context } from "@/app/api/graphql/route";
export const resolvers = {
Query: {
me: async (_: unknown, __: unknown, { session, prisma }: Context) => {
if (!session?.user?.email) return null;
return prisma.user.findUnique({
where: { email: session.user.email },
});
},
user: async (_: unknown, { id }: { id: string }, { prisma, loaders }: Context) => {
return loaders.userLoader.load(id);
},
posts: async (
_: unknown,
{ limit = 10, offset = 0, published, search }: {
limit?: number;
offset?: number;
published?: boolean;
search?: string;
},
{ prisma }: Context
) => {
const where = {
...(published !== undefined && { published }),
...(search && {
OR: [
{ title: { contains: search, mode: "insensitive" as const } },
{ content: { contains: search, mode: "insensitive" as const } },
],
}),
};
const [nodes, totalCount] = await Promise.all([
prisma.post.findMany({
where,
take: limit,
skip: offset,
orderBy: { createdAt: "desc" },
}),
prisma.post.count({ where }),
]);
return {
nodes,
totalCount,
hasNextPage: offset + nodes.length < totalCount,
};
},
},
Mutation: {
createPost: async (
_: unknown,
{ input }: { input: { title: string; content: string; published?: boolean } },
{ session, prisma }: Context
) => {
if (!session?.user?.id) {
throw new GraphQLError("Not authenticated", {
extensions: { code: "UNAUTHENTICATED" },
});
}
return prisma.post.create({
data: {
...input,
authorId: session.user.id,
},
});
},
updatePost: async (
_: unknown,
{ id, input }: { id: string; input: Record<string, unknown> },
{ session, prisma }: Context
) => {
const post = await prisma.post.findUnique({ where: { id } });
if (!post) {
throw new GraphQLError("Post not found", {
extensions: { code: "NOT_FOUND" },
});
}
if (post.authorId !== session?.user?.id) {
throw new GraphQLError("Not authorized", {
extensions: { code: "FORBIDDEN" },
});
}
return prisma.post.update({
where: { id },
data: input,
});
},
},
Post: {
author: async (post: { authorId: string }, _: unknown, { loaders }: Context) => {
return loaders.userLoader.load(post.authorId);
},
comments: async (post: { id: string }, _: unknown, { loaders }: Context) => {
return loaders.commentsByPostLoader.load(post.id);
},
},
User: {
posts: async (user: { id: string }, _: unknown, { prisma }: Context) => {
return prisma.post.findMany({
where: { authorId: user.id },
orderBy: { createdAt: "desc" },
});
},
},
};
```
### DataLoaders for N+1 Prevention
```typescript
// graphql/loaders.ts
import DataLoader from "dataloader";
import type { PrismaClient } from "@prisma/client";
export function createLoaders(prisma: PrismaClient) {
return {
userLoader: new DataLoader<string, User | null>(async (ids) => {
const users = await prisma.user.findMany({
where: { id: { in: [...ids] } },
});
const userMap = new Map(users.map((u) => [u.id, u]));
return ids.map((id) => userMap.get(id) || null);
}),
commentsByPostLoader: new DataLoader<string, Comment[]>(async (postIds) => {
const comments = await prisma.comment.findMany({
where: { postId: { in: [...postIds] } },
orderBy: { createdAt: "asc" },
});
const grouped = new Map<string, Comment[]>();
comments.forEach((c) => {
const list = grouped.get(c.postId) || [];
list.push(c);
grouped.set(c.postId, list);
});
return postIds.map((id) => grouped.get(id) || []);
}),
};
}
```
## Apollo Client Setup
```typescript
// lib/apollo-client.ts
"use client";
import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
const client = new ApolloClient({
uri: "/api/graphql",
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
posts: {
keyArgs: ["search", "published"],
merge(existing, incoming, { args }) {
if (!args?.offset) return incoming;
return {
...incoming,
nodes: [...(existing?.nodes || []), ...incoming.nodes],
};
},
},
},
},
},
}),
});
export function GraphQLProvider({ children }: { children: React.ReactNode }) {
return <ApolloProvider client={client}>{children}</ApolloProvider>;
}
```
This GraphQL guide covers schema design, resolvers, DataLoader optimization, and Apollo Client integration.This graphql prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their graphql implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For graphql projects, consider mentioning your framework version, coding style, and any specific libraries you're using.