GraphQL in NestJS for REST Developers

GraphQL in NestJS for REST Developers
GraphQL in NestJS for REST Developers

If you already understand REST, learning GraphQL feels a bit like being told:

“Good news, your API can now be more flexible.”
“Also, some of the rules you got comfortable with are about to change.”

Fair enough.

GraphQL is one of those things that attracts both genuine excitement and a little too much hype.

So this article is not going to treat it like magic.

Instead, we are going to look at GraphQL the most useful way possible:

through the eyes of a REST developer

That means we will answer questions like:

  • what GraphQL actually changes
  • how it differs from REST
  • how NestJS supports it
  • what resolvers do
  • what code-first and schema-first mean
  • when GraphQL is a good fit
  • when REST is still the simpler choice

By the end, you should be able to look at GraphQL in NestJS without either over-romanticizing it or avoiding it for no reason.

Let’s make it practical.


What GraphQL Actually Is

GraphQL is an API query language and execution model where clients ask for exactly the data they need.

That is the big idea.

Instead of exposing many resource-based endpoints like:

  • GET /users
  • GET /users/1
  • GET /posts
  • GET /posts/1/comments

you often expose a single GraphQL endpoint, commonly:

/graphql

Then the client sends a query describing the shape of the data it wants.

Example:

query {
  user(id: 1) {
    id
    name
    posts {
      title
    }
  }
}

Instead of the server deciding the exact response shape for each endpoint, the client can request the fields it needs.

That is one of GraphQL’s biggest differences from REST.


How REST Developers Usually Experience the Difference

If you come from REST, you are probably used to thinking like this:

  • one resource
  • one route
  • one HTTP method
  • one controller handler

GraphQL changes that mental model.

Instead of focusing on many routes, you focus more on:

  • a schema
  • types
  • queries
  • mutations
  • resolvers

That can feel weird at first.

Because instead of asking:
“Which endpoint should I call?”

you start asking:
“What data shape should this query request?”

That is a pretty big shift.


GraphQL vs REST: The Simplest Practical Difference

Here is the cleanest way to compare them.

REST says

“Here is a route. I decide what this route returns.”

GraphQL says

“Here is a schema. You ask for the fields you want.”

That leads to some important differences.

REST is often better when

  • your API is simple
  • your resource model is straightforward
  • HTTP semantics matter a lot
  • your team already works well with route-based APIs
  • you want fewer moving parts

GraphQL is often better when

  • clients need flexible data shapes
  • one screen needs data from many related resources
  • over-fetching or under-fetching becomes annoying
  • frontend teams want more control over what gets returned
  • your domain model is deeply connected

This is why GraphQL often becomes attractive in apps with rich UI data needs.

Not because it is automatically “better.”

Because it solves certain API pain points well.


How NestJS Supports GraphQL

Nest’s GraphQL docs say Nest provides official GraphQL integrations through drivers like @nestjs/apollo and @nestjs/mercurius.

So GraphQL is not some weird side-path in Nest.

It is a first-class feature area.

That means Nest gives you:

  • GraphQL module integration
  • resolver support
  • decorators for schema definition
  • code-first and schema-first approaches
  • subscriptions support
  • federation support

Which is great, because it means GraphQL still fits into the same Nest philosophy:

  • modular
  • decorator-based
  • structured
  • scalable

Very on-brand.


The Two Main GraphQL Approaches in NestJS

Nest’s GraphQL quick start still documents two main ways to build GraphQL APIs:

  • code-first
  • schema-first

This is one of the first decisions you will run into.

Let’s make it simple.


Code-First Approach

In the code-first approach, you use TypeScript classes and decorators to generate the GraphQL schema automatically.

Nest’s docs say the @nestjs/graphql package reads metadata from decorators and generates the schema for you. The standard setup uses autoSchemaFile.

Example setup:

import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: true,
    }),
  ],
})
export class AppModule {}

