---
title: "Learning Go: Day Nine"
date: 2024-05-10T08:00:00.0Z
tags:
    - learning
    - go
excerpt: "Time to start getting myself connected to a database and write a bit of data"
---

Okay, so now I have a deployed project, and I'm ready to do things for real. To go back to my to-do list, here's where I am so far:

* ✅ Run a web server
* Render web pages with some dynamic content
* Store & read data from/to a database
* Send requets to a server & handle the responses
* Do all of the above on a periodic schedule

This isn't a prioritised list, so I'm going to move to the third item in the list, "Store & read data from/to a database".

## Choosing my database

It's Sqlite. It's not even a decision. It's got such a low barrier to setup, it's quick enough for the scale I'm working at, it's portable. I could also use PostgreSQL, but I don't want to.

## Setting up Sqlite

First-things-first, I need a way to communicate with my database. I already have the necessary drivers etc installed on my machine, so all I need is a Go library to handle the interface. I landed on [mattn/go-sqlite3](https://github.com/mattn/go-sqlite3), which seems to be pretty popular and actively maintained. 

So I require the module using `go get github.com/mattn/go-sqlite3`, which adds a `require` block to my `go.mod` file, as well as creating a `go.sum` file[^1].

Now I'm going to try and do a test connection:

```go
// main.go

import (
    "database/sql"
)

const createSitesTable = `CREATE TABLE IF NOT EXISTS sites (
id INTEGER NOT NULL PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
name TEXT NOT NULL,
url TEXT NOT NULL
);`

func main() {
    db, _ := sql.Open("sqlite3", "test.sqlite3")
    db.Exec(createSitesTable)
    

    // the rest of the main function...
}
```

This didn't work though. I got a nasty-looking segfault:

```bash
go run main.go

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x104c2ffb8]
```

So just having the sqlite module as a dependency isn't enough, I need to import it. But if I update the `import` statement to include `github.com/mattn/go-sqlite3`, the auto-formatter just removes it immediately. Instead, I have to prefix the import with `_` so that it remains:

```go
// main.go
import (
    "database/sql"

	_ "github.com/mattn/go-sqlite3"
)
```

Now this works, and when I do `go run main.go` it creates a `test.sqlite3` file, with the table defined:

```shell
sqlite3 test.sqlite3
SQLite version 3.43.2 2023-10-10 13:08:14
Enter ".help" for usage hints.
sqlite> .schema sites
CREATE TABLE sites (
id INTEGER NOT NULL PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
name TEXT NOT NULL,
url TEXT NOT NULL
);
sqlite> 
```

## Defining the sites models

Okay, now I'm going to create a data model to represent a stored Site. First, I created a new package, `/sites/sites.go`, and then made a `CreateTable` function that just ran the query from the previous section:

```go
// sites/sites.go

package sites

import (
	"database/sql"
	"fmt"
)

const createSitesTable = `CREATE TABLE IF NOT EXISTS sites (
id INTEGER NOT NULL PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
name TEXT NOT NULL,
url TEXT NOT NULL
);`

func CreateTable(db *sql.DB) {
	if _, err := db.Exec(createSitesTable); err != nil {
		fmt.Println(fmt.Errorf(err.Error()))
	}
}
```

Then in my main function, I can just do:

```go
// main.go

import (
	// ...
	"lewisdale.dev/oopsie/sites"
)

func main() {
    // ...
    sites.CreateTable(db)
}
```

Now, I want to define a model, which I'm going to do using a `struct`:

```go
// sites/sites.go

type Site struct {
	id         uint64
	created_at uint64
	Name       string
	Url        string
}
```

The `Name` and `Url` arguments are capitalised because I want them to be publicly accessible. This caught me out when I first tried to instantiate the struct with named parameters. I don't want `id` or `created_at` to be public fields - right now, at least.

Now I can add a method to my struct that will allow me to save the model in the database:

```go
// sites/sites.go

func (s *Site) Save(db *sql.DB) {
	if s.id != 0 {
		query := `UPDATE SITES
		SET
			name=?,
			url=?
		WHERE id =? 
	`
		db.Exec(query, s.Name, s.Url, s.id)
	} else {
		query := `INSERT INTO SITES (name, url) VALUES (?, ?)`
		db.Exec(query, s.Name, s.Url)
	}
}
```

Really, I want these sites to be unique on the URL, which would also make for easier upserts, but for now this is fine. I can then, in `main.go`, trigger this function to insert a new row to my table:

```go
// main.go

func main() {
	site := sites.Site{Name: "Lewisdale.dev", Url: "https://lewisdale.dev"}
	site.Save(db)
}
```

And I can see it's been inserted:

```bash
sqlite3 test.sqlite3
SQLite version 3.43.2 2023-10-10 13:08:14
Enter ".help" for usage hints.
sqlite> SELECT * FROM sites;
1|2024-05-08 07:56:52|Lewisdale.dev|https://lewisdale.dev
```

I think that's where I'll leave today's post, but things are starting to get a bit more interesting now at least! I'm going to have to work out how to properly test this stuff soon.

[^1]: Apparently this is for the checksums of my dependencies