Remix with Database integration
How to use Remix and Supabase for CRUD operations
Saturday, November 27, 2021
Table of Contents
TL;DR
Link to the source code
Here's a live demo
UPDATE: After implementing authentication, the link above has only view access.
Here's the demo with authentication
Preface
This post will be the first part of the series on how I will create the entire application. So I will start with a boring introduction about the motivation for this series.
I have chosen Supabase to store my data as it allows me to focus on the Frontend part due to Supabase's easy-to-use API. You can use any provider of your choice, or you can even create your custom backend.
This part of the series will focus on how to use Remix for CRUD operations.
Basic overview of the app
The Vocabulary section will consist of lists of words that are publicly available and a protected admin route to perform a CRUD operation.
Here are the properties we need for each word
:
- name: the word itself
- type: the type of the word (noun, verb, adjective, etc.)
- definitions: an array of definitions
- sentences: an array of how I would use the word in a sentence
Prerequisites if you want to follow along
If you are not yet familiar with Remix, I suggest checking first my previous blog post about it, or refer to their documentation.
Create a Supabase project
If you want to use another provider, you can skip to this part Create the Remix project
Refer to their official documentation on how to create a Supabase project.
After creating your account, go to SQL Editor tab and execute the queries below:
Create words table
sql
Add a new word
sql
In the Table Editor tab, you should see the new entry.
Add anonymous access
sql
Lastly, in Authentication/Policies
tab, should be seeing this.
Create a Remix project
Installation
sh
Cleaning up
sh
Re-create file root.tsx
file under app
folder.
app/root.tsx12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
Re-create file index.tsx
file under app/routes
folder.
app/routes/index.tsx1234567
The mandatory hello world
page is now ready.
Expect the design will be terrible. I will create a separate blog for styling.
Integration Prerequisites
Install Supabase javascript library
Not using Supabase? Skip to Fetch All Words
Install Supabase
sh
Create a Supabase client utility
The next step will allow us to create a Supabase client utility that we can use across the whole application.
Create a .env
file to hold your Supabase credentials.
.env12
Make sure to add
.env
in the.gitignore
file.
Create a Supabase client utility for reusability
libs/supabase-client.ts123456
Chores before integration
(OPTIONAL)Create type definition
To take advantage of strong typing, I will create a type definition.
If you prefer not to use TypeScript, remove the declarations and usages of types, and change the file extensions from
.tsx
to.jsx
.
app/models/word.ts12345678910111213
(OPTIONAL) Redirect /
to /words
As I plan to create multiple mini-apps in this project, I'll redirect /
to /words
, for now.
The code below will ensure we don't need to manually navigate to /words
every time we open the root page.
app/routes/index.tsx12345
Integrating Supabase with Remix
Create the words listing page
If you are not using Supabase, replace the Supabase API calls with your choice.
fetch list of words using Remix's loader
app/routes/words.tsx12345678910111213
Create a React component to display the list of words
app/routes/words.tsx123456789101112131415161718192021222324
The code above will fetch the data from Supabase and display it in a list.
Create the word details page
Create a file named $id.tsx
under app/routes/words
folder.
Create the loader function
app/routes/words/$id.tsx12345678910111213
Create the component
app/routes/words/$id.tsx123456789101112131415161718192021222324
The image below shows that it still won't show even after creating the /words/[id]
route.
Adding a router Outlet
We need to add an Outlet
inside our Words Index component to fix the above issue.
app/routes/words.tsx1234567891011121314151617181920212223242526
After clicking on a word, $id.tsx route
will render on where we put the Outlet
.
Delete a word
Since we're already on the /words/$id
page, let's proceed with deletion first
Add a button to delete the word
app/routes/words/$id.tsx1234567891011121314151617
The image shows a message that we did not define any action to handle the submit event.
Delete the word in the database using Remix's action
app/routes/words/$id.tsx1234567891011121314151617
After we click on the delete button, the word hello
will be deleted from the database, and the page will redirect to the /words
page.
Explanation:
- We created a form with a hidden input field named
_method
with valuedelete
. - When the submit button is clicked, the
action
handler will trigger in the server. - Inside the
action
handler, we check if the_method
isdelete
. - If it is, we delete the word from the database.
- Redirect to
/words
route.
Why go through all this trouble?
It just happens that this approach does not need any JavaScript to run(try it on your browser). This means our app is interactive even before we load the JavaScript from the server.
Create a new word entry
Now we don't have anything on the list; let's create the route to handle creation.
Create a button in the /words
route that will navigate to /words/add
We can use the useNavigate
hook here, But for this blog, I'll show you how we can perform the entire CRUD operations without using any JavaScript on the client-side.
app/routes/words.tsx1234567891011121314151617
Create the add new word route
To avoid a 404
page, let's create the /words/add
route.
Create the component
app/routes/words/add.tsx12345678910111213141516171819202122232425262728293031323334353637383940
The image below shows the form we created after clicking on the Add new word
button.
Add an action
To avoid the missing action error after clicking on the Submit
button, let's add an action on the words/add
route.
app/routes/words/add.tsx123456789101112131415161718
After clicking on the Submit
button, the word will be added to the database, and the page will redirect to the /words/$id
page.
I'm not sure if you noticed, but we haven't used any JavaScript code on the Frontend, yet it could complete the intended task. We only used an HTML form as it was initially used to handle validations and perform the submission. This is what I like about Remix; we can focus on the web fundamentals to become a better Web developer instead of making some workarounds.
Edit a word entry
Now, to handle the missing operation in our CRUD app, let's add the ability to modify an existing entry.
Create a file named edit.$id.tsx
under app/routes/words
When we add a .
between words, it will transform to /
in the URL.
The above example will result in words/edit/[id]
.
Create a form for editing the word
Refactoring
Since the edit form is very similar to the add form, we can reuse the same form with additional checks to determine if we are adding or editing.
app/components/WordForm.tsx12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
That's a lot of code; however, we can reap the benefits of simplifying the code in add.tsx
and edit.$id.tsx
.
Update routes/words/add.tsx
app/routes/words/add.tsx12345
Create routes/words/edit.$id.tsx
app/routes/words/edit.$id.tsx123456789
Now, we have a reusable form. If we have to make a style change, we can update the WordForm
component, reflecting the change on both routes.
NOTE: I'm extracting everything to
WordForm.tsx
since it is applicable in my use case.
Create a loader for the word details
In order for the edit form to be populated with the existing data, we need to create a loader.
app/routes/words/edit.$id.tsx1234567891011121314
Create a button in the /words/$id
page to edit a word
app/routes/words/$id.tsx1234567891011121314
The image below shows the pre-filled form depending on the content of id
in the URL.
Add an action handler
To handle the form submission, we need to add an action handler.
app/routes/words/edit.$id.tsx123456789101112131415161718192021
After modifying some fields and clicking the submit button, the page will redirect to the /words/$id
page with the updated data.
Add indicator for state transition
By utilizing the useTransition
hook, we can add or change something on the screen depending on the route's state.
.tsx123456
We can replace the text states below with global loading indicator, local component spinner, disabling elements, etc.
Extra Demo: CRUD operations without JavaScript
Here's proof that we can perform the CRUD operations without using any JavaScript on the client-side(as indicated by errors in the network tab). Take note that I also simulated a slower network connection, yet the performance is not that terrible.
I'm not saying we should not use JavaScript on the client-side, as JavaScript can do some cool stuff to help with user experience. For instance, you might notice that the states were stuck in
idle
.
Conclusion
So far, I'm having a positive experience with the framework. Of course, I'm still learning, but I'm enjoying the process. I'm starting to agree with the Remix team said that if we become better with Remix, we become better with the Web. Working with Remix allows me to refresh my HTML skills that are almost diminishing due to too much dependency on JavaScript. I'm looking forward to using more of their features in the next iteration of this app.
What's next?
- Styling
- Authentication
- Error handling
- SEO
- Deeply nested routes