Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Adding Users to the Node.js / React.js Neo4j Movie App

DZone's Guide to

Adding Users to the Node.js / React.js Neo4j Movie App

Using the Neo4j Movie App Template, we wil ltake a look at how to leverage it as a foundation for your next Node.js or React.js project, including how to create users that can log in and interact with the web application's data.

· Web Dev Zone
Free Resource

Tips, tricks and tools for creating your own data-driven app, brought to you in partnership with Qlik.

Introduction

The Neo4j Movie App Template provides an easy-to-use foundation for your next Neo4j project or experiment using either Node.js or React.js. This article will walk through the creation of users that can log in and interact with the web app’s data.

In the Neo4j Movie App Template example, these users will be able to log in and out, rate movies, and receive movie recommendations.

The User Model

Aside from creating themselves and authenticating with the app, Users (blue) can rate Movies (yellow) with the :RATED relationship, illustrated in the graph data model below.

Learn how to add users to the Node.js / React.js example Neo4j Movie App
User Properties
  • password: The hashed version of the user’s chosen password
  • api_key: The user’s API key, which the user uses to authenticate requests
  • id: The user’s unique ID
  • username: The user’s chosen username
:RATED Properties

rating: an integer rating between 1 and 5, with 5 being love it and 1 being hate it.

My Rated Movie in the Neo4j Movie App

Users Can Create Accounts

Before a User can rate a Movie, the the user has to exist – someone has to sign up for an account. Signup will create a node in the database with a User label along with properties necessary for logging in and maintaining a session.

Create a new user account in the Neo4j Movie App

Figure 1. web/src/pages/Signup.jsx

The registration endpoint is located at /api/v0/register. The app submits a request to the register endpoint when a user fills out the “Create an Account” form and taps “Create Account”.

Assuming you have the API running, you can test requests either by using the interactive docs at 3000/docs/, or by using cURL.

Use Case: Create New User

Request

curl -X POST 
--header 'Content-Type: application/json' 
--header 'Accept: application/json' 
-d '{ "username": "Mary Jane", "password": "SuperPassword"}' 'http://localhost:3000/api/v0/register'

Response

{
   "id":"e1e157a2-1fb5-416a-b819-eb75c480dfc6",
   "username":"Mary333 Jane",
   "avatar":{
      "full_size":"https://www.gravatar.com/avatar/b2a02b21db2222c472fc23ff78804687?d=retro"
   }
}

Use Case: Try to Create a New User, But Username is Already Taken

Request

curl -X POST 
--header 'Content-Type: application/json' 
--header 'Accept: application/json' 
-d '{ "username": "Mary Jane", "password": "SuperPassword"}' 'http://localhost:3000/api/v0/register'

Response

{
   "username":"username already in use"
}

User registration logic is implemented in /api/models/users.js. Here’s the JavaScript:

var register = function(session, username, password) {
    return session.run('MATCH (user:User {username: {username}}) RETURN user', {
            username: username
        })
        .then(results => {
            if (!_.isEmpty(results.records)) {
                throw {
                    username: 'username already in use',
                    status: 400
                }
            }
            else {
                return session.run('CREATE (user:User {id: {id}, username: {username},
                       password: {password}, api_key: {api_key}}) RETURN user', {
                    id: uuid.v4(),
                    username: username,
                    password: hashPassword(username, password),
                    api_key: randomstring.generate({
                        length: 20,
                        charset: 'hex'
                    })
                }).then(results => {
                    return new User(results.records[0].get('user'));
                })
            }
        });
};

Users Can Log in

Now that users are able to register for an account, we can define the view that allows them to login to the site and start a session.

User login on the Neo4j Movies AppFigure 2. /web/src/pages/Login.jsx

The registration endpoint is located at /api/v0/login. The app submits a request to the login endpoint when a user fills a username and password and taps “Create Account”.

Assuming you have the API running, you can test requests either by using the interactive docs at 3000/docs/, or by using cURL.

Use Case: Login

Request

