Editor’s note: This GraphQL and NestJS tutorial was last updated on 3 August 2023 to explore the benefits of using GraphQL APIs.
NestJS is a TypeScript Node.js framework that helps you build enterprise-grade, efficient, and scalable Node.js applications. It supports both RESTful and GraphQL API design methodologies.
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. It provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need, makes it easier to evolve APIs over time, and facilitates the use of powerful developer tools.
In this tutorial, we’ll demonstrate how to use NestJS to build and harness the power of GraphQL APIs. We’ll cover the following:
Starting a Nest project is simple, as Nest provides a CLI that you can use to generate new projects. If you have npm installed, you can create a new Nest project with the following commands:
npm i -g @nestjs/cli nest new project-name
Nest will create a project directory using project-name
and add boilerplate files:
Under the hood, Nest exposes a GraphQL module that can be configured to use the Apollo GraphQL server in Nest applications. To add GraphQL APIs to our Nest project, we need to install Apollo Server and other GraphQL dependencies:
$ npm i --save @nestjs/graphql graphql-tools graphql apollo-server-express
With the dependencies installed, you can now import GraphQLModule
into AppModule
:
// src/app.module.ts import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; @Module({ imports: [ GraphQLModule.forRoot({}), ], }) export class AppModule {}
GraphQLModule
is a wrapper over Apollo Server. It provides a static method, forRoot()
, for configuring the underlying Apollo instance. The forRoot()
method accepts a list of options that is passed into the ApolloServer()
constructor.
In this article, we will use the code-first approach, which uses decorators and TypeScript classes to generate the GraphQL schema. For this approach, we need to add the autoSchemaFile
property — the path where our generated schema is created — to our GraphQLModule
options:
// src/app.module.ts import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; @Module({ imports: [ GraphQLModule.forRoot({ autoSchemaFile: 'schema.gql' }), ], }) export class AppModule {}
The
autoSchemaFile
can also be set totrue
, which means the generated schema will be saved to memory.
Nest is database-agnostic, meaning it allows integration with any database: Object Document Mapper (ODM), or Object Relational Mapper (ORM). For the purpose of this guide, we’ll use PostgreSQL and TypeORM.
The Nest team recommends using TypeORM with Nest because it’s the most mature ORM available for TypeScript. And because it’s written in TypeScript, it integrates well with the Nest framework. Nest provides the @nestjs/typeorm
package for working with TypeORM.
Let’s install these dependencies for working with the TypeORM database:
$ npm install --save @nestjs/typeorm typeorm pg
Once the installation process is complete, we can connect to the database using TypeOrmModule
:
// src/app.module.ts import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { GraphQLModule } from '@nestjs/graphql'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ GraphQLModule.forRoot({ autoSchemaFile: 'schema.gql' }), TypeOrmModule.forRoot({ type: 'postgres', host: 'localhost', port: 5432, username: 'godwinekuma', password: '', database: 'invoiceapp', entities: ['dist/**/*.model.js'], synchronize: false, }), ], controllers: [AppController], providers: [AppService], }) export class AppModule { }
Nest offers two methods of building GraphQL APIs: code-first and schema-first. The code-first approach involves using TypeScript classes and decorators to generate GraphQL schemas. With this approach, you can reuse your data model class as a schema and decorate it with the @ObjectType()
decorator. Nest will autogenerate the schema from your model. Meanwhile, the schema-first approach involves defining the schema using GraphQL’s Schema Definition Language (SDL) and then implementing a service by matching the definitions in the schema.
As mentioned earlier, this article will use the code-first method. With this approach, @nestjs/graphql
generates the schema from reading the metadata specified in the decorators of our TypeScript class definitions.
The GraphQL API is made up of several components that execute the API requests or form the objects for its responses.
Resolvers
Resolvers provide instructions for turning a GraphQL operation (a query, mutation, or subscription) into data. They either return the type of data we specify in our schema, or a promise for that data.
The @nestjs/graphql
package automatically generates a resolver map using the metadata provided by the decorators used to annotate classes. To demonstrate how to use the package features to create a GraphQL API, we’ll create a simple invoice API.
Object types
Object types are the most basic components of GraphQL. It is a collection of fields that you can fetch from your service, with each field declaring a type. Each defined object type represents a domain object in your API, specifying the structure of the data that can be queried or mutated in the API. For example, our sample invoice API needs to be able to fetch a list of customers and their invoices, so we should define the Customer
and Invoice
object types to support this functionality.
The Object types are used to define query objects, mutations, and schema for our API. Because we’re using the code-first approach, we’ll define schemas using TypeScript classes and then use TypeScript decorators to annotate the fields of those classes:
// src/invoice/customer.model.ts import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm'; import { ObjectType, Field } from '@nestjs/graphql'; import { InvoiceModel } from '../invoice/invoice.model'; @ObjectType() @Entity() export class CustomerModel { @Field() @PrimaryGeneratedColumn('uuid') id: string; @Field() @Column({ length: 500, nullable: false }) name: string; @Field() @Column('text', { nullable: false }) email: string; @Field() @Column('varchar', { length: 15 }) phone: string; @Field() @Column('text') address: string; @Field(type => [InvoiceModel], { nullable: true }) @OneToMany(type => InvoiceModel, invoice => invoice.customer) invoices: InvoiceModel[] @Field() @Column() @CreateDateColumn() created_at: Date; @Field() @Column() @UpdateDateColumn() updated_at: Date; }
Notice that we decorated the class with the @ObjectType()
from @nestjs/graphql
. This decorator tells NestJS that the class is an object class. This TypeScript class will then be used to generate our GraphQL CustomerModel
schema.
Note: The
ObjectType
decorator also optionally takes the name of the type being created. It is useful to add this name to the decorator when errors likeError: Schema must contain uniquely named types but contains multiple types named "Item"
are encountered.An alternative solution to this error is deleting the output directory before building and running the app.
Schemas
A schema in GraphQL is the definition of the structure of the data queried in the API. It defines the fields of the data, the types, and also operations that can be performed. GraphQL Schemas are written in the GraphQL Schema Definition Language (SDL).
Using the code-first approach, the schema is generated using TypeScript classes and ObjectType
decorators. The schema generated from our CustomerModel
class above will look like:
// schema.gql type CustomerModel { id: String! name: String! email: String! phone: String! address: String! invoices: [InvoiceModel!] created_at: DateTime! updated_at: DateTime! }
Field
Each property in our CustomerModel
class above is decorated with the @Field()
decorator. Nest requires us to explicitly use the @Field()
decorator in our schema definition classes to provide metadata about each field’s GraphQL type, optionality, and attributes, like being nullable.
A field’s GraphQL type can be either a scalar type or another object type. GraphQL comes with a set of default scalar types out of the box: Int
, String
, ID
, Float
, and Boolean
. The @Field()
decorator accepts an optional type function (e.g., type → Int) and, optionally, an options object.
When the field is an array, we must manually indicate the array type in the @Field()
decorator’s type function. Here is an example that indicates an array of InvoiceModel
s:
@Field(type => [InvoiceModel]) invoices: InvoiceModel[]
Now that we’ve created the CustomerModel
object type, let’s define the InvoiceModel
object type:
// src/invoice/invoice.model.ts import { CustomerModel } from './../customer/customer.model'; import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, JoinColumn, ManyToOne, ChildEntity } from 'typeorm'; import { ObjectType, Field } from '@nestjs/graphql'; export enum Currency { NGN = "NGN", USD = "USD", GBP = "GBP", EUR = " EUR" } export enum PaymentStatus { PAID = "PAID", NOT_PAID = "NOT_PAID", } @ObjectType() export class Item{ @Field() description: string; @Field() rate: number; @Field() quantity: number } @ObjectType() @Entity() export class InvoiceModel { @Field() @PrimaryGeneratedColumn('uuid') id: string; @Field() @Column({ length: 500, nullable: false }) invoiceNo: string; @Field() @Column('text') description: string; @Field(type => CustomerModel) @ManyToOne(type => CustomerModel, customer => customer.invoices) customer: CustomerModel; @Field() @Column({ type: "enum", enum: PaymentStatus, default: PaymentStatus.NOT_PAID }) paymentStatus: PaymentStatus; @Field() @Column({ type: "enum", enum: Currency, default: Currency.USD }) currency: Currency; @Field() @Column() taxRate: number; @Field() @Column() issueDate: string; @Field() @Column() dueDate: string; @Field() @Column('text') note: string; @Field( type => [Item]) @Column({ type: 'jsonb', array: false, default: [], nullable: false, }) items: Item[]; @Column() @Field() taxAmount: number; @Column() @Field() subTotal: number; @Column() @Field() total: string; @Column({ default: 0 }) @Field() amountPaid: number; @Column() @Field() outstandingBalance: number; @Field() @Column() @CreateDateColumn() createdAt: Date; @Field() @Column() @UpdateDateColumn() updatedAt: Date; }
Our InvoiceModel
‘s generated schema will look like this:
// schema.gql type Item { description: String! rate: Float! quantity: Float! } type InvoiceModel { id: String! invoiceNo: String! description: String! customer: CustomerModel! paymentStatus: String! currency: String! taxRate: Float! issueDate: String! dueDate: String! note: String! Items: [Item!]! taxAmount: Float! subTotal: Float! total: String! amountPaid: Float! outstandingBalance: Float! createdAt: DateTime! updatedAt: DateTime! }
GraphQL special object types
We’ve seen how to define object types with Nest. However, there are two special types in GraphQL: Query
and Mutation
. They serve as parents to other object types and define the entry point to other objects. Every GraphQL API has a Query
type and may or may not have a Mutation
type.
The Query
and Mutation
objects are used to make requests to GraphQL APIs. Query
objects are used to make read (i.e., SELECT) requests on GraphQL APIs while Mutation
objects are used to make create, update, and delete requests.
Our invoice API should have a Query
object that returns our API objects. Here is an example:
type Query { customer: CustomerModel invoice: InvoiceModel }
Having created the objects that should exist in our graph, we can now define our resolver class to give our client a way to interact with our API.
In the code-first method, a resolver class both defines resolver functions and generates the Query
type. To create a resolver, we’ll create a class with resolver functions as methods and decorate the class with the @Resolver()
decorator:
// src/customer/customer.resolver.ts import { InvoiceModel } from './../invoice/invoice.model'; import { InvoiceService } from './../invoice/invoice.service'; import { CustomerService } from './customer.service'; import { CustomerModel } from './customer.model'; import { Resolver, Mutation, Args, Query, ResolveField, Parent } from '@nestjs/graphql'; import { Inject } from '@nestjs/common'; @Resolver(of => CustomerModel) export class CustomerResolver { constructor( @Inject(CustomerService) private customerService: CustomerService, @Inject(InvoiceService) private invoiceService: InvoiceService ) { } @Query(returns => CustomerModel) async customer(@Args('id') id: string): Promise<CustomerModel> { return await this.customerService.findOne(id); } @ResolveField(returns => [InvoiceModel]) async invoices(@Parent() customer): Promise<InvoiceModel[]> { const { id } = customer; return this.invoiceService.findByCustomer(id); } @Query(returns => [CustomerModel]) async customers(): Promise<CustomerModel[]> { return await this.customerService.findAll(); } }
In our example above, we created the CustomerResolver
, which defines one query resolver function and one field resolver function. To specify that the method is a query handler, we annotated the method with the @Query()
decorator. We also used @ResolveField()
to annotate the method that resolves the invoices
field of the CustomerModel
. And the @Args()
decorator is used to extract arguments from a request for use in the query handler.
The @Resolver()
decorator accepts an optional argument of
that is used to specify the parent of a field resolver function. Using our example above, @Resolver(of =>CustomerModel)
indicates that our CustomerModel
object is the parent of the field invoices and is passed to the invoices field resolver method.
Our resolver class defined above does not hold the logic needed to fetch and return data from our database. Instead, we abstract that logic into a service class, which our resolver class calls. Here is our customer service class:
// src/customer/customer.service.ts import { Injectable } from '@nestjs/common'; import { CustomerModel } from './customer.model'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CustomerDTO } from './customer.dto'; @Injectable() export class CustomerService { constructor( @InjectRepository(CustomerModel) private customerRepository: Repository<CustomerModel>, ) {} create(details: CustomerDTO): Promise<CustomerModel>{ return this.customerRepository.save(details); } findAll(): Promise<CustomerModel[]> { return this.customerRepository.find(); } findOne(id: string): Promise<CustomerModel> { return this.customerRepository.findOne(id); } }
TypeORM provides Repositories, which are connected to our data entities and used to execute queries on them. You can find more details about TypeORM Repositories here.
Mutations
We’ve walked through how to retrieve data from a GraphQL server, but what about modifying the server-side data? As we discussed earlier, Mutation
methods are used for modifying server-side data in GraphQL.
Technically, a Query
could be implemented to add server-side data. But the common convention is to annotate any method that causes data to be written with the @Mutations()
decorator. Instead, the decorator tells Nest that such a method is for data modification.
Now, let’s add the new createCustomer()
class to our CustomerResolver
resolver class:
@Mutation(returns => CustomerModel) async createCustomer( @Args('name') name: string, @Args('email') email: string, @Args('phone', { nullable: true }) phone: string, @Args('address', { nullable: true }) address: string, ): Promise<CustomerModel> { return await this.customerService.create({ name, email, phone, address }) }
createCustomer()
has been decorated with @Mutations()
to indicate that it modifies or adds new data. If a mutation needs to take an object as an argument, we would need to create a special kind of object called InputType
and then pass it as an argument to the method. To declare an input type, use the @InputType()
decorator:
import { PaymentStatus, Currency, Item } from "./invoice.model"; import { InputType, Field } from "@nestjs/graphql"; @InputType() class ItemDTO{ @Field() description: string; @Field() rate: number; @Field() quantity: number } @InputType() export class CreateInvoiceDTO{ @Field() customer: string; @Field() invoiceNo: string; @Field() paymentStatus: PaymentStatus; @Field() description: string; @Field() currency: Currency; @Field() taxRate: number; @Field() issueDate: Date; @Field() dueDate: Date; @Field() note: string; @Field(type => [ItemDTO]) items: Array<{ description: string; rate: number; quantity: number }>; } @Mutation(returns => InvoiceModel) async createInvoice( @Args('invoice') invoice: CreateInvoiceDTO, ): Promise<InvoiceModel> { return await this.invoiceService.create(invoice) }
Now that we’ve created an entry point to our graph service, we can view our GraphQL API via the playground. The playground is a graphical, interactive, in-browser GraphQL IDE, available by default on the same URL as the GraphQL server itself.
To access the playground, we need to have our GraphQL server running. Run the following command to start the server:
npm start
With the server running, open your web browser and navigate to http://localhost:3000/graphql
to see the playground:
With the GraphQL playground, we can test making requests to our API using the query and mutation objects. Additionally, we can run mutations like createCustomer
below to create new entries of customers:
// Request mutation { createCustomer( address: "Test Address", name: "Customer 1", email: "[email protected]", phone: "00012344" ) { id, name, address, email, phone } } // Result { "data": { "createCustomer": { "id": "0be45472-4257-4e2d-81ab-efb1221eb9f1", "name": "Customer 1", "address": "Test Address", "email": "[email protected]", "phone": "00012344" } } }
And queries like:
// Request query { customer(id: "0be45472-4257-4e2d-81ab-efb1221eb9f1") { id, email } } // Result { "data": { "customer": { "id": "0be45472-4257-4e2d-81ab-efb1221eb9f1", "email": "[email protected]" } } }
As we mentioned earlier, GraphQL gives us the opportunity to specify what data we want returned for both query and mutation requests. You can also test your API by installing the GraphQL Playground desktop app, or API clients like Postman.
GraphQL APIs are popular for providing simplified and efficient communication with the data of a server-side API. Here are some benefits of building and using a GraphQL API:
Note: Removing or renaming existing fields will still be disruptive to existing users but it is less disruptive for users when a GraphQL API is expanded than a REST one.
In this tutorial, we demonstrated how to use the code-first approach to build a GraphQL API with NestJS. You can find the complete version of the sample code shared here on GitHub. To learn more about the schema-first approach and other best practices, check out the Nest documentation.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
5 Replies to "How to build a GraphQL API with NestJS"
got an error when running the code
Error: Schema must contain uniquely named types but contains multiple types named “Item”.
at new GraphQLSchema
Where are the services files?
I am looking for the services either. Otherwise, thank you for this comprehensive article.
You can also try to use this extension which is ontop of nestjs graphql. With this solution you’ll be able to build flexible filters and get rid of n+1 problem https://github.com/Adrinalin4ik/Nestjs-Graphql-Tools
If link can help you with services files.
https://github.com/GodwinEkuma/invoice-app-without-test