That autoSchemaFile option is the key signal of code-first. Nest’s quick start docs still show autoSchemaFile: true or a file path as the standard code-first configuration.

Why code-first feels nice

  • very TypeScript-friendly
  • less duplicate definition work
  • easy for Nest developers who already like decorators
  • single source of truth often feels easier in small-to-medium apps

Why code-first can feel awkward sometimes

  • schema visibility is a bit less direct at first
  • some teams prefer seeing SDL explicitly
  • advanced schema control can feel less obvious

For many Nest beginners, code-first is the easiest place to start.


Schema-First Approach

In the schema-first approach, you write GraphQL SDL files directly and point Nest at them with typePaths.

Nest’s quick start docs still show typePaths: ['./**/*.graphql'] as the standard schema-first setup.

Example:

import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      typePaths: ['./**/*.graphql'],
    }),
  ],
})
export class AppModule {}

Then your schema file might look like:

type User {
  id: Int!
  name: String!
}

type Query {
  user(id: Int!): User
}

Why schema-first feels nice

  • the schema is explicit
  • teams can reason about API design directly in SDL
  • good when the schema is the true contract you want to design first

Why schema-first can feel heavier

  • more duplication with TypeScript models
  • syncing schema and TypeScript can become tedious
  • you may need generated typings or careful maintenance

Nest’s docs explicitly call out that hand-maintaining both SDL and TypeScript definitions can become repetitive, which is one reason code-first is popular.


So Which One Should Beginners Choose?

For most Nest beginners, I would lean code-first.

Why?

Because if you already like Nest’s TypeScript + decorators style, code-first feels very natural.

You stay in one mental model:

  • classes
  • decorators
  • modules
  • providers
  • resolvers

That said, schema-first is still a good choice when:

  • your team wants the schema to be the primary design artifact
  • you already think heavily in GraphQL SDL
  • your API contract is being designed very intentionally outside implementation details

So the short version is:

  • code-first = easier for many Nest beginners
  • schema-first = great when schema design is the main priority

What Is a Resolver in NestJS?

If controllers are the heart of REST in NestJS, resolvers are the heart of GraphQL.

Nest’s GraphQL docs still describe resolvers as the classes that resolve GraphQL operations and fields. In code-first, you typically use decorators like @Resolver(), @Query(), @Mutation(), and @ResolveField().

A resolver is basically the logic that tells GraphQL how to fetch the requested data.

So instead of:

@Get(':id')
findOne() {}

you write something more like:

@Query(() => User)
user() {}

That is the role shift:

  • REST uses controllers and routes
  • GraphQL uses resolvers and schema operations

A Simple Code-First Resolver Example

Let’s make that real.

Model

import { Field, Int, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class User {
  @Field(() => Int)
  id: number;

  @Field()
  name: string;
}

Nest’s resolvers docs still show code-first object types being built with decorators like @ObjectType() and @Field().

Resolver

import { Args, Int, Query, Resolver } from '@nestjs/graphql';
import { User } from './models/user.model';

@Resolver(() => User)
export class UsersResolver {
  private users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ];

  @Query(() => User, { nullable: true })
  user(@Args('id', { type: () => Int }) id: number) {
    return this.users.find((user) => user.id === id);
  }

  @Query(() => [User])
  users() {
    return this.users;
  }
}

That is already enough to show the pattern:

  • @Resolver() defines the resolver class
  • @Query() defines read operations
  • @Args() reads GraphQL arguments
  • return types are declared explicitly

Very GraphQL.
Still very Nest.


Queries vs Mutations

This part is simple once you stop overthinking it.

Query

Use it for reading data.

Example:

  • fetch one user
  • fetch all posts
  • fetch comments for a post

Mutation

Use it for changing data.

Example:

  • create a user
  • update a post
  • delete a comment

Nest’s GraphQL docs still separate these operation types clearly with decorators like @Query() and @Mutation().

So if REST taught you:

  • GET reads
  • POST / PATCH / DELETE change state

