This is part of a series of articles. Read the other parts here:
- Build a REST API from scratch with Go, Docker & Postgres
- Build a fullstack app with Go Fiber, Docker, and Postgres
- Create a CRUD app with Go Fiber, docker, and Postgres
Introduction
CRUD refers to the four basic operations an app should be able to perform. The acronym represents the Create, Read, Update, and Delete functions.
In the first and second parts of this tutorial series, we learnt how to Create and Read from a database. In this third instalment, we will learn how to Update and Delete data from a database.
Prerequisites
To follow along, you will need to have Docker installed and running.
You will also need the source code from the second part of the tutorial series. If you did not happen to follow that tutorial, you can download the finished code and use it as your starting point.
Revisiting fact creation
Before we start adding new functionality, let’s first improve some existing code.
In the previous tutorial, we had set up a confirmation page which we redirected to after we successfully created a new Fact
. We did this to get some practice creating new handlers, now we can update this behaviour so that we are redirecting to the index
page, instead.
In the handlers/facts.go
file, let’s update the CreateFact
handler. If we get errors when trying to parse the body of the request, we will render the new
template. And if we get an error when trying to save the Fact
to the database, we will also just render the new
template. But if can successfully save the new Fact
, then we will redirect to the index page to see our newly created Fact
being listed:
|
|
Now that our CreateFact
handler is no longer using the ConfirmationView
handler, we can delete it.
Read
In the first part of this tutorial series, we already learnt how to read all our Facts
from the database. Then in the second part, we built a UI to display them all. To get more practice with reading from the database, we will now learn how to Show
a single fact on a page of its own.
In our browser window, we can navigate to localhost:3000/fact/1 because this will be the url format we will use to show
a fact, given its id
. Since we newly created a fact, we expect its id
to be 1
. However, this currently results in an error because we have nothing set up to support a show
action.
Route
Let’s head into the routes
file to add a new show route so that our app recognises this new url format. Within the body of the setupRoutes()
method, we can add a new route to show
a single Fact
. This route will use the GET
action. It will also have a :id
path parameter.
|
|
We are referencing the ShowFact
handler that does not exist yet. We can create that next.
Handler
Within the handlers
package, we will create the ShowFact
handler that will retrieve and render a single fact.
As with all handlers in Go Fiber, we pass in the context and return an error. Then within the body of the ShowFact
handler, we create a new variable called fact
to store a Fact
model instance to hold the fact we will retrieve.
To be able to retrieve the Fact
from the database, we will need to know its id
. We can use the c.Params()
method to pull this information from the URL.
A typical URL will look something like this:
We use Go Fiber’s c.Params()
method when we’re specifically trying to get values from the path parameters. So c.Params("id")
would get us the id
value from the above example URL.
Once we have the id
value, we can use it to query the database
and get the First
record where the id
matches.
Then we can use the c.Render()
method to render a frontend view.
|
|
Let’s use the frontend views we already have to add a new Fact
. If we try to view this newly-created Fact
in our browser, we get the following error:
We can fix this by creating a new template called show
:
|
|
Then within this view/show.html
file, we can add some markup. The classes used in this markup correspond with styles that have been pre-prepared in the CSS. We will not go into depth about them, as CSS is out of scope for this tutorial.
|
|
We use an {{if}}
block to check for the presence of a Fact
and conditionally display content. If a Fact
exists, we will render it along with an Edit
link and a Delete
button. If we head into the browser now, we should be able to see our single Fact
being displayed by the show
template.
However, you may notice that none of our CSS styles are being applied to this page. We can quickly fix this by heading into our view/layouts/main.html
file and update the link to our stylesheet so that is has a preceding /
in the path. So it should look like this:
|
|
And if your logo image is also missing, we can fix this by heading into the views/partials/header.html
file and update the link to our image so that is has a preceding /
in the path. So it will look like this:
|
|
Now when we refresh the browser, we should see our styled show
page
404 page
So far things are looking promising, but what happens if we try to request a fact that does not exist? We can find out by changing the id
in our path parameter to 99
.
We’re getting an empty result but our show
template is still rendering. This behaviour would be confusing to a user because they have no way of knowing what is going on. We can improve the experience by introducing a 404
page. If a fact does not exist, we should render this 404
page instead.
In the cmd/routes.go
file, we update the ShowFact
handler to return a NotFound
when we are unable to retrieve the record. This NotFound
handler does not exist yet, so we will need to create it.
|
|
Then we can go and update our app config in cmd/main.go
to also use the NotFound
handler. Remember to place this after the other setup, but right before the call to app.Listen
|
|
If we try to test this in the browser now, we will get a blank page. We need to create a 404.html
file in our public folder:
|
|
Then inside the public/404.html
file, we can paste some ready-made content from the project repository.
Now when we try to navigate to a route that does not exist, we see our new 404
page:
Our show page is done! Let’s keep going.
Update
The next CRUD action we will tackle is the Update
operation. Again let’s start in our cmd/routes.go
file.
In our cmd/routes.go
file, we need to add 2 new routes. We need a GET
route that will take us to the edit
form and another PATCH
route that will actually update
the record we are editing. GET
and PATCH
are HTTP
verbs (methods) and aren’t specific to Go Fiber.
|
|
Neither of these handlers exist yet, so we can head into our handlers
package to create them.
Within the body of the EditFact
handler, we create a new variable called fact
to store the Fact
we will be editing. Then we use the c.Params("id")
method call to get the id
value from the URL.
We query the database and retrieve the first record where
the id
matches. If we can’t find it, we render the NotFound
handler. Otherwise, we will Render
the view template and send the fact
variable through to the frontend.
You may notice that this handler is very similar to the ShowFact
handler, the main difference being we’re rendering different frontend views for each. If you’re feeling adventurous, you could consider DRYing out the code that is common between the two handlers.
|
|
The next handler we’ll need to create is the UpdateFact
handler which will persist the changes to the database
. If we run into an error during the parsing step, we will send back a Service Unavailable
status. If we get an error when trying to write these changes to the database, we will redirect to the edit
template
|
|
Now we need a frontend view so users can edit
facts in the browser.
|
|
Inside the edit.html
file, we will create a simple form with the id
attribute of form-update-fact
. We will also store the current fact’s id
column value inside a data
attribute called data-factid
. These id
values might be confusing, but just be mindful that the first id
is an HTML
attribute that can be targeted by JavaScript and the fact’s id
refers to the database column.
Then within the form body, we will add an input field for question
, an input field for answer
, and a submit
button.
|
|
We will also head into the show.html
page and update the edit
link so that its href
value is pointing to our new edit
route. We use {{ .Fact.ID }}
to populate the id
of the current fact we want to edit.
|
|
If we take a look at your Fact
in the browser, it should be able to click on the Edit
button. This will take you to the edit
form
Currently, our form fields are empty. This isn’t ideal, because we should be able to see the values we want to change. We can remedy this by adding a value
attribute to each of the fields. So the markup for our form should now look like this:
|
|
If we refresh our browser, the fields of our edit
form should now have values
But if we try to submit the form now, it does not work as expected. Why is this?
The HTML form
element only supports the GET
and POST
methods, and the default method for a form
is GET
. However, our update route is using the PATCH
method to update our facts. We will need a way to send a PATCH
request from our edit
form. We can achieve this with a little bit of JavaScript.
Send PATCH request with JavaScript
In the previous part of this tutorial series, we added some JavaScript to our views/index.html
page because this was the only place in our app that was using JavaScript. Now, we’ve reached a point where we need JavaScript on other pages too, so we will need to import our script
in a place that is accessible throughout the app. Let’s move our script
tag from views/index.html
into our main
layout. So our views/layout/main.html
file should now look like this:
|
|
By default, all our static assets are cached so that the app is faster. This makes sense when our app is in production, because we do not want to keep fetching styles and scripts that have not changed. However, this can be annoying in development, because we need to make frequent changes to our CSS and JS while we’re building things.
We can work around the caching by appending a timestamp to the end of the asset urls. This hack should only be used, locally, though. We do not want to have this behaviour in production.
First, we can add an id
attribute to our <link>
and <script>
tags so we can target them with JavaScript. Then we can add a little snippet that will append a timestamp to the end of each path. Notice that we also removed the ?v=1
query param from the end of the stylesheet path.
|
|
We have now set up everything we need for the PATCH
request. Inside the public/javascript/app.js
file, we can paste in a snippet. This snippet uses the JavaScript fetch
API to make a PATCH
request to edit the current fact. JavaScript is outside the scope of this tutorial, so we will not step through every line of this snippet, but this is what is should look like:
|
|
Now we can try this out in the browser. If we visit http://localhost:3000/fact/1/edit and change the values, we can submit the edit
form to update the current Fact
.
Delete
The last CRUD action we will implement is the delete
action. First, we will head into our routes.go
file to add a new delete route.
|
|
We are passing in a handler called DeleteFact
, but it does not exist yet, so let’s head into our handlers
package to create it. First we will find the current Fact
using the provided id
and then we will delete
the fact using the Delete()
method that GORM gives us. Once we have successfully deleted the fact, we will redirect to the index
page
|
|
Now that we can delete the app in the API, let’s wire up the delete
button that appears on our show
page. We will give it an id
of delete-button
so we can target it with JavaScript. Then we will also add a data-factid
attribute so that we can pass the current fact’s id
column value to JavaScript
|
|
Then in our app.js
file, we can add a little snippet that sends a DELETE
request. We won’t go over this snippet in too much detail, but this is the basic idea:
- When the
deleteButton
is clicked, we ask the user toconfirm
their decision to delete - Then we use the
fetch
API to send aDELETE
request to our endpoint - After the
fact
is successfully deleted, we redirect to theindex
page
|
|
Improving the index page
So far, we have been manually typing in the URL every time we want to see a single Fact
. It would be a nicer experience if we could click on the facts from our List without having to remember the IDs for each one.
Back in browser, let’s add a few facts to work with:
Since we have a show
page now, we no longer need the toggle answer
button. So we can update our index.html
page to remove it. The updated markup looks like this:
|
|
We are using a new partial
called icon-arrow-right
, but it does not exist yet. So let’s create that now.
First let’s create a new folder to hold all our icon
partials
|
|
Then inside this new folder, let’s create our icon-arrow-right
partial
|
|
Then within icon-arrow-right.html
, we can paste some SVG code:
|
|
If we refresh our app in the browser now, this is what it should look like
Now we can click on each fact and be taken to its individual show
page where we can edit and delete it, easily.
And with that, we’ve come to the end.
Conclusion
In this tutorial we implemented all the CRUD actions - Create, Read, Update and Delete. We used JavaScript’s fetch
API to make PATCH
and DELETE
requests. We also added a little hack to bust our browser’s cache during development.
Congratulations, you did great! Keep learning and keep coding. Bye for now, <3.