How to Integrate MongoDB Realm with React: Part 2
Shahed Nasser
Posted on May 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 use yup
for validation and formik
to 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 reviewSchema
which 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 the mongoContext
prop to the component which has the MongoDB client
and the Realm app
instances:
function AddReview({mongoContext: {client, app}}) {
//...
}
Next, we'll get the id
of the restaurant from the URL param using useParam:
const { id } = useParams()
And we'll get the history
instance to use later to redirect back to home page:
const history = useHistory()
We can now add the logic to update the restaurant
document of the passed id
, adding the user's grade
. To do that, we'll first get the restaurants
collection from our sample_restaurants
database:
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 the grades
array inside the restaurant
document:
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:
- To query the
_id
field, we need to useBSON.ObjectID
to correctly pass the object id. Make sure to add at the beginning of the fileimport { BSON } from 'realm-web'
. - the
grades
array holds objects that havedate
,score
, anduser_id
. This way, we're linking the grade to the appropriate user. -
updateOne
returns a promise, so once it resolves we're redirecting to the home page usinghistory.push('/')
.
And with that, our AddReview
component is ready. Next, we need to add the new page in our routes in src/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 the
src/components/RestaurantCard.js
component'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 the grades
field 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 the grades
field or any field of the restaurants
collection.
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 in src/components/RestaurantCard.js
:
{!isAnon(user) && <Link to={`/review/${restaurant._id}`} className="card-link">Add Review</Link>}
Add user
to the list of props for RestaurantCard
:
function RestaurantCard ({restaurant, user}) {
//...
}
Pass the user
prop to RestaurantCard
in src/pages/Home.js
:
<RestaurantCard key={restaurant._id} restaurant={restaurant} user={user} />
And let's add a condition in src/pages/AddReview.js
to 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 the grades
field. However, they should not be able to edit any other field.
Let's change the parameter for updateOne
in AddReview
to 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
Posted on May 29, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.