Why GraphQL matters in 2026
After building REST APIs for years, I switched to GraphQL for a complex project and never looked back for certain use cases. GraphQL solves two fundamental problems with REST: over-fetching (getting more data than you need) and under-fetching (needing multiple requests to get related data).
Created by Facebook in 2015, GraphQL is a query language for APIs that lets clients request exactly the data they need in a single request. In 2026, it powers APIs at GitHub, Shopify, Stripe, and thousands of other companies.
GraphQL vs REST: when to use which
| Feature | REST | GraphQL |
|---|---|---|
| Data fetching | Fixed endpoints, fixed data shape | Client specifies exact fields needed |
| Multiple resources | Multiple requests needed | Single request for related data |
| Versioning | URL versioning (/v1/, /v2/) | Schema evolution, no versioning needed |
| Caching | HTTP caching built-in | Requires client-side caching (Apollo) |
| Learning curve | Low | Medium |
| Best for | Simple CRUD, microservices | Complex data relationships, mobile apps |
Setting up your first GraphQL server
# Create project
mkdir graphql-api && cd graphql-api
npm init -y
# Install dependencies
npm install @apollo/server graphql
# For TypeScript (recommended)
npm install -D typescript @types/node ts-node
npx tsc --init
Define your schema
The schema is the contract between your client and server. It defines what data is available and what operations clients can perform.
// src/schema.ts
export const typeDefs = `#graphql
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
createdAt: String!
}
type Query {
users: [User!]!
user(id: ID!): User
posts(published: Boolean): [Post!]!
post(id: ID!): Post
}
type Mutation {
createUser(name: String!, email: String!): User!
createPost(title: String!, content: String!, authorId: ID!): Post!
publishPost(id: ID!): Post!
deletePost(id: ID!): Boolean!
}
`;
Implement resolvers
// src/resolvers.ts
const users = [
{ id: "1", name: "Alice", email: "alice@example.com", createdAt: new Date().toISOString() },
{ id: "2", name: "Bob", email: "bob@example.com", createdAt: new Date().toISOString() },
];
const posts = [
{ id: "1", title: "GraphQL Basics", content: "Learn GraphQL...", published: true, authorId: "1", createdAt: new Date().toISOString() },
{ id: "2", title: "Advanced Queries", content: "Deep dive...", published: false, authorId: "1", createdAt: new Date().toISOString() },
];
let nextUserId = 3;
let nextPostId = 3;
export const resolvers = {
Query: {
users: () => users,
user: (_: any, { id }: { id: string }) => users.find(u => u.id === id),
posts: (_: any, { published }: { published?: boolean }) => {
if (published !== undefined) return posts.filter(p => p.published === published);
return posts;
},
post: (_: any, { id }: { id: string }) => posts.find(p => p.id === id),
},
Mutation: {
createUser: (_: any, { name, email }: { name: string; email: string }) => {
const user = { id: String(nextUserId++), name, email, createdAt: new Date().toISOString() };
users.push(user);
return user;
},
createPost: (_: any, { title, content, authorId }: any) => {
const post = { id: String(nextPostId++), title, content, published: false, authorId, createdAt: new Date().toISOString() };
posts.push(post);
return post;
},
publishPost: (_: any, { id }: { id: string }) => {
const post = posts.find(p => p.id === id);
if (!post) throw new Error("Post not found");
post.published = true;
return post;
},
deletePost: (_: any, { id }: { id: string }) => {
const index = posts.findIndex(p => p.id === id);
if (index === -1) return false;
posts.splice(index, 1);
return true;
},
},
// Field resolvers for relationships
User: {
posts: (parent: any) => posts.filter(p => p.authorId === parent.id),
},
Post: {
author: (parent: any) => users.find(u => u.id === parent.authorId),
},
};
Start the server
// src/index.ts
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { typeDefs } from "./schema";
import { resolvers } from "./resolvers";
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
console.log(`GraphQL server ready at ${url}`);
Querying your API
# Get all users with their posts (single request!)
query {
users {
name
email
posts {
title
published
}
}
}
# Get a specific user
query {
user(id: "1") {
name
posts {
title
content
}
}
}
# Create a new post
mutation {
createPost(title: "My New Post", content: "Hello GraphQL!", authorId: "1") {
id
title
author {
name
}
}
}
Troubleshooting common issues
Issue 1: "Cannot return null for non-nullable field". Your resolver is returning null or undefined for a field marked with ! (non-nullable). Check that your data source actually contains the expected data and your resolver logic is correct.
Issue 2: N+1 query problem. When fetching a list of users with their posts, the posts resolver runs once per user, causing N+1 database queries. Solution: use DataLoader to batch and cache database queries.
Issue 3: Schema validation errors. If your schema does not compile, check for typos in type names, missing exclamation marks, and ensure all referenced types are defined. The Apollo Server error messages are usually descriptive enough to pinpoint the issue.