then GraphQL teaches you:

  • Query reads
  • Mutation changes state

Same idea, different packaging.


A Simple Mutation Example

import { Args, Int, Mutation, Resolver } from '@nestjs/graphql';
import { User } from './models/user.model';

@Resolver(() => User)
export class UsersResolver {
  private users = [{ id: 1, name: 'Alice' }];

  @Mutation(() => User)
  createUser(@Args('name') name: string) {
    const newUser = {
      id: this.users.length + 1,
      name,
    };

    this.users.push(newUser);
    return newUser;
  }
}

Now the client can send a mutation like:

mutation {
  createUser(name: "Ziye") {
    id
    name
  }
}

Very different from REST routing.
Still not too hard once you see it in action.


How GraphQL Solves the Over-Fetching Problem

This is one of the most common selling points for GraphQL.

Imagine a REST endpoint returns:

{
  "id": 1,
  "name": "Alice",
  "email": "alice@example.com",
  "bio": "Long bio here",
  "posts": [...],
  "settings": {...}
}

But your frontend screen only needs:

  • id
  • name

That means the client got more data than it needed.

GraphQL helps because the client can ask for exactly:

query {
  user(id: 1) {
    id
    name
  }
}

Much tighter.

That is one reason frontend-heavy teams often love GraphQL.


How GraphQL Can Also Reduce Under-Fetching

The opposite problem also happens in REST.

One screen may need:

  • user details
  • post list
  • comment count
  • profile stats

And maybe it takes:

  • one request to /users/1
  • another to /users/1/posts
  • another to /users/1/stats

GraphQL can let the client request everything in one query if the schema supports it.

That can be very nice for complex screens.

So yes, GraphQL often shines when the UI needs a flexible, nested data shape.


What GraphQL Does Not Magically Solve

This part matters.

GraphQL is useful, but it is not magic.

It does not automatically solve:

  • bad backend architecture
  • poor database access patterns
  • authorization complexity
  • caching strategy by itself
  • performance issues caused by naive resolvers

You can absolutely build a messy GraphQL API.

Very easily, in fact.

So the smart mindset is:
GraphQL is a different API model, not an automatic upgrade in all situations.

That is much healthier than the hype version.


Subscriptions in NestJS

Nest’s subscriptions docs still explain that GraphQL supports a third operation type called subscription, which lets the server push real-time updates to clients. They also warn that Apollo’s old installSubscriptionHandlers option is gone and strongly recommend using graphql-ws.

So in GraphQL, you have:

  • Query = read once
  • Mutation = change data
  • Subscription = listen for real-time updates

A common example would be:

  • notify clients when a new post is created
  • push live comment updates
  • stream status changes

This is one of the areas where GraphQL can be very appealing for interactive apps.

But for beginners, I would treat subscriptions as a later step, not the first reason to adopt GraphQL.


Apollo Federation in NestJS

Nest also has official docs for Apollo Federation, which is useful when GraphQL is spread across multiple services. The docs still show Nest federation support with ApolloFederationDriver for subgraphs and ApolloGatewayDriver plus @apollo/gateway for a gateway.

This matters because in larger systems, GraphQL is not always just one app.

Sometimes you want:

  • users service
  • posts service
  • billing service

all contributing part of one unified GraphQL graph.

That is where federation comes in.

For beginners, you do not need to start here.

But it is useful to know that GraphQL in Nest can scale beyond a single small app.


When GraphQL Makes a Lot of Sense

GraphQL is often a strong fit when:

  • frontend screens need flexible nested data
  • many related resources must be combined
  • multiple client apps need different data shapes
  • over-fetching and under-fetching are becoming annoying
  • your team is comfortable managing schema design

It is especially attractive in:

  • dashboards
  • social apps
  • complex admin panels
  • highly interactive frontends
  • apps with evolving client needs

That is where GraphQL often feels worth the extra mental model.


When REST Is Still the Better Choice

