GoLand 2024.3 Help

Building the Notes App with a database and a web form

This tutorial will guide you through creating a simple notes application using Go, SQLite, and a basic HTML/JavaScript form to add and delete notes.

Prerequisites

  • Go installed on your machine (version 1.16 or higher)

  • GoLand – most versions will do. This tutorial was created using GoLand 2024.2.

  • Internet connection to fetch the SQLite dependency.

Steps

  1. Initialize the project

  2. Set up the database

  3. Create the note struct

  4. Implement CRUD operations

  5. Create the main function

  6. Create the HTML form

  7. Run the application

Step 1: Initialize the project

Create a new directory for your project and initialize a Go module.

  1. Click File | New | Project.

    Alternatively, on the Welcome to GoLand screen, click New Project.

  2. In the New Project dialog, select Go.

  3. In the Location field, type or navigate to the location where you want to store your project.

  4. In the GOROOT field, specify the location of your Go installation. GoLand typically detects this location automatically.

    To change or install a new version of the Go SDK, click Add SDK (Add SDK icon) and select one of the following options:

    • Local: to select a Go SDK version on your local drive.

    • Download: to download the Go SDK from the official repository.

  5. Clear the Add sample code checkbox. You can leave this checkbox, if you want to have some onboarding tips before you start.

  6. Click Create.

    GoLand creates a project folder and initializes a project, performing the following Bash actions instead of you:

    mkdir notes-app cd notes-app go mod init notes-app
    Initialize the Project

Step 2: Set up the database

Now let's create a new file named database.go. In this file, we will set up the SQLite database connection. The overall configuration includes the creation of the database file (notes.db) and the notes table, in which we will store information.

In the root directory of your project, create database.go with the following content:

package main import ( "database/sql" "log" // Import the SQLite driver anonymously to initialize it // without directly referencing _ "github.com/mattn/go-sqlite3" ) // Declare a global variable to hold the database connection pool var db *sql.DB // initDB initializes the database connection and creates // the 'notes' table if it doesn't exist func initDB() { var err error // Open a connection to the SQLite database file named "notes.db" db, err = sql.Open("sqlite3", "./notes.db") if err != nil { // Log any error that occurs and terminate the program log.Fatal(err) } // SQL statement to create the notes table if it doesn't exist createTable := ` CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, content TEXT );` // Execute the SQL statement to create the table _, err = db.Exec(createTable) if err != nil { // Log any error that occurs while executing the SQL statement // and terminate the program log.Fatal(err) } }

The following procedure will guide you through the process of file creation.

Create a Go file

  1. A new project has no Go files. To create a Go file, perform one of the following actions:

    • In the Project tool window, right-click the parent folder of a project, and select New | Go File.

    • Click the parent folder of a project, press Alt+Insert, and select Go File.

    • Click the parent folder of a project, navigate to File | New | Go File.

  2. In the New Go File dialog, type a name of the file and select whether you want to create an empty Go file (Empty file) or a Go file with the defined main function (Simple application).

    Create a Go file

Step 3: Create the Note struct

Create a new file named note.go and define the Note struct.

The Note struct defines the structure of a note, which includes an ID, title, and content. This struct provides a clear and organized way to represent a note in the application.

package main type Note struct { ID int Title string Content string }

Also, when interacting with the database, it's important to have a structured way to handle the data. The Note struct allows you to map database rows to Go objects and vice versa. For example, when retrieving notes from the database, you can scan each row into a Note struct.

Step 4: Implement CRUD operations

Let's add CRUD operations in note.go. CRUD operations are the basic functions that are necessary to interact with a database or persistent storage. CRUD stands for Create, Read, Update, and Delete.

Create operations are used to add new records or data entries to the database. In SQL, this is typically done using the INSERT statement.

func CreateNote(title, content string) { query := `INSERT INTO notes (title, content) VALUES (?, ?)` _, err := db.Exec(query, title, content) if err != nil { log.Fatal(err) } }

Read operations are used to retrieve existing records or data from the database. In SQL, this is typically done using the SELECT statement.

func GetNotes() ([]Note, error) { rows, err := db.Query("SELECT id, title, content FROM notes") if err != nil { return nil, err } defer rows.Close() var notes []Note for rows.Next() { var note Note err = rows.Scan(&note.ID, &note.Title, &note.Content) if err != nil { return nil, err } notes = append(notes, note) } return notes, nil }

Update operations are used to modify existing records or data in the database. In SQL, this is typically done using the UPDATE statement.

func UpdateNote(id int, title, content string) { query := `UPDATE notes SET title = ?, content = ? WHERE id = ?` _, err := db.Exec(query, title, content, id) if err != nil { log.Fatal(err) } }

Delete operations are used to remove existing records or data from the database. In SQL, this is typically done using the DELETE statement.

func DeleteNote(id int) { query := `DELETE FROM notes WHERE id = ?` _, err := db.Exec(query, id) if err != nil { log.Fatal(err) } }
package main import ( "log" ) type Note struct { ID int Title string Content string } // CreateNote adds a new note to the database func CreateNote(title, content string) { query := `INSERT INTO notes (title, content) VALUES (?, ?)` _, err := db.Exec(query, title, content) if err != nil { log.Fatal(err) } } // GetNotes retrieves all notes from the database func GetNotes() ([]Note, error) { rows, err := db.Query("SELECT id, title, content FROM notes") if err != nil { return nil, err } defer rows.Close() var notes []Note for rows.Next() { var note Note err = rows.Scan(&note.ID, &note.Title, &note.Content) if err != nil { return nil, err } notes = append(notes, note) } return notes, nil } // DeleteNote removes a note from the database func DeleteNote(id int) { query := `DELETE FROM notes WHERE id = ?` _, err := db.Exec(query, id) if err != nil { log.Fatal(err) } }

Step 5: Create the main function

Now let's modify main.go and add the main function there. Code in main.go will set up a basic web server with routes to display notes, add new notes, and delete existing notes. It uses a SQLite database to store the notes and handles HTTP requests to perform CRUD operations.

To handle HTTP requests, we will use handlers. HTTP handlers in Go are functions that handle HTTP requests. They are the building blocks for creating web servers in Go. An HTTP handler takes an HTTP request, processes it, and generates an HTTP response. In Go, these handlers are functions or methods that satisfy the http.Handler interface or are passed directly to functions like http.HandleFunc.

We will start with the main function.

Function: main

The main function will perform the following functionality:

  • Database initialization: initDB() is called to set up the database connection. The defer db.Close() statement ensures that the database connection is closed when the program exits.

  • HTTP handlers: the http.HandleFunc function sets up routes and their corresponding handler functions:

    • / is handled by indexHandler.

    • /add is handled by addHandler.

    • /delete is handled by deleteHandler.

When we run our program, the code will start Starting the Server: The server is started on port 8080, and any errors are logged and terminate the program.

func main() { // Initialize the database initDB() // Ensure the database connection is closed when the main function exits defer db.Close() // Set up HTTP handlers for different routes http.HandleFunc("/", indexHandler) // Route for the homepage http.HandleFunc("/add", addHandler) // Route for adding a new note http.HandleFunc("/delete", deleteHandler) // Route for deleting a note // Log the server start and listen on port 8080 log.Println("Server started at :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }

Function: the index handler

The index handler performs several functions:

  • Retrieve notes: calls GetNotes() to get all notes from the database. If there is an error, it sends an internal server error response.

  • Parse template: parses the index.html template file. If there is an error parsing the template, it sends an internal server error response.

  • Execute template: executes the template with the retrieved notes, rendering the HTML to the response writer w.

func indexHandler(w http.ResponseWriter, r *http.Request) { // Retrieve all notes from the database notes, err := GetNotes() if err != nil { // Send an internal server error response if something goes wrong http.Error(w, err.Error(), http.StatusInternalServerError) return } // Parse the HTML template file tmpl, err := template.ParseFiles("index.html") if err != nil { // Send an internal server error response if the template cannot be parsed http.Error(w, err.Error(), http.StatusInternalServerError) return } // Execute the template with the notes data tmpl.Execute(w, notes) }

Function: the add handler

The add handler ensures that the note will be added to the database. The handler includes the following functionality:

  • Check request method: ensures that only POST requests are handled.

  • Retrieve form values: gets the title and content values from the form.

  • Create note: calls CreateNote to add a new note to the database.

  • Redirect: redirects the user back to the homepage after adding the note.

func addHandler(w http.ResponseWriter, r *http.Request) { // Only handle POST requests if r.Method == "POST" { // Get the title and content from the form values title := r.FormValue("title") content := r.FormValue("content") // Create a new note with the provided title and content CreateNote(title, content) } // Redirect the user back to the homepage after adding the note http.Redirect(w, r, "/", http.StatusSeeOther) }

Function: the delete handler

The delete handler deletes notes from the database. This handler does the following:

  • Convert ID: Converts the id form value to an integer. If the conversion fails, it sends a bad request response.

  • Delete note: Calls DeleteNote to remove the note with the given ID from the database.

  • Redirect: Redirects the user back to the homepage after deleting the note.

func deleteHandler(w http.ResponseWriter, r *http.Request) { // Convert the ID from the form value to an integer id, err := strconv.Atoi(r.FormValue("id")) if err != nil { // Send a bad request response if the ID is not valid http.Error(w, "Invalid ID", http.StatusBadRequest) return } // Delete the note with the given ID DeleteNote(id) // Redirect the user back to the homepage after deleting the note http.Redirect(w, r, "/", http.StatusSeeOther) }
package main import ( "encoding/json" "fmt" "html/template" "log" "net/http" "strconv" ) // main function is the entry point of the program func main() { // Initialize the database initDB() // Ensure the database connection is closed when the main function exits defer db.Close() // Set up HTTP handlers for different routes http.HandleFunc("/", indexHandler) // Route for the homepage http.HandleFunc("/add", addHandler) // Route for adding a new note http.HandleFunc("/delete", deleteHandler) // Route for deleting a note // Log the server start and listen on port 8080 log.Println("Server started at :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } // indexHandler handles the requests to the homepage func indexHandler(w http.ResponseWriter, r *http.Request) { // Retrieve all notes from the database notes, err := GetNotes() if err != nil { // Send an internal server error response if something goes // wrong http.Error(w, err.Error(), http.StatusInternalServerError) return } // Parse the HTML template file tmpl, err := template.ParseFiles("index.html") if err != nil { // Send an internal server error response if the template // cannot be parsed http.Error(w, err.Error(), http.StatusInternalServerError) return } // Execute the template with the notes data tmpl.Execute(w, notes) } // addHandler handles the requests to add a new note func addHandler(w http.ResponseWriter, r *http.Request) { // Only handle POST requests if r.Method == "POST" { // Get the title and content from the form values title := r.FormValue("title") content := r.FormValue("content") // Create a new note with the provided title and content CreateNote(title, content) } // Redirect the user back to the homepage after adding the note http.Redirect(w, r, "/", http.StatusSeeOther) } // deleteHandler handles the requests to delete a note func deleteHandler(w http.ResponseWriter, r *http.Request) { // Convert the ID from the form value to an integer id, err := strconv.Atoi(r.FormValue("id")) if err != nil { // Send a bad request response if the ID is not valid http.Error(w, "Invalid ID", http.StatusBadRequest) return } // Delete the note with the given ID DeleteNote(id) // Redirect the user back to the homepage after deleting the note http.Redirect(w, r, "/", http.StatusSeeOther) }

Step 6: Create the HTML form

Now let's create a new file named index.html with a form to add and delete notes. This file will use html/template package. The html/template package in Go provides the functionality to parse and execute templates. The placeholders like {{range .}} and {{.Title}} are part of Go's template syntax, allowing dynamic content to be inserted into the HTML.

Here is some breakdown of the syntax used in our HTML template:

  • {{range .}}: starts a loop over a collection of data passed to the template. In this case, . represents the data passed to the template, which is a slice of Note structs.

  • {{range .}} iterates over each element in the collection.

  • {{.Title}}: inside the {{range .}} block, . refers to the current element of the iteration. {{.Title}} accesses the Title field of the current Note struct.

  • {{else}}: provides an alternative content to display if the range does not iterate over any elements (for example, if the collection is empty).

  • {{end}}: marks the end of the range block.

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Notes App</title> </head> <body> <h1>Notes</h1> <form action="/add" method="post"> <label for="title">Title:</label> <input type="text" id="title" name="title" required><br> <label for="content">Content:</label> <textarea id="content" name="content" required></textarea><br> <button type="submit">Add Note</button> </form> <h2>Existing Notes</h2> {{range .}} <div> <h3>{{.Title}}</h3> <p>{{.Content}}</p> <form action="/delete" method="post" style="display:inline;"> <input type="hidden" name="id" value="{{.ID}}"> <button type="submit">Delete</button> </form> </div> {{else}} <p>No notes available.</p> {{end}} </body> </html>

Connection with the Go code

In the indexHandler function in your Go code, you retrieve the notes from the database and pass them to the template.

In the following code snippet, template.ParseFiles("index.html") reads and parses the index.html file.

tmpl.Execute(w, notes) executes the template, passing notes as the data. This notes slice of Note structs is what {{range .}} iterates over.

// Parse the HTML template file tmpl, err := template.ParseFiles("index.html") if err != nil { // Send an internal server error response if the template cannot be parsed http.Error(w, err.Error(), http.StatusInternalServerError) return } // Execute the template with the notes data tmpl.Execute(w, notes)

Step 7: Run the application

GoLand has various options to run your application. The easiest one is just right from editor.

Run from the editor

If you are not going to pass any parameters to your program, and your program does not require any specific actions to be performed before start, you can run it right from the editor.

  • Click in the gutter near the mainfunction and select Run.

  • To run a script, open it in the editor or select it in the Project tool window, and then select Run <script file name> from the context menu.

Behind the scenes, GoLand runs go run main.go amd displays the output in the Run tool window.

Now, you just need to open your web browser and navigate to http://localhost:8080. You should see a form to add notes and a list of existing notes with options to delete them.

Run the Application
Last modified: 12 November 2024