curl -X POST 
--header 'Content-Type: application/json' 
--header 'Accept: application/json' 
-d '{"username": "Mary Jane", "password": "SuperPassword"}' 'http://localhost:3000/api/v0/login'

Response

{
"token":"5a85862fb28a316ea6a1"
}

Use Case: Wrong Password

Request

curl -X POST 
--header 'Content-Type: application/json' 
--header 'Accept: application/json' 
-d '{ "username": "Mary Jane", "password": "SuperPassword"}' 'http://localhost:3000/api/v0/register'

Response

{
   "username":"username already in use"
}

Use Case: See Myself

Request

curl -X GET 
--header 'Accept: application/json' 
--header 'Authorization: Token 5a85862fb28a316ea6a1' 'http://localhost:3000/api/v0/users/me'

Response

{
  "id": "94a604f7-3eab-4f28-88ab-12704c228936",
  "username": "Mary Jane",
  "avatar": {
    "full_size": "https://www.gravatar.com/avatar/c2eab5611cabda1c87463d7d24d98026?d=retro"
  }
}

You can take a look at the implementation in /api/models/users.js:

var me = function(session, apiKey) {
    return session.run('MATCH (user:User {api_key: {api_key}}) RETURN user', {
            api_key: apiKey
        })
        .then(results => {
            if (_.isEmpty(results.records)) {
                throw {
                    message: 'invalid authorization key',
                    status: 401
                };
            }
            return new User(results.records[0].get('user'));
        });
};
var login = function(session, username, password) {
    return session.run('MATCH (user:User {username: {username}}) RETURN user', {
            username: username
        })
        .then(results => {
            if (_.isEmpty(results.records)) {
                throw {
                    username: 'username does not exist',
                    status: 400
                }
            }
            else {
                var dbUser = _.get(results.records[0].get('user'), 'properties');
                if (dbUser.password != hashPassword(username, password)) {
                    throw {
                        password: 'wrong password',
                        status: 400
                    }
                }
                return {
                    token: _.get(dbUser, 'api_key')
                };
            }
        });
};

The code here should look similar to /register. There is a similar form to fill out, where a user types in their username and password.

With the given username, a User is initialized. The password they filled out in the form is verified against the hashed password that was retrieved from the corresponding :User node in the database.

If the verification is successful it will return a token. The user is then directed to an authentication page, from which they can navigate through the app, view their user profile and rate movies. Below is a rather empty user profile for a freshly created user:

An empty user profile in the Neo4j Movies App

Figure 3. /web/src/pages/Profile.jsx

Users Can Rate Movies

Once a user has logged in and navigated to a page that displays movies, the user can select a star rating for the movie or remove the rating of a movie he or she has already rated.

My Rated Movie in the Neo4j Movie App

The user should be able to access their previous ratings (and the movies that were rated) both on their user profile and the movie detail page in question.

Use Case: Rate a Movie

Request

curl -X POST 
--header 'Content-Type: application/json' 
--header 'Accept: application/json' 
--header 'Authorization: Token 5a85862fb28a316ea6a1' 
-d '{"rating":4}' 'http://localhost:3000/api/v0/movies/683/rate'

Response

{}

Use Case: See All of My Ratings

Request

curl -X GET 
--header 'Accept: application/json' 
--header 'Authorization: Token 5a85862fb28a316ea6a1' 'http://localhost:3000/api/v0/movies/rated'

Response

[
  {
    "summary": "Six months after the events depicted in The Matrix, ...",
    "duration": 138,
    "rated": "R",
    "tagline": "Free your mind.",
    "id": 28,
    "title": "The Matrix Reloaded",
    "poster_image": "http://image.tmdb.org/t/p/w185/ezIurBz2fdUc68d98Fp9dRf5ihv.jpg",
    "my_rating": 4
  },
  {
    "summary": "Thomas A. Anderson is a man living two lives....",
    "duration": 136,
    "rated": "R",
    "tagline": "Welcome to the Real World.",
    "id": 1,
    "title": "The Matrix",
    "poster_image": "http://image.tmdb.org/t/p/w185/gynBNzwyaHKtXqlEKKLioNkjKgN.jpg",
    "my_rating": 4
  }
]

