IMDb(Internet Movie Database) is the world's most popular and authoritative source for information on movies, TV shows and celebrities. This application is an IMDB clone with basic account authentication and movie recommendation functionality. You will learn the power of RedisGraph and NodeJS to build a simple movie database.
docker run -d -p 6379:6379 redis/redis-stack
Ensure that Docker container is up and running:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fd5ef30f025a redis/redis-stack "redis-server --load…" 2 hours ago Up 2 hours 0.0.0.0:6379->6379/tcp nervous_buck
docker run -d -v redisinsight:/db -p 8001:8001 redislabs/redisinsight:latest
Ensure that Docker container is up and runnig
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
264db1706dcc redislabs/redisinsight:latest "bash ./docker-entry…" About an hour ago Up About an hour 0.0.0.0:8001->8001/tcp angry_shirley
fd5ef30f025a redis/redis-stack "redis-server --load…" 2 hours ago Up 2 hours 0.0.0.0:6379->6379/tcp nervous_buck
git clone https://github.com/redis-developer/basic-redisgraph-movie-demo-app-nodejs
Copy .env.sample
to .env
and add the following details:
REDIS_ENDPOINT_URL = "Redis server URI"
REDIS_PASSWORD = "Password to the server"
npm install
node app.js
cd client
yarn install
yarn start
Open http://IP:3000 to access the movie app
Enter the details to create a new account:
GRAPH.QUERY "MovieApp" "MATCH (director:Director {tmdbId: \"4945\"})-[:DIRECTED]->(movie:Movie) RETURN DISTINCT movie,director"
Run the below query under RedisGraph to find the author acted in a movie
GRAPH.QUERY "MovieApp" "MATCH (actor:Actor {tmdbId: \"8537\"})-[:ACTED_IN_MOVIE]->(movie:Movie) RETURN DISTINCT movie,actor"
CREATE (user:User {id: 32,
username: "user", password: "hashed_password", api_key: "525d40da10be8ec75480"})
RETURN user
MATCH (user:User {username: "user"}) RETURN user
The app consumes the data provided by the Express API and presents it through some views to the end user, including:
The home page shows the genres and a brief listing of movies associated with them.
create (g:Genre{name:"Adventure"})
create (m:Movie {
url: "https://themoviedb.org/movie/862",
id:232,
languages:["English"],
title:"Toy Story",
countries:["USA"],
budget:30000000,
duration:81,
imdbId:"0114709",
imdbRating:8.3,
imdbVotes:591836,
movieId:42,
plot:"...",
poster:"https://image.tmd...",
poster_image:"https://image.tmdb.or...",
released:"1995-11-22",
revenue:373554033,
runtime:$runtime,
tagline:"A cowboy doll is profoundly t...",
tmdbId:"8844",
year:"1995"})
MATCH (g:Genre), (m:Movie)
WHERE g.name = "Adventure" AND m.title = "Toy Story"
CREATE (m)-[:IN_GENRE]->(g)
MATCH (genre:Genre) RETURN genre
MATCH (movie:Movie)-[:IN_GENRE]->(genre)
WHERE toLower(genre.name) = toLower("Film-Noir") OR id(genre) = toInteger("Film-Noir")
RETURN movie
const getByGenre = function (session, genreId) {
const query = [
'MATCH (movie:Movie)-[:IN_GENRE]->(genre)',
'WHERE toLower(genre.name) = toLower($genreId) OR id(genre) = toInteger($genreId)',
'RETURN movie',
].join('\n');
return session
.query(query, {
genreId,
})
.then((result) => manyMovies(result));
};
To be able to rate movies a user needs to be logged in: for that a basic JWT-based authentication system is implemented, where user details are stored in the RedisGraph for persistence.
CREATE (user:User {id: 32,
username: "user", password: "hashed_password", api_key: "525d40da10be8ec75480"})
RETURN user
MATCH (user:User {username: "user"}) RETURN user
const me = function (session, apiKey) {
return session
.query('MATCH (user:User {api_key: $api_key}) RETURN user', {
api_key: apiKey,
})
.then((foundedUser) => {
if (!foundedUser.hasNext()) {
throw {message: 'invalid authorization key', status: 401};
}
while (foundedUser.hasNext()) {
const record = foundedUser.next();
return new User(record.get('user'));
}
});
};
On this page a user can rate the film and view the Actors/directors who participated in the production of the film.
MATCH (m:Movie) WHERE m.title="Jumanji" CREATE (a:Actor :Person{
bio:"Sample...",
bornIn:"Denver, Colorado, USA",
imdbId:"0000245",
name:"Robin Williams",
poster:"https://image.tmdb.org/t/p/w440_and_...",
tmdbId:"2157",
url:"https://themoviedb.org/person/2157"})-[r:ACTED_IN_MOVIE
{role: "Alan Parrish"}]->(m)
MATCH (m:Movie) WHERE m.title="Dead Presidents" CREATE (d:Director :Person{
bio: "From Wikipedia, the free e...",
bornIn: "Detroit, Michigan, USA",
imdbId: "0400436",
name: "Albert Hughes",
tmdbId: "11447",
url: "https://themoviedb.org/person/11447"})-[r:DIRECTED]->(m)
MATCH (movie:Movie {tmdbId: $movieId})
OPTIONAL MATCH (movie)<-[my_rated:RATED]-(me:User {id: "e1e3991f-fe81-439e-a507-aa0647bc0b88"})
OPTIONAL MATCH (movie)<-[r:ACTED_IN_MOVIE]-(a:Actor)
OPTIONAL MATCH (movie)-[:IN_GENRE]->(genre:Genre)
OPTIONAL MATCH (movie)<-[:DIRECTED]-(d:Director)
WITH DISTINCT movie, my_rated, genre, d, a, r
RETURN DISTINCT movie,
collect(DISTINCT d) AS directors,
collect(DISTINCT a) AS actors,
collect(DISTINCT genre) AS genres
const getById = function (session, movieId, userId) {
if (!userId) throw {message: 'invalid authorization key', status: 401};
const query = [
'MATCH (movie:Movie {tmdbId: $movieId})\n' +
' OPTIONAL MATCH (movie)<-[my_rated:RATED]-(me:User {id: $userId})\n' +
' OPTIONAL MATCH (movie)<-[r:ACTED_IN_MOVIE]-(a:Actor)\n' +
' OPTIONAL MATCH (movie)-[:IN_GENRE]->(genre:Genre)\n' +
' OPTIONAL MATCH (movie)<-[:DIRECTED]-(d:Director)\n' +
' WITH DISTINCT movie, my_rated, genre, d, a, r\n' +
' RETURN DISTINCT movie,\n' +
' collect(DISTINCT d) AS directors,\n' +
' collect(DISTINCT a) AS actors,\n' +
' collect(DISTINCT genre) AS genres',
].join(' ');
return session
.query(query, {
movieId: movieId.toString(),
userId: userId.toString(),
})
.then((result) => {
if (result.hasNext()) {
return _singleMovieWithDetails(result.next());
}
throw {message: 'movie not found', status: 404};
});
};
MATCH (actor:Actor {tmdbId: "8537"})-[:ACTED_IN_MOVIE]->(movie:Movie)
RETURN DISTINCT movie,actor
MATCH (director:Director {tmdbId: "4945"})-[:DIRECTED]->(movie:Movie)
RETURN DISTINCT movie,director
const getByDirector = function (session, personId) {
const query = [
'MATCH (director:Director {tmdbId: $personId})-[:DIRECTED]->(movie:Movie)',
'RETURN DISTINCT movie,director',
].join('\n');
return session
.query(query, {
personId,
})
.then((result) => manyMovies(result));
};
MATCH (u:User {id: 42}),(m:Movie {tmdbId: 231})
MERGE (u)-[r:RATED]->(m)
SET r.rating = "7"
RETURN m
MATCH (:User {id: "d6b31131-f203-4d5e-b1ff-d13ebc06934d"})-[rated:RATED]->(movie:Movie)
RETURN DISTINCT movie, rated.rating as my_rating
const getRatedByUser = function (session, userId) {
return session
.query(
'MATCH (:User {id: $userId})-[rated:RATED]->(movie:Movie) \
RETURN DISTINCT movie, rated.rating as my_rating',
{userId},
)
.then((result) =>
result._results.map((r) => new Movie(r.get('movie'), r.get('my_rating'))),
);
};
id, bio, born , bornIn, imdbId, name, poster, tmdbId, url
id, name
id, born, bornIn, imdbId, name, tmdbId, url
id, username, password, api_key
id, url, languages, countries, budget, duration, imdbId, imdbRating, indbVotes, movieId, plot, poster, poster_image, released, revenue, runtime, tagline, tmdbId, year
RATED
->MovieDIRECTED
->MovieACTED_IN_MOVIE
->MovieIN_GENRE
->GenreRedis is phasing out RedisGraph. This blog post explains the motivation behind this decision and the implications for existing Redis customers and community members.
End of support is scheduled for January 31, 2025.