GraphQL Complete Guide — Schema, Resolvers, Apollo
Advertisement
GraphQL is a query language for APIs. It lets clients request exactly the data they need, nothing more.
GraphQL Basics
# Define types
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
# Queries (reading data)
type Query {
user(id: ID!): User
users: [User!]!
post(id: ID!): Post
}
# Mutations (writing data)
type Mutation {
createUser(name: String!, email: String!): User!
deleteUser(id: ID!): Boolean!
createPost(title: String!, content: String!): Post!
}
Apollo Server Setup
npm install @apollo/server graphql
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
const typeDefs = `
type User {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
}
`;
const resolvers = {
Query: {
user: (parent, args) => {
return { id: args.id, name: "Alice", email: "alice@example.com" };
},
users: () => [
{ id: "1", name: "Alice", email: "alice@example.com" },
{ id: "2", name: "Bob", email: "bob@example.com" },
],
},
Mutation: {
createUser: (parent, args) => {
return { id: "3", ...args };
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
console.log(`GraphQL server running at ${url}`);
- GraphQL Basics
- Apollo Server Setup
- TypeScript Types
- Schema Design Patterns
- Resolvers
- Mutations
- Authentication
- Query Examples
- Directives
- Error Handling
- FAQ
TypeScript Types
import { TypedDocumentNode as DocumentNode } from "@graphql-typed-document-node/core";
interface User {
id: string;
name: string;
email: string;
}
interface Post {
id: string;
title: string;
content: string;
author: User;
}
const resolvers = {
Query: {
user: (parent: any, args: { id: string }): User | null => {
// Implementation
return null;
},
users: (): User[] => [],
},
User: {
posts: (parent: User): Post[] => [],
},
};
Schema Design Patterns
# Connection pattern for pagination
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
}
type UserEdge {
node: User!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}
# Updated Query
type Query {
users(first: Int!, after: String): UserConnection!
}
Resolvers
const resolvers = {
Query: {
// Nested resolvers for related data
user: async (parent, args, context) => {
return await context.db.users.findById(args.id);
},
},
User: {
// Resolver for nested field
posts: async (parent, args, context) => {
return await context.db.posts.findByUserId(parent.id);
},
},
};
Mutations
const typeDefs = `
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
input CreateUserInput {
name: String!
email: String!
}
input UpdateUserInput {
name: String
email: String
}
`;
const resolvers = {
Mutation: {
createUser: async (parent, args, context) => {
const user = await context.db.users.create(args.input);
return user;
},
updateUser: async (parent, args, context) => {
const user = await context.db.users.update(args.id, args.input);
return user;
},
deleteUser: async (parent, args, context) => {
await context.db.users.delete(args.id);
return true;
},
},
};
Authentication
import jwt from "jsonwebtoken";
interface AuthContext {
user?: { id: string; email: string };
}
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }): Promise<AuthContext> => {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return {};
try {
const decoded = jwt.verify(token, "secret");
return { user: decoded as any };
} catch {
return {};
}
},
});
// Protect resolvers
const resolvers = {
Query: {
me: (parent, args, context: AuthContext) => {
if (!context.user) throw new Error("Not authenticated");
return context.user;
},
},
};
Query Examples
# Get specific fields
query GetUser {
user(id: "1") {
id
name
email
}
}
# Get nested data
query GetUserWithPosts {
user(id: "1") {
id
name
posts {
id
title
}
}
}
# Multiple queries
query MultiQuery {
alice: user(id: "1") {
name
}
bob: user(id: "2") {
name
}
}
# Variables
query GetUser($userId: ID!) {
user(id: $userId) {
name
email
}
}
Directives
# Skip field conditionally
query GetUser($includeEmail: Boolean!) {
user(id: "1") {
name
email @include(if: $includeEmail)
}
}
# Custom directive
directive @auth(role: String!) on FIELD_DEFINITION
type Query {
adminData: String! @auth(role: "admin")
}
Error Handling
import { GraphQLError } from "graphql";
const resolvers = {
Query: {
user: async (parent, args) => {
const user = await getUser(args.id);
if (!user) {
throw new GraphQLError("User not found", {
extensions: { code: "NOT_FOUND" },
});
}
return user;
},
},
};
FAQ
Q: When should I use GraphQL vs REST? A: GraphQL for flexible queries. REST for simple, predictable APIs. GraphQL shines with complex data relationships.
Q: What's the performance impact of GraphQL? A: GraphQL adds overhead. REST is faster for simple cases. But GraphQL prevents over-fetching which saves bandwidth.
Q: How do I handle file uploads in GraphQL? A: Use GraphQL Upload scalar type and handle multipart form data.
GraphQL solves REST's shortcomings by letting clients specify exactly what data they need. For complex applications with many data relationships, GraphQL is powerful. For simple APIs, REST remains simpler.
Advertisement