| name | graphql |
| description | Builds APIs with GraphQL including schemas, queries, mutations, resolvers, and client integration. Use when designing flexible APIs, fetching related data, or implementing real-time subscriptions. |
GraphQL
Query language for APIs with type system and efficient data fetching.
Quick Start
Install (Apollo Server):
npm install @apollo/server graphql
Install (Client):
npm install @apollo/client graphql
Schema Definition
Basic Types
# schema.graphql
type User {
id: ID!
email: String!
name: String
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
comments: [Comment!]!
createdAt: DateTime!
}
type Comment {
id: ID!
content: String!
author: User!
post: Post!
}
scalar DateTime
Input Types
input CreateUserInput {
email: String!
name: String
password: String!
}
input UpdateUserInput {
email: String
name: String
}
input CreatePostInput {
title: String!
content: String!
published: Boolean = false
}
Enums
enum Role {
USER
ADMIN
MODERATOR
}
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
type User {
id: ID!
role: Role!
}
Interfaces
interface Node {
id: ID!
}
type User implements Node {
id: ID!
name: String!
}
type Post implements Node {
id: ID!
title: String!
}
Unions
union SearchResult = User | Post | Comment
type Query {
search(term: String!): [SearchResult!]!
}
Queries
Root Query
type Query {
# Single item
user(id: ID!): User
post(id: ID!): Post
# Lists
users: [User!]!
posts(published: Boolean): [Post!]!
# Pagination
paginatedPosts(
first: Int
after: String
last: Int
before: String
): PostConnection!
# Search
search(term: String!): [SearchResult!]!
# Current user
me: User
}
Client Queries
# Simple query
query GetUser {
user(id: "1") {
id
name
email
}
}
# With variables
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts {
id
title
}
}
}
# Multiple queries
query Dashboard {
me {
id
name
}
recentPosts: posts(first: 5) {
id
title
}
}
# Fragments
fragment UserFields on User {
id
name
email
}
query GetUsers {
users {
...UserFields
posts {
id
title
}
}
}
Mutations
Root Mutation
type Mutation {
# Create
createUser(input: CreateUserInput!): User!
createPost(input: CreatePostInput!): Post!
# Update
updateUser(id: ID!, input: UpdateUserInput!): User!
updatePost(id: ID!, input: UpdatePostInput!): Post!
# Delete
deleteUser(id: ID!): Boolean!
deletePost(id: ID!): Boolean!
# Actions
login(email: String!, password: String!): AuthPayload!
publishPost(id: ID!): Post!
}
type AuthPayload {
token: String!
user: User!
}
Client Mutations
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
mutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
user {
id
name
}
}
}
Subscriptions
Schema
type Subscription {
postCreated: Post!
commentAdded(postId: ID!): Comment!
userStatusChanged: User!
}
Server Setup
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import express from 'express';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { PubSub } from 'graphql-subscriptions';
const pubsub = new PubSub();
const resolvers = {
Subscription: {
postCreated: {
subscribe: () => pubsub.asyncIterator(['POST_CREATED']),
},
},
Mutation: {
createPost: async (_, { input }, ctx) => {
const post = await ctx.prisma.post.create({ data: input });
pubsub.publish('POST_CREATED', { postCreated: post });
return post;
},
},
};
Apollo Server
Basic Setup
// server.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { readFileSync } from 'fs';
const typeDefs = readFileSync('./schema.graphql', 'utf-8');
const resolvers = {
Query: {
users: async (_, __, { prisma }) => {
return prisma.user.findMany();
},
user: async (_, { id }, { prisma }) => {
return prisma.user.findUnique({ where: { id } });
},
},
Mutation: {
createUser: async (_, { input }, { prisma }) => {
return prisma.user.create({ data: input });
},
},
User: {
posts: async (parent, _, { prisma }) => {
return prisma.post.findMany({
where: { authorId: parent.id },
});
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => ({
prisma,
user: await getUser(req),
}),
listen: { port: 4000 },
});
console.log(`Server ready at ${url}`);
Next.js API Route
// app/api/graphql/route.ts
import { ApolloServer } from '@apollo/server';
import { startServerAndCreateNextHandler } from '@as-integrations/next';
import { NextRequest } from 'next/server';
const typeDefs = `#graphql
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => 'Hello, World!',
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const handler = startServerAndCreateNextHandler<NextRequest>(server, {
context: async (req) => ({
req,
}),
});
export { handler as GET, handler as POST };
Apollo Client
Setup
// lib/apollo-client.ts
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
const httpLink = new HttpLink({
uri: '/api/graphql',
});
export const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
});
Provider
// app/providers.tsx
'use client';
import { ApolloProvider } from '@apollo/client';
import { client } from '@/lib/apollo-client';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ApolloProvider client={client}>
{children}
</ApolloProvider>
);
}
useQuery
import { gql, useQuery } from '@apollo/client';
const GET_USERS = gql`
query GetUsers {
users {
id
name
email
}
}
`;
function UserList() {
const { loading, error, data, refetch } = useQuery(GET_USERS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
useMutation
import { gql, useMutation } from '@apollo/client';
const CREATE_USER = gql`
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`;
function CreateUserForm() {
const [createUser, { loading, error }] = useMutation(CREATE_USER, {
refetchQueries: ['GetUsers'],
onCompleted: (data) => {
console.log('User created:', data.createUser);
},
});
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
await createUser({
variables: {
input: {
name: formData.get('name'),
email: formData.get('email'),
},
},
});
};
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" />
<input name="email" placeholder="Email" />
<button disabled={loading}>
{loading ? 'Creating...' : 'Create User'}
</button>
{error && <p>{error.message}</p>}
</form>
);
}
useSubscription
import { gql, useSubscription } from '@apollo/client';
const POST_CREATED = gql`
subscription OnPostCreated {
postCreated {
id
title
}
}
`;
function NewPosts() {
const { data, loading } = useSubscription(POST_CREATED);
if (loading) return null;
return <p>New post: {data?.postCreated.title}</p>;
}
Pagination
Cursor-Based
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type PostEdge {
cursor: String!
node: Post!
}
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type Query {
posts(first: Int, after: String): PostConnection!
}
// Resolver
posts: async (_, { first = 10, after }, { prisma }) => {
const posts = await prisma.post.findMany({
take: first + 1,
cursor: after ? { id: after } : undefined,
skip: after ? 1 : 0,
orderBy: { createdAt: 'desc' },
});
const hasNextPage = posts.length > first;
const edges = hasNextPage ? posts.slice(0, -1) : posts;
return {
edges: edges.map((post) => ({
cursor: post.id,
node: post,
})),
pageInfo: {
hasNextPage,
endCursor: edges[edges.length - 1]?.id,
},
};
},
Error Handling
Custom Errors
import { GraphQLError } from 'graphql';
const resolvers = {
Mutation: {
createPost: async (_, { input }, { prisma, user }) => {
if (!user) {
throw new GraphQLError('Not authenticated', {
extensions: {
code: 'UNAUTHENTICATED',
},
});
}
if (!user.canCreatePosts) {
throw new GraphQLError('Not authorized', {
extensions: {
code: 'FORBIDDEN',
},
});
}
return prisma.post.create({ data: input });
},
},
};
Best Practices
- Use fragments - Reuse field selections
- Implement pagination - For lists
- Use DataLoader - Prevent N+1 queries
- Validate inputs - Use custom scalars
- Document schema - Add descriptions
Common Mistakes
| Mistake | Fix |
|---|---|
| N+1 queries | Use DataLoader |
| Over-fetching | Query only needed fields |
| No error handling | Return proper errors |
| Missing types | Add Input types |
| No caching | Configure cache policies |
Reference Files
- references/schema.md - Schema design
- references/resolvers.md - Resolver patterns
- references/caching.md - Cache strategies