Now that you have read data from the database, you are now ready to write data to the database.
In this challenge, you will rewrite the register()
method in the AuthService
to do the following:
Registering Users
A key piece of functionality that the application should provide is for new users to be able to register themselves with the site. This functionality is already built into the front end, but at the moment the credentials are hard coded in the API. This might be fine for demo purposes, but limiting the number of users to one is bad for Neoflix’s bottom line.
The dummy register logic is already written into the register()
method of the AuthService
at src/service/auth.service.js
. As we can see from the snippet below, at the moment it will only accept an email address of graphacademy@neo4j.com
.
async register(email, plainPassword, name) {
const encrypted = await hash(plainPassword, parseInt(SALT_ROUNDS))
// TODO: Handle Unique constraints in the database
if (email !== 'graphacademy@neo4j.com') {
throw new ValidationError(`An account already exists with the email address ${email}`, {
email: 'Email address taken'
})
}
// TODO: Save user
const { password, ...safeProperties } = user
return {
...safeProperties,
token: jwt.sign(this.userToClaims(safeProperties), JWT_SECRET),
}
}
From the last line, you can see that an additional token
property is added to the return.
This represents the JWT token required to authenticate the user on any future requests.
This token is generated further down within this class.
You will replace these TODO
comments with working code to complete the challenge.
Challenge: Implementing Write Transactions
You will follow similar steps to the previous challenge, with the one change that the Cypher statement will be executed within a Write Transaction.
To do so, you will need to call the executeWrite()
method on the session
object with a function to represent unit of work.
Here are the steps to complete the challenge.
Opensrc/services/auth.service.js
→
Open a new Session
First, open a new session:
// Open a new session
const session = this.driver.session()
Execute a Cypher statement within a new Write Transaction
Next, within that session, run the executeWrite()
method with two arguments:
-
A Cypher statement to
CREATE
a new:User
node in the database, with parameters passed to the function prefixed with a dollar sign ($
). -
An object containing the values relating to the parameters prefixed with
$
in the Cypher statement.-
email
- provided as the first parameter to the.register()
method -
encrypted
- The encrypted password -
name
- The name as provided as the third parameter to the.register()
method
-
// Create the User node in a write transaction
const res = await session.executeWrite(
tx => tx.run(
`
CREATE (u:User {
userId: randomUuid(),
email: $email,
password: $encrypted,
name: $name
})
RETURN u
`,
{ email, encrypted, name }
)
)
Extract the User from the Result
The Cypher statement executed above returns the newly-created :User
node as u
.
As this query creates a single node, it will only ever return one result, so the u
value may be taken from the first row.
// Extract the user from the result
const [ first ] = res.records
const node = first.get('u')
const { password, ...safeProperties } = node.properties
The final line uses the Destructuring assignment and Spread syntax (…
) to extract a set of safe properties that can be used in the JWT token.
Close the Session
Before the user information is returned, make sure that the session is closed.
// Close the session
await session.close()
Return the Results
The return statement has already been written, so this can be left as it is.
return {
...safeProperties,
token: jwt.sign(this.userToClaims(safeProperties), JWT_SECRET),
}
Working Solution
Click here to reveal the fully-implemented register()
method.
async register(email, plainPassword, name) {
const encrypted = await hash(plainPassword, parseInt(SALT_ROUNDS))
// TODO: Handle Unique constraints in the database
if (email !== 'graphacademy@neo4j.com') {
throw new ValidationError(`An account already exists with the email address ${email}`, {
email: 'Email address taken'
})
}
// Open a new session
const session = this.driver.session()
// Create the User node in a write transaction
const res = await session.executeWrite(
tx => tx.run(
`
CREATE (u:User {
userId: randomUuid(),
email: $email,
password: $encrypted,
name: $name
})
RETURN u
`,
{ email, encrypted, name }
)
)
// Extract the user from the result
const [ first ] = res.records
const node = first.get('u')
const { password, ...safeProperties } = node.properties
// Close the session
await session.close()
return {
...safeProperties,
token: jwt.sign(this.userToClaims(safeProperties), JWT_SECRET),
}
}
Testing
To test that this functionality has been correctly implemented, run the following code in a new terminal session:
npm run test 03
The test file is located at test/challenges/03-registering-a-user.spec.js
.
Are you stuck? Click here for help
If you get stuck, you can see a working solution by checking out the 03-registering-a-user
branch by running:
git checkout 03-registering-a-user
You may have to commit or stash your changes before checking out this branch. You can also click here to expand the Support pane.
Verifying the Test
Here is where things get interesting.
If you have completed the course to this point, you should have a project that connects to the Neo4j Sandbox instance.
If the test above has succeeded, there should be a :User
node in the sandbox with the email address graphacademy@neo4j.com
, name Graph Academy
and an encrypted password.
Hint
You can run the following query to check for the user within the database.
If the shouldVerify
value returns true, the verification should be successful.
MATCH (u:User {email: 'graphacademy@neo4j.com'})
RETURN u.email, u.name, u.password,
(u.email = 'graphacademy@neo4j.com' AND u.name = 'Graph Academy' AND u.password <> 'letmein') AS shouldVerify
Solution
The following statement will mimic the behaviour of the test, merging a new :User
node with the email address graphacademy@neo4j.com
and assigning a random UUID value to the .userId
property.
MERGE (u:User {email: "graphacademy@neo4j.com"})
SET u.userId = randomUuid(),
u.createdAt = datetime(),
u.authenticatedAt = datetime(),
u.password = apoc.util.md5('password')
Once you have run this statement, click Try again…* to complete the challenge.
Lesson Summary
In this Challenge, you wrote the code to create a new User node to Neo4j.
We still have a TODO
comment in this method for handling unique constraint violations in the database, so let’s learn about that in the next lesson.