Creating a movie review platform involves managing complex data structures and relationships, which makes GraphQL an excellent choice due to its flexibility and efficiency. By using NestJS for the server framework and MongoDB for the database, you can build a scalable and robust backend. In this blog post, we'll guide you through setting up a movie review platform backend with GraphQL, NestJS, and MongoDB.
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.
Setting Up the Project
1. Initialize the NestJS Project
First, create a new NestJS project:
nest new movie-review-platform
cd movie-review-platform
2. Install 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
3. Configure GraphQL and MongoDB
Update the AppModule
to include GraphQL and MongoDB configurations:
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { MoviesModule } from './movies/movies.module';
import { ReviewsModule } from './reviews/reviews.module';
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/movie-review-platform', {
useNewUrlParser: true,
useUnifiedTopology: true,
}),
GraphQLModule.forRoot({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
MoviesModule,
ReviewsModule,
],
})
export class AppModule {}
Defining the Schema and Resolvers
4. Create the Movies and Reviews Modules
Generate modules, services, and resolvers for Movie
and Review
:
nest generate module movies
nest generate service movies
nest generate resolver movies
nest generate module reviews
nest generate service reviews
nest generate resolver reviews
5. Define Mongoose Schemas
Create Mongoose schemas for Movie
and Review
:
movie.schema.ts
import { Schema, Document } from 'mongoose';
export const MovieSchema = new Schema({
title: { type: String, required: true },
director: { type: String, required: true },
releaseDate: { type: Date, required: true },
});
export interface Movie extends Document {
id: string;
title: string;
director: string;
releaseDate: Date;
}
review.schema.ts
import { Schema, Document } from 'mongoose';
import { Movie } from '../../movies/schemas/movie.schema';
export const ReviewSchema = new Schema({
movie: { type: Schema.Types.ObjectId, ref: 'Movie', required: true },
reviewer: { type: String, required: true },
comment: { type: String, required: true },
rating: { type: Number, required: true },
});
export interface Review extends Document {
id: string;
movie: Movie;
reviewer: string;
comment: string;
rating: number;
}
6. Integrate Schemas with Modules
movies.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { MoviesService } from './movies.service';
import { MoviesResolver } from './movies.resolver';
import { MovieSchema } from './schemas/movie.schema';
@Module({
imports: [MongooseModule.forFeature([{ name: 'Movie', schema: MovieSchema }])],
providers: [MoviesService, MoviesResolver],
})
export class MoviesModule {}
reviews.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { ReviewsService } from './reviews.service';
import { ReviewsResolver } from './reviews.resolver';
import { ReviewSchema } from './schemas/review.schema';
import { MoviesModule } from '../movies/movies.module';
@Module({
imports: [
MongooseModule.forFeature([{ name: 'Review', schema: ReviewSchema }]),
MoviesModule,
],
providers: [ReviewsService, ReviewsResolver],
})
export class ReviewsModule {}
7. Implement Services and Resolvers
movies.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Movie } from './schemas/movie.schema';
@Injectable()
export class MoviesService {
constructor(@InjectModel('Movie') private movieModel: Model<Movie>) {}
async findAll(): Promise<Movie[]> {
return this.movieModel.find().exec();
}
async findOne(id: string): Promise<Movie> {
return this.movieModel.findById(id).exec();
}
async create(movie: Movie): Promise<Movie> {
const newMovie = new this.movieModel(movie);
return newMovie.save();
}
async update(id: string, movie: Movie): Promise<Movie> {
return this.movieModel.findByIdAndUpdate(id, movie, { new: true }).exec();
}
async delete(id: string): Promise<Movie> {
return this.movieModel.findByIdAndRemove(id).exec();
}
}
movies.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { MoviesService } from './movies.service';
import { MovieType } from './dto/movie.dto';
import { MovieInput } from './inputs/movie.input';
@Resolver(() => MovieType)
export class MoviesResolver {
constructor(private readonly moviesService: MoviesService) {}
@Query(() => [MovieType])
async movies() {
return this.moviesService.findAll();
}
@Query(() => MovieType)
async movie(@Args('id') id: string) {
return this.moviesService.findOne(id);
}
@Mutation(() => MovieType)
async createMovie(@Args('input') input: MovieInput) {
return this.moviesService.create(input);
}
@Mutation(() => MovieType)
async updateMovie(@Args('id') id: string, @Args('input') input: MovieInput) {
return this.moviesService.update(id, input);
}
@Mutation(() => MovieType)
async deleteMovie(@Args('id') id: string) {
return this.moviesService.delete(id);
}
}
reviews.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Review } from './schemas/review.schema';
@Injectable()
export class ReviewsService {
constructor(@InjectModel('Review') private reviewModel: Model<Review>) {}
async findAll(): Promise<Review[]> {
return this.reviewModel.find().populate('movie').exec();
}
async findOne(id: string): Promise<Review> {
return this.reviewModel.findById(id).populate('movie').exec();
}
async create(review: Review): Promise<Review> {
const newReview = new this.reviewModel(review);
return newReview.save();
}
async update(id: string, review: Review): Promise<Review> {
return this.reviewModel.findByIdAndUpdate(id, review, { new: true }).exec();
}
async delete(id: string): Promise<Review> {
return this.reviewModel.findByIdAndRemove(id).exec();
}
}
reviews.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { ReviewsService } from './reviews.service';
import { ReviewType } from './dto/review.dto';
import { ReviewInput } from './inputs/review.input';
@Resolver(() => ReviewType)
export class ReviewsResolver {
constructor(private readonly reviewsService: ReviewsService) {}
@Query(() => [ReviewType])
async reviews() {
return this.reviewsService.findAll();
}
@Query(() => ReviewType)
async review(@Args('id') id: string) {
return this.reviewsService.findOne(id);
}
@Mutation(() => ReviewType)
async createReview(@Args('input') input: ReviewInput) {
return this.reviewsService.create(input);
}
@Mutation(() => ReviewType)
async updateReview(@Args('id') id: string, @Args('input') input: ReviewInput) {
return this.reviewsService.update(id, input);
}
@Mutation(() => ReviewType)
async deleteReview(@Args('id') id: string) {
return this.reviewsService.delete(id);
}
}
8. Define GraphQL DTOs and Inputs
Create DTOs (Data Transfer Objects) and Input types for GraphQL.
movie.dto.ts
import { ObjectType, Field, ID } from '@nestjs/graphql';
@ObjectType()
export class MovieType {
@Field(() => ID)
id: string;
@Field()
title: string;
@Field()
director: string;
@Field()
releaseDate: Date;
}
review.dto.ts
import { ObjectType, Field, ID } from '@nestjs/graphql';
import { MovieType } from '../../movies/dto/movie.dto';
@ObjectType()
export class ReviewType {
@Field(() => ID)
id: string;
@Field(() => MovieType)
movie: MovieType;
@Field()
reviewer: string;
@Field()
comment: string;
@Field()
rating: number;
}
movie.input.ts
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class MovieInput {
@Field()
title: string;
@Field()
director: string;
@Field()
releaseDate: Date;
}
review.input.ts
import { InputType, Field, ID } from '@nestjs/graphql';
@InputType()
export class ReviewInput {
@Field(() => ID)
movie: string;
@Field()
reviewer: string;
@Field()
comment: string;
@Field()
rating: number;
}
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 {
movies {
id
title
director
releaseDate
}
reviews {
id
movie {
title
}
reviewer
comment
rating
}
}
Creating Data
mutation {
createMovie(input: { title: "Inception", director: "Christopher Nolan", releaseDate: "2010-07-16" }) {
id
title
}
createReview(input: { movie: "MOVIE_ID", reviewer: "John Doe", comment: "Great movie!", rating: 5 }) {
id
reviewer
comment
}
}
Replace "MOVIE_ID"
with the actual movie ID obtained from the createMovie
mutation.
Conclusion
By following this guide, you've built a movie review platform backend API using GraphQL, NestJS, and MongoDB. This setup allows for efficient and flexible data querying and management, making it an ideal choice for complex applications. Continue exploring NestJS and GraphQL to add more features and refine your application. Happy coding!