There’s this funny thing that happens every few years in tech, the hype bubble.
And without fail, about five years later, micro-legacy systems are born out of it.
GraphQL is slowly sliding into that category. If you work in the backend, you’ll eventually bump into GraphQL systems, a few good ones, but most are really bad.
Now, GraphQL itself isn’t the problem. The real culprit is the hype, the wave of eager adopters jumping in without clarity. And hype always comes with terrible assumptions.
With GraphQL, the big blunder was thinking of it as a “database-like query language.” Spoiler: it’s not, and it never will be.
In this post, I’ll walk you through the fundamental idea of GraphQL, plus a basic example that everything else builds on. This is useful because, odds are, you’ll inherit a GraphQL codebase at some point.
But let’s be honest, the chances of it being good? Slim.
What is GraphQL?
To understand GraphQL, you first need to understand the problem it solves:
APIs and endpoints are dynamic, they change a lot. This leads to what I call the “sprawl problem.”
Imagine you have a simple users API:
const userSchema = {
id: string,
name: string,
email: string,
isActive: boolean,
createdAt: Date,
}
const users: Array<userSchema> = [/* ... */]
You start with a default endpoint:
host/users/
Soon, you realize you need to fetch a single user:
host/users/userid
Then, clients start loving the isActive
property, so you make an endpoint just for that:
host/users/:userid/isactive
But isActive
isn’t descriptive enough. You replace it with status
because users can have more than just a true/false state:
# /users/userid/isactive is now dead
host/users/:userid/status
And it just keeps going. In big systems, endpoint creep is a real problem.
GraphQL says, “Instead of creating endless endpoints like we did at Facebook, let’s have one or two endpoints that can handle everything.”
Example:
host/users
You simply define how to access all possible data shapes. When new possibilities arise, you add a new schema and accessor (you’ll see how later).
GraphQL sits between your endpoints and the client. The client says, “Here’s the exact shape of data I want,” and GraphQL delivers, if it exists.
That’s the fundamental idea. Nothing more.
But hype does weird things to people. They started treating GraphQL like a database query language, which caused performance nightmares, over-fetching, under-fetching, and all kinds of uncanny valley weirdness.
Now the tool is getting hate, not because it’s bad, but because it was misunderstood from day one.
GraphQL in 3 Minutes
To get started with GraphQL, we’ll use an Apollo Server that’s already set up for it.
Create Folder Structure:
mkdir graphql-server-example
cd graphql-server-example
Init a new npm project:
npm init --yes && npm pkg set type="module"
install dependencies
npm install apollo-server graphql
Update your package.json
if needed to include the start script:
"scripts": {
"start": "node index.js"
}
Starter Stub:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
const typeDefs = `#graphql
type Query {
}
`
const resolvers = {
Query: {
},
};
// instance of an appollo server
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true,
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});
console.log(`🚀 Server ready at: ${url}`);
To start the server:
npm start
TypeDefs
This is your schema, the shape of your data.
In our example, we’re working with User
objects:
const typeDefs = `#graphql
type User {
id: ID!
name: String
email: String
status: String
isActive: Boolean
createdAt: String
}
# special type listing all possible available queries for the schema, returns an array of type user
# example if you had another type let's say logs, you'll put as a query too to be a possible shape.
type Query {
users: [User],
# logs: [log] <- if you have a log typedef
}
`;
The schema tells GraphQL what fields exist and how they’re related. Once you’ve got that, you’ll need resolvers.
Queries
Queries define what clients are allowed to ask for, along with the type of data returned, as you seen above:
type Query {
users: [User]
}
Resolvers
Resolvers are functions that return data for your schema.
Think of them as “dynamic endpoints”, each one maps to a type or field in your schema.
const users = [
{ id: "1", name: "Alice", email: "alice@example.com", status: "ACTIVE", isActive: true, createdAt: "2025-01-01" },
{ id: "2", name: "Bob", email: "bob@example.com", status: "INACTIVE", isActive: false, createdAt: "2025-01-10" }
];
const resolvers = {
Query: {
users: () => users
}
};
When a query hits users
, GraphQL fetches that list, then filters and shapes the result to match exactly what the client asked for.
Example Query in Sandbox
query StatusOnly {
users {
status
}
}
We still hit users
, but only status
comes back, no extra fields.
More Query Examples
# Want everything?
query FullUser {
users {
id
name
email
status
isActive
createdAt
}
}
# Just status?
query StatusOnly {
users {
status
}
}
# Name + active flag?
query NameAndActive {
users {
name
isActive
}
}
No endpoint sprawl.
The client drives the shape of the data. Your schema and resolvers don’t change, only the query does.
Queries are for reading data. Mutations are for changing it, creating, updating, or deleting records. The core idea is the same.
In this post we covered the fundamental idea behind GraphQl, and solidified it with a basic query example.
Ever wondered what it really takes to build low-level Node.js tooling or distributed systems from scratch?
- Learn raw TCP
- Go over a message broker in pure JavaScript
- Go from "can code" to "can engineer"
Check out: How to Go from a 6.5 to an 8.5 Developer
—
Or maybe you're ready to master the dark art of authentication?
- From salting and peppering to mock auth libraries
- Understand tokens, sessions, and identity probes
- Confidently use (or ditch) auth-as-a-service or roll out your own?
Grab: The Authentication Handbook: With Node.js Examples
thanks for reading. 🙏
Top comments (2)
This is a great breakdown of GraphQL without the hype. I like how you highlight that it’s not a database query language, but a flexible layer between client and server. The basic Apollo example makes it clear how queries map to resolvers without causing endpoint sprawl.
Resources:
Appollo Docs
For clients (replaces the sandbox):
Appollo Client