How to Integrate MongoDB Realm with React: Part 2

Shahed Nasser
Level Up Coding
Published in
6 min readMay 29, 2021

--

This article was originally published on my personal blog.

Please participate in this survey to voice your opinion as a developer for an upcoming article!

In the first part of this tutorial, we went over how to set up a MongoDB Realm app with sample data, generate the schema, create and restrict roles, and then integrated it with a React app, implementing an authentication system.

In this tutorial, we’ll go over how to ensure only logged-in users by email and password can add reviews, and we’ll test adding reviews by users that are not logged in to see MongoDB Realm Roles and data access rules in action.

You can find the code for this tutorial here.

Add Reviews Form

We’ll start with the add reviews form. This form will be accessed through a link in the card of the restaurant that’s showing on the home page. The restaurant ID will be passed as a URL parameter, then whatever review the user enters will be saved to that restaurant. At first, we’ll allow all users to access the page to test the difference between the logged-in user and anonymous user, then we’ll restrict access to the page to logged-in users only.

Create the component src/pages/AddReview.js with the following content:

function AddReview() {

}

export default AddReview

Just like in the Authentication form, we’ll useyupfor validation andformikto make creating a form easier:

const reviewSchema = yup.object().shape({
review: yup.number().required()
})

function AddReview() {
const [loading, setLoading] = useState(false)

function submitHandler (values) {
//TODO add review
}

return (
<Formik
initialValues={{
review: 0
}}

validationSchema={reviewSchema}
onSubmit={submitHandler}>

{({errors, touched, handleSubmit, values, handleChange}) => (
<Form noValidate onSubmit={handleSubmit}>
{loading && <Loading />}
{!loading && (<div>
<h1>Submit Review</h1>
<Form.Row>
<Form.Label>Review Score</Form.Label>
<Form.Control type="number" name="review" value={values.review} onChange={handleChange}
isValid={touched.review && !errors.review} />
<Form.Control.Feedback>{errors.review}</Form.Control.Feedback>
</Form.Row>
<div className="text-center mt-2">
<Button variant="primary" type="submit">Submit</Button>
</div>
</div>)}
</Form>
)}

</Formik>
)
}

We’re just creating a form that has one number input for the review and for validation we’re using the reviewSchemawhich just checks that the review is filled and is a number.

Next, we need to add the logic of adding the review to the restaurant by the logged-in user. To do this, first, we need to pass themongoContextprop to the component which has the MongoDBclientand the Realmappinstances:

function AddReview({mongoContext: {client, app}}) {
//...
}

Next, we’ll get theidof the restaurant from the URL param using useParam:

const { id } = useParams()

And we’ll get thehistoryinstance to use later to redirect back to home page:

const history = useHistory()

We can now add the logic to update therestaurantdocument of the passedid, adding the user'sgrade. To do that, we'll first get therestaurantscollection from oursample_restaurantsdatabase:

function submitHandler(values){
const rests = client.db('sample_restaurants').collection('restaurants')
}

Next, we’ll use the method updateOne which takes a query to choose which document to update, then takes the changes. For us, the query will be the restaurant having the id that is passed as a URL param, and the change will be pushing a new entry into thegradesarray inside therestaurantdocument:

rests.updateOne({"_id": BSON.ObjectID(id)}, {"$push": {"grades": {
date: new Date(),
score: values.review,
user_id: BSON.ObjectID(app.currentUser.id)
}}}).then (() => history.push('/'))
.catch ((err) => {
alert(err)
setLoading(false)
})

Notice that:

  1. To query the_idfield, we need to useBSON.ObjectIDto correctly pass the object id. Make sure to add at the beginning of the fileimport { BSON } from 'realm-web'.
  2. thegradesarray holds objects that havedate,score, anduser_id. This way, we're linking the grade to the appropriate user.
  3. updateOnereturns a promise, so once it resolves we're redirecting to the home page usinghistory.push('/').

And with that, ourAddReviewcomponent is ready. Next, we need to add the new page in our routes insrc/App.js:

