Database Modelling
Until now, we've been working with a very simple table, just a list of contacts.
This was good, but kind of extremely simple, in comparison with real applications.
We're going to do a few things here:
create a migration script for our database. A migration script is a piece of code that creates our database tables, and then updates them. Its presence insures every one working on the project has the same database
add a
userstable to the database, so we keep track of our usersadd a
author_idcolumn to contacts. Contacts will be visible to everyone, but only the authors will be able to edit them or delete them
Later, we will:
add a
contact_imagecolumn to thecontactstable, so we can have imagesadd file uploads capability to both React and Express
Create the Initial Migration Script
There's no definite way to write a migration script. It can be any language, any system. One thing matters: the script should be simple to run, and when it is, every computer that ran it should have the same database.
If we're using the sqlite module, there is a migration system baked in:
Create a directory called
migrationsin the root (not insrc!)Create a file called
001-initial-schema.sql. Write in it the SQL code that creates the database. Write also in it the code to destroy the database.Later, when the database changes, create a file called
002-whatever_you_want.sql, with the changesAdd a line in your code
db => db.migrate({ force: 'last' }), which reads those files and applies them (Don't forget: you probably want to change that for your production build!) .
You can see an example in the sqlite documentation
Let's remind ourselves of our database schema.
This is what it is:

And this would be the migration file for it.
Go ahead, and write this in a file /back/migrations/001-contacts.sql
While we're at it, let's also add some content. In the Up section, below the table creation, add:
finally, in db.js, under the line that create the database, add:
Run your application now, you should see the new database changes. Good!
Add the New Table
Let's add the migration we need. Add a new file, 002-users to the migrations directory.
This is the new schema we're going for:

Yes, I'm in there twice, because I logged in once through google, and once through email. I console.logged the user object returned by Auth0, and noted the sub property.
note: we're using the auth0_sub as a property to link the two tables. We really should be using the users' nickname instead, so the same email is considered the same user. However, this would add lines to our code, because for each request to change a contact, we'd need to verify that the auth0 id used matches the email of the contact's author before allowing the change. It is easy to do, but would make this tutorial even longer, so we'll skip that for now.
Handle Changes in the Controller
You notice that the date is in a specific text format. Here's a function that will provide you with that format from Javascript:
Stick it at the top of your controller (db.js)
Let's add a few methods to our controller. We're going to need:
change the method that gets contacts so it can optionally get all contacts from a specific user
change the method that updates a contact so it cannot update a contact without a proper
author_id(for added security); same fordeletechangee the create method so it records the
author_ida new method that creates a user on the fly. Users are not created by registering. Users are created automatically when Auth0 answers. This method will first check if the user already exists (has logged in in the past). If yes, it will do nothing. If not, it will create the user.
First, let's change getContactsList.
Now, getContactsList can get all contacts from a specific user.
note: We could profit from the occasion to add some pagination. Most tutorials tell you to use limit and offset, but as described in this article on AllYouNeedIsBackend, it is rather slow. We'll use the technique called keyset pagination. This will not be explained in this tutorial, however you can check the code here
Similarly, let's change the update & delete methods so they don't work without author_id:
What we've mainly done here is change the function to include the author_id in the WHERE clause. This way, if we try to edit a contact without providing the proper author_id, an error will be thrown and the operation will fail.
Now, let's change the create method:
Finally, let's add the createUser and createUserIfNotExists methods:
And, let's reflect those changes on our router. We want to ensure the user is logged in before create, update, and delete:
Use the Authentication From the Front End
Finally, let's send the authentication token with every request. in front/src/App.js, everywhere you use fetch, send the proper headers:
We are now all set!
If you try to create a contact now without being logged in, you will get an error.
3 things left to do:
on the profile page, show the user's contacts
do not show the
createpage at all if the user is not logged indo not show the
editordeletebuttons if the user is not logged in (or doesn't have permission to see the note)
Show User's Contacts on the Profile Page
Until now, we've used getPersonalPageData just to test if everything is working well. It runs when the user logins, and just puts the server's message in a pop-up (which has since long been showing the unhelpful message [object Object]).
We'll change it to put all the stuff we need in state.
first, remove the line
Since we will modify the state, our Component will update, no need to force anything
We will also replace the lines in login
with just
in getPersonalPageData, add:
We're now setting the user in state when they log in. We're also calling getPersonalPageData both when we login and after silentAuth, so it will be set both when the user has just logged in, and when the user has just refreshed the page
the user object that we're sending from the database contains an array of contacts. We can now display those on the user's page:
If you now load the profile page while being logged in, you will see the contacts you have added yourself (provided you have created any).
Little problem though:
If you edit a contact, then go back to the profile page, you will see the contact is not updated there (unless you refresh). The reason is that the contact inside the user key in state is duplicated. The edited contact resides in the state's contacts_list. This is relatively simple to fix: we simply have to use the same contacts everywhere.
Instead of using the contacts in the user's array, we will just use it to reference contacts from the main array.
Here's how it looks:
Let's use it:
Conditionally hide or show elements
Currently, any user has access to all pages, even if they don't work. For example, even a non logged-in user has access to the create page, even though they can't submit.
We want to:
hide things depending on if the user is logged in or not
make some routes conditional
For both those things, we're going to create what we call Higher Order Components, or HOC for short.
HOCs are components that contain other components. For example, we could have
This is kinda contrived, but it is valid code.
What we want is something like:
In the example above HideIfUserNotLoggedIn doesn't show for the user. It just conditionally renders it's content (the children property, in React parlance).
We will implement almost exactly that, we'll just call it something a little more sensible than HideIfUserNotLoggedIn.
Create a new file, call it IfAuthenticated.js:
note: The children are still evaluated. Which means if user is not set, this code will give us an error user is undefined:
Because the children are read and evaluated, whether they end up rendering or not. Bear that in mind when you use this component.
Let's use this component. We're going to hide
the
profileandcreatelinks if the user is not authenticatedthe
editanddeletebuttons on the contact if the user is not authenticated
While we're editing the Contact component, we'll also make it so the edit and delete buttons also don't show if the current user is not the author
Last but not least, we want to protect the routes. We've hidden the links, but for example, someone can still navigate to /create. It shouldn't work.
What we will do is create a Higher Order Component that wraps react-router's <Route> (itself a HOC). It will be called SecureRoute and will work like that:
Let's implement it. Open a file SecuredRoute.js:
Then, let's use it:
Congratulations! You have a proper database, with foreign keys, relations, and proper permissions for editing.
Last updated
Was this helpful?