Foundations — Topic Progress
0 / 4 read
The Basics
What is GraphQL? Critical
▼
Definition: GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. It provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more.
Developed by Facebook in 2012 and open-sourced in 2015, GraphQL addresses the limitations of REST APIs by allowing precise data fetching.
- Ask for what you need: Send a GraphQL query to your API and get exactly what you interpret, nothing more and nothing less.
- Get many resources in a single request: GraphQL queries access not just the properties of one resource but also smoothly follow references between them.
- Describe what’s possible with a type system: GraphQL APIs are organized in terms of types and fields, not endpoints.
The Anatomy of a Request
# Client Query
query GetUser {
user(id: "1") {
name
email
friends {
name
}
}
}
# Server Response (JSON)
{
"data": {
"user": {
"name": "Satendra",
"email": "sssat@example.com",
"friends": [
{ "name": "DeepMind" }
]
}
}
}
Key Takeaways
- GraphQL is not a database; it is a communication protocol.
- It works over HTTP, usually on a single
/graphqlendpoint. - Response structure mirrors the request structure.
Why "Graph"? In GraphQL, your data is viewed as a collection of nodes (objects like Users, Posts, Comments) connected by edges (relationships like "User wrote Post"). This guide will show you how to traverse this graph!
GraphQL vs REST Important
▼
Comparison: While REST is architectural-style based on resources (endpoints), GraphQL is a query-based system that uses a single endpoint to fetch graph-like data.
REST Limitations:
- Over-fetching: Getting more data than you need (e.g., getting full user object just for a name).
- Under-fetching: One endpoint isn't enough; you need multiple requests (e.g.,
/user/1then/user/1/posts). - Versioning: REST often requires
v1/,v2/endpoints. GraphQL evolves without versioning by adding new fields.
- No over/under fetching: Client defines the response.
- Strongly Typed: Schema acts as a contract between frontend and backend.
- Introspection: Clients can query the server for its schema (enables tools like GraphiQL).
GraphQL is great for complex systems with many relations, but REST might still be simpler for basic CRUD or binary file handling.
Schema & Type System Important
▼
Schema: The core of any GraphQL server implementation. It describes the functionality available to the clients.
Every GraphQL service defines a set of types which completely describe the set of possible data you can query on that service. When queries come in, they are validated and executed against that schema.
Operations Overview Important
▼
- Query: Read-only fetch.
- Mutation: Write followed by a fetch.
- Subscription: Long-lived request that fetches data in response to source events.
Backend Setup — Topic Progress
0 / 5 read
Getting Started
1. Installation & Initialization Critical
▼
Setup: To build a GraphQL server with Node.js and Express, you need to initialize a project and install the Apollo Server core along with Express integration.
Terminal Commands
# Initialize project
mkdir graphql-server && cd graphql-server
npm init -y
# Install dependencies
npm install @apollo/server express graphql cors body-parser
Package Breakdown
@apollo/server: The modern Apollo Server (v4+).express: The web framework to host our endpoint.graphql: The core GraphQL implementation for JS.cors: Middleware for cross-origin requests.
2. Creating the Schema (TypeDefs) Important
▼
The Schema is the blueprint of your data. It defines the types and fields available for clients to query.
// schema.js
export const typeDefs = `#graphql
type Book {
title: String
author: String
}
type Query {
books: [Book]
}
`;
Deep Dive: Understanding the Syntax
- #graphql: This is a comment tag that tells your editor (VS Code, etc.) that the string following it is GraphQL code. This enables Syntax Highlighting and Autocomplete inside a JavaScript file.
- type Book: This defines a new "Object Type". Think of it like a Class or an Interface in other languages. It describes what a "Book" looks like (it has a title and an author).
- String: This is a built-in "Scalar" type. Other basics include
Int,Float,Boolean, andID. - type Query: This is a Special Type. It is the mandatory entry point for all "read" operations. Every field inside
type Querybecomes an endpoint that a client can request. - books: [Book]: This says the
booksquery will return an Array (List) of Book objects.
Think of the Schema as a "Contract". If it's not in the schema, the client can't ask for it!
3. Writing Resolvers Important
▼
Resolvers: Functions that fulfill the queries defined in the schema. They fetch the actual data from databases, REST APIs, or static files.
// resolvers.js
const books = [
{ title: 'The Awakening', author: 'Kate Chopin' },
{ title: 'City of Glass', author: 'Paul Auster' },
];
export const resolvers = {
Query: {
books: () => books,
},
};
How it works: The Data Bridge
- Mapping: The structure of the
resolversobject must mirror the structure of yourtypeDefs. Since we have abooksfield insidetype Query, we must have abooksfunction insideQueryin our resolvers. - The Function: When a client queries
books, GraphQL executes this function. It can return a hardcoded array (like our example), or perform adb.find()or anfetch()request. - Parent/Args: Although empty here, these functions receive arguments (like
id) to filter data.
4. Initializing the Server Critical
▼
Now, we combine everything and start the Express server with Apollo Middleware.
// index.js
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import express from 'express';
import http from 'http';
import cors from 'cors';
import { typeDefs } from './schema.js';
import { resolvers } from './resolvers.js';
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
app.use(
'/graphql',
cors(),
express.json(),
expressMiddleware(server),
);
await new Promise((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log(`🚀 Server ready at http://localhost:4000/graphql`);
5. Testing with Sandbox Important
▼
By default, Apollo Server includes **Apollo Sandbox**, a powerful UI for testing your queries. Just visit
http://localhost:4000/graphql in your browser.You can browse your schema, see documentation, and use explorer to click-and-build queries without typing.
Core Concepts — Topic Progress
0 / 8 read
The Building Blocks
Object Types & Fields Critical
▼
Object Types: Represent a kind of object you can fetch from your service, and what fields it has.
type Character {
id: ID!
name: String!
appearsIn: [Episode!]!
}
Explanation
String,ID: Built-in Scalar types.!: Non-nullable. Server promises to always return a value.[Type]: A list (array) of that type.
Queries & Arguments Important
▼
Every field on a GraphQL object type can have zero or more arguments.
Schema
type Query {
user(id: ID!): User
}
Resolver
user: (parent, args, context, info) => {
return users.find(u => u.id === args.id);
}
Resolver Arguments
parent: The result of the previous resolver.args: The arguments passed in the query.context: Shared object (Auth, Database connections).info: Execution state and field details.
Mutations (Creating/Updating Data) Critical
▼
Mutations: Used to modify server-side data. While queries are for reads, mutations are for writes, updates, and deletes.
Schema
type Mutation {
createUser(name: String!, email: String!): User
}
Resolver
Mutation: {
createUser: (_, { name, email }) => {
const newUser = { id: Date.now().toString(), name, email };
users.push(newUser);
return newUser;
}
}
Always return the modified object from a mutation so the client can update its cache instantly.
Input Types Important
▼
When you have many arguments for a mutation, use an
input type for better organization.input UserInput {
name: String!
email: String!
age: Int
}
type Mutation {
createUser(input: UserInput!): User
}
Subscriptions (Real-time) Advanced
▼
Subscriptions: Allow you to push data from the server to the clients that choose to listen to real-time messages from the server.
Usually implemented over WebSockets rather than HTTP. Used for chat apps, stock tickers, or live notifications.
type Subscription {
postAdded: Post
}
Variables Important
▼
Avoid hardcoding values in queries. Use variables for dynamic values.
query GetUser($id: ID!) {
user(id: $id) {
name
}
}
Fragments Important
▼
Fragments let you build sets of fields, and then include them in queries where you need them.
fragment UserDetails on User {
id
name
email
}
query GetUser {
user(id: "1") {
...UserDetails
}
}
Directives (@include/@skip) Advanced
▼
Directives provide a way to dynamically change the structure and shape of our queries using variables.
query GetUser($showEmail: Boolean!) {
user(id: "1") {
name
email @include(if: $showEmail)
}
}
Advanced Schema — Topic Progress
0 / 5 read
Polymorphism & Extensions
Interfaces Important
▼
Interfaces: An abstract type that includes a certain set of fields that a type must include to implement the interface.
interface Character {
id: ID!
name: String!
}
type Human implements Character {
id: ID!
name: String!
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
primaryFunction: String
}
Unions Important
▼
Unions represent an object that could be one of several types, but those types don't necessarily share any fields.
union SearchResult = Human | Droid | Starship
query Search($text: String!) {
search(text: $text) {
... on Human { name }
... on Starship { name, length }
}
}
Custom Scalars Advanced
▼
By default, GraphQL includes
Int, Float, String, Boolean, and ID. You can define your own, like Date.scalar Date
You'll need a library like
graphql-scalars to handle the parsing and serialization logic for common custom types.Schema Stitching vs Federation Advanced
▼
When scaling to multiple teams, you might split your schema into microservices.
- Stitching: An older method where a gateway manually merges schemas.
- Federation: The modern Apollo standard where subgraphs define their own entities and the gateway handles execution.
Apollo Link (Middleware) Advanced
▼
Apollo Link is a system of modular components that you can chain together to customize the flow of data between your client and server.
const authLink = setContext((_, { headers }) => {
return {
headers: { ...headers, authorization: token }
}
});
Frontend (Next.js) — Topic Progress
0 / 5 read
Apollo Client Integration
1. Setup Apollo Client Critical
▼
npm install @apollo/client graphql
// lib/apollo-client.js
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache(),
});
export default client;
2. ApolloProvider in Next.js Critical
▼
In Next.js App Router, you wrap your client-side components with the provider.
// app/providers.js
'use client';
import { ApolloProvider } from '@apollo/client';
import client from '@/lib/apollo-client';
export function Providers({ children }) {
return {children} ;
}
3. The useQuery Hook Important
▼
import { useQuery, gql } from '@apollo/client';
const GET_BOOKS = gql`
query GetBooks {
books {
title
author
}
}
`;
function BookList() {
const { loading, error, data } = useQuery(GET_BOOKS);
if (loading) return Loading...
;
if (error) return Error: {error.message}
;
return data.books.map(({ title, author }) => (
{title}
{author}
));
}
4. The useMutation Hook Important
▼
const ADD_BOOK = gql`
mutation AddBook($title: String!, $author: String!) {
addBook(title: $title, author: $author) {
title
author
}
}
`;
function AddBookForm() {
let inputTitle, inputAuthor;
const [addBook, { data, loading, error }] = useMutation(ADD_BOOK);
return (
);
}
5. Client-side Fragments Important
▼
Use fragments in your frontend components to ensure they only request the data they actually need.
BookList.fragments = {
book: gql`
fragment BookDetails on Book {
title
author
}
`,
};
Production Ready — Topic Progress
0 / 4 read
Best Practices
1. Authentication & Context Critical
▼
Context: A shared object that is passed to every resolver. It's the perfect place to put authentication info.
// In server setup
const server = new ApolloServer({
typeDefs,
resolvers,
});
app.use(
'/graphql',
expressMiddleware(server, {
context: async ({ req }) => ({
user: await getUserFromToken(req.headers.authorization),
}),
}),
);
2. N+1 Problem & Dataloader Advanced
▼
The N+1 problem occurs when you fetch a list of items (1 query) and then fetch a related item for each of those items (N queries).
Dataloader is a utility that batches and caches requests to a back-end, reducing the number of database hits.
const authorLoader = new DataLoader(async (ids) => {
const authors = await getAuthorsByIds(ids);
return ids.map(id => authors.find(a => a.id === id));
});
3. Error Handling Important
▼
Apollo Server provides built-in error types like
GraphQLError.import { GraphQLError } from 'graphql';
if (!user) {
throw new GraphQLError('Not authenticated', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
4. Security: Depth & Complexity Critical
▼
Malicious queries can try to crash your server by nesting calls infinitely.
- Depth Limiting: Limit how deep a query can go.
- Complexity Analysis: Assign costs to fields and reject queries above a certain threshold.
Use libraries like
graphql-depth-limit to easily protect your server.