return (
<Router>
<Navigation user={user} />
<MongoContext.Provider value={{app, client, user, setClient, setUser, setApp}}>
<Container>
<Switch>
<Route path="/signup" render={() => renderComponent(Authentication, {type: 'create'})} />
<Route path="/signin" render={() => renderComponent(Authentication)} />
<Route path="/logout" render={() => renderComponent(LogOut)} />
<Route path="/review/:id" render={() => renderComponent(AddReview)} />
<Route path="/" render={() => renderComponent(Home)} />
</Switch>
</Container>
</MongoContext.Provider>
</Router>
);

Then, we’ll need to add the link to the page inside each restaurant card. To do that, edit thesrc/components/RestaurantCard.jscomponent's return statement:

return (
<Card className="m-3">
<Card.Body>
<Card.Title>{restaurant.name} <Badge variant="warning">{avg}</Badge></Card.Title>
<Link to={`/review/${restaurant._id}`} className="card-link">Add Review</Link>
</Card.Body>
</Card>
)

Notice that we’re passing the restaurant id to the link as a parameter.

Let’s run the server now:

npm start

Make sure to log in if you aren’t already. We’ll test how this will work as a guest in a bit.

You should be able to see new links for each restaurant on the home page now.

Click on “Add Review” for any of the restaurants. You’ll see a number input field, enter any number and click “Submit”. If you’re logged in, you should see a loader, then you’ll be redirected to the home page. You can see that the restaurant’s review has changed.

Test MongoDB Realm Authorization Roles

If you recall from part 1, we added a new User role. This user role allows users that have an email to insert or update only thegradesfield of a restaurant. For a user to "belong" to the User role they need to have an email, which we declared in the "Apply When" field:

{
"%%user.data.email": {
"%exists": true
}
}

So, an anonymous user does not have permission to make any changes to thegradesfield or any field of therestaurantscollection.

Let’s test that. Log out from the current user. You should still be able to see the Add Review link and be able to access the page, as we still haven’t added conditions for a user’s authentication.

Try to add a review to any restaurant. Since you’re not logged in, you’ll get an alert with an error and nothing will be added.

As you can see the error says “update not permitted”. The user does not belong to the “User” role we created, so they’re not allowed to add a review.

Let’s now hide the link to “Add Review” for anonymous users insrc/components/RestaurantCard.js:

{!isAnon(user) && <Link to={`/review/${restaurant._id}`} className="card-link">Add Review</Link>}

Adduserto the list of props forRestaurantCard:

function RestaurantCard ({restaurant, user}) {
//...
}

Pass theuserprop toRestaurantCardinsrc/pages/Home.js:

<RestaurantCard key={restaurant._id} restaurant={restaurant} user={user} />

And let’s add a condition insrc/pages/AddReview.jsto redirect to home page if the user is not logged in:

function AddReview({mongoContext: {client, app, user}}) {
const [loading, setLoading] = useState(false)
const { id } = useParams()
const history = useHistory()

if (isAnon(user)) {
history.push('/')
}
//...
}

Now, if you’re not logged in you will not be able to see the review and if you try to access the reviews page directly you’ll be redirected to the home page.

Let’s test another aspect of the role we created. As we said, the role we created allows logged-in users to update thegradesfield. However, they should not be able to edit any other field.

Let’s change the parameter forupdateOneinAddReviewto change the name instead:

rests.updateOne({"_id": BSON.ObjectID(id)}, {"name": "test"})

This is just to easily test this restriction. Now, login and go to the “Add Review” and click Submit. You’ll see the same “update not permitted” message as before.

This shows how we can easily manage our users, their roles, and data access through MongoDB Realm.

Conclusion

Using MongoDB Realm allows us to easily create serverless apps while also managing data access, roles, and authentication. It’s also available to be used on the web (like in this tutorial), on mobile apps, and more. In this tutorial, we covered the basics that you’ll probably need in most use cases. If you dive deeper into it, you’ll surely find even more features that will be helpful for your serverless apps.

If you would like to connect and talk more about this article or programming in general, you can find me on my twitter account @shahednasserr

--

--