Recently, I discovered NestJS and instantly fell in love. It is everything that I love about Angular (which includes TypeScript, the opinionated file structure, the modules, the decorators, and the dependency injection) in a Node framework. Additionally, it supports GraphQL.
Before We Get Started
This tutorial is not meant for beginners. It will not cover Node basics. If you're looking to learn how Node and Express work, I made a series of videos where I create and deploy a basic timestamp microservice. Additionally, it will not cover GraphQL basics.
I made a video tutorial based off of this blog post. I suggest reading this blog post and watching the video as they complement each other well.
This tutorial uses nest-cli
version 6.6.4 and was written on September 18th, 2019. If anything is out of date or to report any errors/blockers, please feel free to send me a tweet.
If at any point you feel lost, you can take a look at the final form of this code.
AryanJ-NYC / nestjs-graphql-tutorial
See README below
With all of that out of the way, let's do this!
Getting Started with NestJS
NestJS is a Node.js framework that is compatible with both TypeScript and pure JavaScript. It comes with guard, pipe and interceptor support out-of-the-box. This makes it easy-to-use yet extremely powerful.
To get started, install the NestJS CLI. This allows you to easily create a new NestJS project.
npm i -g @nestjs/cli
nest new my-node-project
where my-node-project
is the name of your Node project. If you have NPM and yarn installed, the NestJS will ask for your preference. If the project was created correctly, you should have a newly created project with the following structure:
.
├── README.md
├── nest-cli.json
├── package.json
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
├── tsconfig.json
├── tslint.json
└── yarn.lock
Now go into that directory and run your newly created Node server:
cd my-node-project
npm run start:dev
Go to http://localhost:3000 (by default) to hit your "Hello World!" endpoint.
Sprinkle in GraphQL
GraphQL is a query language for APIs. NestJS uses their own GraphQLModule
(imported from @nestj/graphql
) which is a wrapper around the Apollo GraphQL server.
Before we get started, let's remove the soon-to-be unused files (that were used for the "Hello World!" endpoint). More specifically, please delete src/app.controller.ts
, src/app.service.ts
, and their corresponding test files.
To get started with GraphQL and NestJS, install the necessary dependencies:
npm i --save @nestjs/graphql apollo-server-express graphql-tools graphql
With these packages installed, register the GraphQLModule
in /src/app.module.ts
:
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
@Module({
imports: [
GraphQLModule.forRoot({
definitions: {
path: join(process.cwd(), '/src/graphql.schema.d.ts'),
outputAs: 'class',
},
typePaths: ['./**/*.graphql'],
resolverValidationOptions: {
requireResolversForResolveType: false,
},
}),
],
})
export class AppModule {}
I know, I know. There are tons of changes in here that I threw at you all. The NestJS GraphQL documentation does a fantastic job at explaining these changes. Here's my take.
GraphQLModule.forRoot()
This registers the GraphQLModule
with the server. The .forRoot()
method takes an options
object as an argument.
definitions
The @nestjs/graphql
package automatically generates TypeScript defintions from the GraphQL schemas (see typePaths
). We use the definitions
object to configure the path
where TypeScript definitions should be saved. By default, the GraphQL types are transformed to interfaces. I personally prefer classes which is what is seen in definitions.outputAs
.
typePaths
typePaths
tells the GraphQLModule
where in the project to look for GraphQL files.
resolverValidationOptions
When running the server without resolverValidationOptions.requireResolversForResolveType
equal to false, I get a warning similar to this one. Therefore, it's false (for now).
Alright, back to GraphQL. Add src/schema.graphql
to your project as follows:
type Message {
id: Int!
description: String!
}
type Query {
messages: [Message]!
}
type Mutation {
createMessage(description: String!): Message!
}
Restart your Node server, go to http://localhost:3000/graphql and you'll see a GraphQL playground. Of course, any query or mutation you try running will end in an error as we're yet to write our resolvers.
Writing a GraphQL Resolver with NestJS
Let's write our first GraphQL resolver. Firstly, create a new NestJS module:
nest generate module messages
This will import the MessagesModule
into AppModule
and create a new src/messages
directory where the business logic for your Messages resolver will live (see what I was saying about NestJS' modularity?).
Now, let's create that resolver. We'll create a dummy variable named messagesThatReallyShouldBeInADb
that will function as our database and store all the messages and a GraphQL query that returns all the messages. In src/messages/messages.resolver.ts
:
import { Resolver, Query } from '@nestjs/graphql';
@Resolver()
export class MessagesResolver {
// this is just for demonstration purposes
// do NOT do this in real-life
// this is meant as a substitute for a database
messagesThatReallyShouldBeInADb = [
{ id: 0, description: 'The seed message' },
];
@Query()
messages() {
return this.messagesThatReallyShouldBeInADb;
}
}
Note the decorators NestJS provides us (Resolver
and Query
). This automagically maps to the messages
query that we declared in src/schema.graphql
. We must now provide this resolver to the MessagesModule
. In src/messages.module.ts
:
import { Module } from '@nestjs/common';
import { MessagesResolver } from './messages.resolver';
@Module({
providers: [MessagesResolver],
exports: [MessagesResolver],
})
export class MessagesModule {}
Go to http://localhost:3000/graphql, refresh the page, and run the messages query:
{
messages {
description
}
}
If everything was done correctly, you should see the seed message:
Now let's add the createMessage
mutation to src/messages/messages.resolver.ts
. Remember the resolver type signature takes four arguments (parent, args, context, info)
. NestJS provides decorators for each argument. For this specific mutation, we use the @Args()
decorator and pass it the name of the argument we want to access (description
):
import { Mutation, Resolver, Query, Args } from '@nestjs/graphql';
@Resolver()
export class MessagesResolver {
// this is just for demonstration purposes
// do NOT do this in real-life
// this is meant as a substitute for a databse
messagesThatReallyShouldBeInADb = [
{ id: 0, description: 'The seed message' },
];
@Query()
messages() {
return this.messagesThatReallyShouldBeInADb;
}
@Mutation()
createMessage(@Args('description') description: string) {
const id = this.messagesThatReallyShouldBeInADb.length;
const newMessage = { id, description };
this.messagesThatReallyShouldBeInADb.push(newMessage);
return newMessage;
}
}
With the mutation added to the resolver, let's return to our GraphQL Playground at http://localhost:3000/graphql and create some messages:
mutation {
createMessage(description: "This is a witty description") {
description
}
}
which should successfully return:
Feel free to create a few messages using our new mutation and query for all messages.
Conclusion
With that, you now have a NestJS server complete with GraphQL, a simple GraphQL schema and a simple resolver for that schema (complete with a query and mutation). If you did everything correctly, the messages
query and createMessage
mutation should work as it does in this demo server. Again, if at any time you got lost and want to see the entire demo project, check out my GitHub repository.
The next step is to add a database to this stack. Prisma is an amazing solution that provides us with additional GraphQL and database tooling. In the next installment of this series, we will dive into using Prisma to save our messages.
If you liked this post, please support me by following me on Twitter, YouTube and GitHub.
Top comments (6)
I wish there was React-style Node.js framework...
Nest.js is great, but I more of React person rather than Angular.
If you mean barebones, then you already have that with Nodejs. Or Koa.
I really like Nestjs. It really forces you to work in an architecturally sound way.
There are a few things I dislike though, namely:
No, not barebones, but with some actual architecture. Just without abstract classes, decorators and fabrics. Something more functional and declarative.
Koa isn't full blown framework, it is just web-server. Would be nice to have something on top.
This is what I wish too
Hey Hein:
You're right. The docs don't have recipes for many things (as you said, social providers auth with sessions). They do feature an authentication section that describes using passport.js to create an
AuthenticationModule
. Have you taken a look at that?I'm looking for new material to write. Do you think a blog post / set of videos that cover social provider authentication would help?
Hey Aryan, yes I've followed the docs but didn't find it too helpful for the issues I faced. (backend & frontend on different ports, causing same site cookies to not be attached, lack of API docs for some strategies. Not knowing which parameters are available and what they do. )
A lot can be written about social providers and authentication, like:
Many of the examples I found online for Nestjs + social providers contained way too much code and didn't feel like a solid Nestjs way of doing things. A curated approach would be very welcome, be it in article and/or video form.