DEV Community

Cover image for GraphQL Crash Course: The New Legacy System.
Sk
Sk

Posted on

GraphQL Crash Course: The New Legacy System.

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> = [/* ... */]
Enter fullscreen mode Exit fullscreen mode

You start with a default endpoint:

host/users/
Enter fullscreen mode Exit fullscreen mode

Soon, you realize you need to fetch a single user:

host/users/userid
Enter fullscreen mode Exit fullscreen mode

Then, clients start loving the isActive property, so you make an endpoint just for that:

host/users/:userid/isactive
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Init a new npm project:

  npm init --yes && npm pkg set type="module"
Enter fullscreen mode Exit fullscreen mode

install dependencies


npm install apollo-server graphql
Enter fullscreen mode Exit fullscreen mode

Update your package.json if needed to include the start script:

"scripts": {
  "start": "node index.js"
}
Enter fullscreen mode Exit fullscreen mode

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}`);

Enter fullscreen mode Exit fullscreen mode

To start the server:

npm start
Enter fullscreen mode Exit fullscreen mode

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 
  }
`;
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

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
  }
};
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

We still hit users, but only status comes back, no extra fields.


More Query Examples

graphql sandbox

# 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
  }
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
jessica_karen_af16ea6a72d profile image
Jessica Karen

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.

Collapse
 
sfundomhlungu profile image
Sk

Resources:

Appollo Docs

For clients (replaces the sandbox):

Appollo Client