REST is still a great choice when:

  • your API is straightforward
  • your resource model is simple
  • your team already works well with route-based APIs
  • HTTP semantics and caching patterns matter a lot
  • you do not need flexible nested querying
  • you want less complexity

This is important because sometimes the best GraphQL advice is:

you do not actually need GraphQL yet

And that is completely fine.

REST is not outdated.
It is just different.


GraphQL in NestJS Feels Familiar in One Important Way

Even though GraphQL changes the API model, Nest still makes it feel structured.

You still get:

  • modules
  • providers
  • dependency injection
  • decorators
  • clean organization

So while the transport model changes, the Nest development style still feels familiar.

That is one reason Nest is such a comfortable place to learn GraphQL if you already like its architecture.

It gives you a new API style without abandoning the framework habits you already built.


Common Beginner Mistakes with GraphQL

Let’s prevent a few.

1. Treating GraphQL like “REST but fancier”

It is a different API model.
Do not try to mentally map everything too literally.

2. Choosing GraphQL because it sounds modern

Use it when it solves real API problems, not just because it looks cool in architecture diagrams.

3. Ignoring schema design

The schema is the contract.
It deserves careful thought.

4. Jumping into federation too early

Federation is powerful, but it is definitely not the first GraphQL concept beginners need.

5. Assuming GraphQL automatically fixes performance

Bad resolver patterns can still create serious issues.

6. Overcomplicating simple APIs

A small, clean REST API is often better than an unnecessarily clever GraphQL setup.


A Good Mental Model to Remember

Here is the simplest way to carry this in your head:

  • REST = many routes, server decides response shape
  • GraphQL = one graph/schema, client requests response shape

And in Nest terms:

  • controller is to REST
  • resolver is to GraphQL

That one comparison already gets you pretty far.


Why This Chapter Matters

This chapter matters because GraphQL is often introduced in a way that confuses people unnecessarily.

Usually either:

  • it gets oversold as the future of everything
  • or it gets dismissed before people understand why teams use it

Neither approach is very useful.

The useful approach is:

  • understand REST first
  • understand GraphQL next
  • compare them based on actual use cases
  • choose based on the problem you are solving

That is the mature way to think about it.

And now you have that framework.


Final Thoughts

GraphQL in NestJS is a powerful option, especially for developers coming from REST who want more flexible data fetching.

Nest’s current GraphQL docs still support both code-first and schema-first development, official drivers like @nestjs/apollo and @nestjs/mercurius, subscriptions, and federation.

That means GraphQL is not some awkward side-feature in Nest.

It is a real first-class path.

The important thing to remember is this:

  • GraphQL is great when clients need flexible, connected data
  • REST is still great when simplicity and resource-based APIs are enough

So do not think of this as:
REST vs GraphQL, one must win

Think of it as:
Which API model makes more sense for this application?

That question leads to much better architecture decisions.

And now that you understand both REST and GraphQL in NestJS, the next step is one of the most important engineering habits of all:

testing

Because whether your API is REST or GraphQL, code becomes much more trustworthy when you know how to verify it properly.


FAQ

Does NestJS support GraphQL officially?

Yes. Nest has official GraphQL support and official drivers such as @nestjs/apollo and @nestjs/mercurius.

What is the difference between code-first and schema-first in NestJS GraphQL?

Code-first uses TypeScript classes and decorators to generate the schema, usually with autoSchemaFile. Schema-first uses GraphQL SDL files and typePaths to load them.

What is a resolver in NestJS GraphQL?

A resolver is the class that handles GraphQL queries, mutations, and fields, similar to how a controller handles HTTP routes in REST.

Does NestJS support GraphQL subscriptions?

Yes. Nest supports subscriptions, and the current docs strongly recommend using graphql-ws.

Should beginners choose GraphQL or REST first?

Usually REST first. Once you understand REST well, GraphQL becomes much easier to evaluate and use intentionally.

Subscribe for new post. No spam, just tech.