Building a Movie Review Platform Backend API with GraphQL, NestJS, and MongoDB

Building a Movie Review Platform Backend API with GraphQL, NestJS, and MongoDB

·

6 min read

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!