Use Case: See My Rating on a Particular Movie

Request

curl -X GET 
--header 'Accept: application/json' 
--header 'Authorization: Token 5a85862fb28a316ea6a1' 'http://localhost:3000/api/v0/movies/1'

Response

{
   "summary":"Thomas A. Anderson is a man living two lives. By day he is an average
   computer programmer and by night a malevolent hacker known as Neo, who finds
   himself targeted by the police when he is contacted by Morpheus, a legendary 
   computer hacker, who reveals the shocking truth about our reality.",
   "duration":136,
   "rated":"R",
   "tagline":"Welcome to the Real World.",
   "id":1,
   "title":"The Matrix",
"poster_image":"http://image.tmdb.org/t/p/w185/gynBNzwyaHKtXqlEKKLioNkjKgN.jpg",
   "my_rating":4,
   "directors":[...],
   "genres":[...],
   "producers":[...],
   "writers":[...],
   "actors":[...],
   "related":[...],
   "keywords":[...]
}

Users Can Be Recommended Movies Based on Their Recommendations

When a user visits their own profile, the user will see movie recommendations. There are many ways to build a recommendation engine, and you might want to use one or a combination of the methods below to build the appropriate recommendation system for your particular use case.

In the movie template, you can find the recommendation endpoint at movies/recommended.

User-Centric, User-Based Recommendations

Here’s an example Cypher query for a user-centric recommendation: 
MATCH (me:User {username:'Sherman'})-[my:RATED]->(m:Movie)
MATCH (other:User)-[their:RATED]->(m)
WHERE me <> other
AND abs(my.rating - their.rating) < 2
WITH other,m
MATCH (other)-[otherRating:RATED]->(movie:Movie)
WHERE movie <> m
WITH avg(otherRating.rating) AS avgRating, movie
RETURN movie
ORDER BY avgRating desc
LIMIT 25

Movie-Centric, Keyword-Based Recommendations

Newer movies will have few or no ratings, so they will never be recommended to users if the application uses users’ rating-based recommendations.

Since movies have keywords, the application can recommend movies with similar keywords for a particular movie. This case is useful when the user has made few or no ratings.

For example, site visitors interested in movies like Elysium will likely be interested in movies with similar keywords as Elysium.

MATCH (m:Movie {title:'Elysium'})
MATCH (m)-[:HAS_KEYWORD]->(k:Keyword)
MATCH (movie:Movie)-[r:HAS_KEYWORD]->(k)
WHERE m <> movie
WITH movie, count(DISTINCT r) AS commonKeywords
RETURN movie
ORDER BY commonKeywords DESC
LIMIT 25

User-Centric, Keyword-Based Recommendations

Users with established tastes may be interested in finding movies with similar characteristics as his or her highly-rated movies, while not necessarily caring about whether another user has or hasn’t already rated the movie. For example, Sherman has seen many movies and is looking for new movies similar to the ones he has already watched.

MATCH (u:User {username:'Sherman'})-[:RATED]->(m:Movie)
MATCH (m)-[:HAS_KEYWORD]->(k:Keyword)
MATCH (movie:Movie)-[r:HAS_KEYWORD]->(k)
WHERE m <> movie
WITH movie, count(DISTINCT r) AS commonKeywords
RETURN movie
ORDER BY commonKeywords DESC
LIMIT 25

Next Steps

Want to learn more about what you can do with graph databases like Neo4j? Click below to get your free copy the O’Reilly Graph Databases book and discover how to harness the power of graph technology.

Download My Free Copy

Explore data-driven apps with less coding and query writing, brought to you in partnership with Qlik.

Topics:
neo4j ,node.js ,react.js ,graph database ,web dev

Published at DZone with permission of Cristina Escalante, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}