NestJS, a progressive Node.js framework, leverages TypeScript to build scalable and maintainable server-side applications. Combined with GraphQL and MongoDB, NestJS provides a powerful stack for building modern APIs. This article offers a detailed guide on building GraphQL APIs with NestJS and MongoDB, covering setup, schema definition, CRUD operations, and advanced features.
Introduction to the Stack
What is GraphQL?
GraphQL is a query language for APIs and a runtime for executing those queries. It allows clients to request specific data structures, reducing over-fetching and under-fetching compared to traditional REST APIs.
What is NestJS?
NestJS is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications. It uses TypeScript and is inspired by Angular, providing a modular architecture and strong typing.
Why MongoDB?
MongoDB is a NoSQL database known for its flexibility, scalability, and ease of use. It stores data in JSON-like documents, making it a good fit for applications with varied or unstructured data.
Setting Up the Project
Prerequisites
Ensure you have the following installed:
Node.js: Download and install from nodejs.org.
NestJS CLI: Install using npm:
npm install -g @nestjs/cli
.MongoDB: Install and run MongoDB from mongodb.com.
Initializing the NestJS Project
Create a new NestJS project:
nest new graphql-nestjs
cd graphql-nestjs
Installing Required Packages
Add the necessary packages for GraphQL, MongoDB, and Mongoose (an ODM for MongoDB):
npm install @nestjs/graphql @nestjs/mongoose mongoose graphql-tools graphql apollo-server-express
Configuring MongoDB
Create a .env
file in the root of your project to store the MongoDB connection string:
MONGODB_URI=mongodb://localhost:27017/graphql-nestjs
Setting Up Mongoose
Update the AppModule
to include Mongoose configuration:
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
@Module({
imports: [
MongooseModule.forRoot(process.env.MONGODB_URI),
GraphQLModule.forRoot({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
],
})
export class AppModule {}
Defining the Schema and Resolvers
Creating the Book and Author Modules
Generate modules, services, and resolvers for Book
and Author
:
nest generate module books
nest generate service books
nest generate resolver books
nest generate module authors
nest generate service authors
nest generate resolver authors
Defining Mongoose Schemas
Create Mongoose schemas for Book
and Author
:
book.schema.ts
import { Schema, Document } from 'mongoose';
export const BookSchema = new Schema({
title: String,
authorId: { type: Schema.Types.ObjectId, ref: 'Author' },
});
export interface Book extends Document {
id: string;
title: string;
authorId: string;
}
author.schema.ts
import { Schema, Document } from 'mongoose';
export const AuthorSchema = new Schema({
name: String,
});
export interface Author extends Document {
id: string;
name: string;
}
Integrating Schemas with Modules
books.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { BooksService } from './books.service';
import { BooksResolver } from './books.resolver';
import { BookSchema } from './schemas/book.schema';
@Module({
imports: [MongooseModule.forFeature([{ name: 'Book', schema: BookSchema }])],
providers: [BooksService, BooksResolver],
})
export class BooksModule {}
authors.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { AuthorsService } from './authors.service';
import { AuthorsResolver } from './authors.resolver';
import { AuthorSchema } from './schemas/author.schema';
@Module({
imports: [MongooseModule.forFeature([{ name: 'Author', schema: AuthorSchema }])],
providers: [AuthorsService, AuthorsResolver],
})
export class AuthorsModule {}
Implementing Resolvers and Services
books.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Book } from './schemas/book.schema';
@Injectable()
export class BooksService {
constructor(@InjectModel('Book') private bookModel: Model<Book>) {}
async findAll(): Promise<Book[]> {
return this.bookModel.find().exec();
}
async findOne(id: string): Promise<Book> {
return this.bookModel.findById(id).exec();
}
async create(book: Book): Promise<Book> {
const newBook = new this.bookModel(book);
return newBook.save();
}
async update(id: string, book: Book): Promise<Book> {
return this.bookModel.findByIdAndUpdate(id, book, { new: true }).exec();
}
async delete(id: string): Promise<Book> {
return this.bookModel.findByIdAndDelete(id).exec();
}
}
books.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { BooksService } from './books.service';
import { BookType } from './dto/book.dto';
import { BookInput } from './inputs/book.input';
@Resolver(() => BookType)
export class BooksResolver {
constructor(private readonly booksService: BooksService) {}
@Query(() => [BookType])
async books() {
return this.booksService.findAll();
}
@Query(() => BookType)
async book(@Args('id') id: string) {
return this.booksService.findOne(id);
}
@Mutation(() => BookType)
async createBook(@Args('input') input: BookInput) {
return this.booksService.create(input);
}
@Mutation(() => BookType)
async updateBook(@Args('id') id: string, @Args('input') input: BookInput) {
return this.booksService.update(id, input);
}
@Mutation(() => BookType)
async deleteBook(@Args('id') id: string) {
return this.booksService.delete(id);
}
}
authors.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Author } from './schemas/author.schema';
@Injectable()
export class AuthorsService {
constructor(@InjectModel('Author') private authorModel: Model<Author>) {}
async findAll(): Promise<Author[]> {
return this.authorModel.find().exec();
}
async findOne(id: string): Promise<Author> {
return this.authorModel.findById(id).exec();
}
async create(author: Author): Promise<Author> {
const newAuthor = new this.authorModel(author);
return newAuthor.save();
}
async update(id: string, author: Author): Promise<Author> {
return this.authorModel.findByIdAndUpdate(id, author, { new: true }).exec();
}
async delete(id: string): Promise<Author> {
return this.authorModel.findByIdAndDelete(id).exec();
}
}
authors.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { AuthorsService } from './authors.service';
import { AuthorType } from './dto/author.dto';
import { AuthorInput } from './inputs/author.input';
@Resolver(() => AuthorType)
export class AuthorsResolver {
constructor(private readonly authorsService: AuthorsService) {}
@Query(() => [AuthorType])
async authors() {
return this.authorsService.findAll();
}
@Query(() => AuthorType)
async author(@Args('id') id: string) {
return this.authorsService.findOne(id);
}
@Mutation(() => AuthorType)
async createAuthor(@Args('input') input: AuthorInput) {
return this.authorsService.create(input);
}
@Mutation(() => AuthorType)
async updateAuthor(@Args('id') id: string, @Args('input') input: AuthorInput) {
return this.authorsService.update(id, input);
}
@Mutation(() => AuthorType)
async deleteAuthor(@Args('id') id: string) {
return this.authorsService.delete(id);
}
}
Defining GraphQL DTOs and Inputs
Create DTOs (Data Transfer Objects) and Input types for GraphQL.
book.dto.ts
import { ObjectType, Field, ID } from '@nestjs/graphql';
import { AuthorType } from '../../authors/dto/author.dto';
@ObjectType()
export class BookType {
@Field(() => ID)
id: string;
@Field()
title: string;
@Field(() => AuthorType)
author: AuthorType;
}
author.dto.ts
import { ObjectType, Field, ID } from '@nestjs/graphql';
@ObjectType()
export class AuthorType {
@Field(() => ID)
id: string;
@Field()
name: string;
}
book.input.ts
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class BookInput {
@Field()
title: string;
@Field()
authorId: string;
}
author.input.ts
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class AuthorInput {
@Field()
name: string;
}
Running the Application
Ensure MongoDB is running, and start your NestJS application:
npm run start:dev
Open http://localhost:3000/graphql
in your browser. You should see the GraphQL Playground, where you can run queries and mutations.
Example Queries and Mutations
Fetching Data
query {
books {
id
title
author {
id
name
}
}
authors {
id
name
}
}
Creating Data
mutation {
createAuthor(input: { name: "George R.R. Martin" }) {
id
name
}
}
mutation {
createBook(input: { title: "A Game of Thrones", authorId: "author-id-here" }) {
id
title
}
}
Conclusion
By combining NestJS, GraphQL, and MongoDB, you can create powerful, scalable, and flexible APIs. This guide covered the setup, schema definition, CRUD operations, and advanced features of building a GraphQL API with NestJS and MongoDB. With these tools, you can efficiently develop modern applications that meet the needs of today's complex data requirements.