How to use sql database in golang

Golang is something I’am experimenting these days. I find the argument that it has better performance (almost twice as ASP.NET Core) very appealing. So I started to implement an identity server for all my future projects in golang. Why not? After all, you can’t learn something without actually using it in the real world.

Golang sure is interesting. It has many good features. It’s very easy to work with, and writing go just has a certain elegance to it. However, this article is about database in golang, so all the opinion will be saved for another article.

Generic Driver

You may decide to use any kind of database for you next project. It could be SQL(relational), NO-SQL, Object(MongoDB) and so on(Actually, these are the only ones I know). Golang standard library has a genric driver for all of them. For my project, I use SQL.

import (
	"database/sql"
)

Implementation Driver

I did say this is genric didn’t I? Being a generic driver means it can’t do much except being there and telling the compiler that you chose SQL as your database. Don’t get me wrong here. It’s not like the generic driver is not important here. It does everything on a higher level. If you want a database object, you call this generic driver. If you want to execute a SQL command, you call this generic driver.

var statement = `
	INSERT INTO users(firstname, lastname, email, password) 
	VALUES($1, $2, $3, $4);
`
	db.Query(statement, "hello", "world", "email", "123456")

However, you still need a specific driver for the actually implementation of the SQl database. Here we will choose postgresql. There are a few choices, we will choose this one.

import (
	"database/sql"
	_ "github.com/lib/pq"
)

Now when ever you execute a query, the generic driver will know to call this implementation.

Initialize DB

After you imported all the libraries, you may actually want to write some code. Don’t just rush into creating the tables just yet. A DB needs to be initialized and configured.

Configs

Postgresql will need some minimal config to connect and run. You can totally hard code them, but in this case, let’s use environmental variables as it’s more safe. Put these in your bashrc or zshrc or whatever the shell you use. Of course the port number and the names can be whatever you want.

export DBHOST=localhost
export DBPORT=5432
export DBUSER=admin
export DBPASS=admin
export DBNAME=postgres

Now that we have our env, we just need to read them in go and pass them into the db driver. I’m skipping some null checks here as it is one of the least elegant thing in go and showing them in tutorial like this is just not helping the readability.

func InitializeDatabase() {
	config := getConfig()
	var err error
	psqlInfo := fmt.Sprintf(`
		host=%s port=%s user=%s 
		password=%s dbname=%s sslmode=disable`,
		config[dbhost], config[dbport],
		config[dbuser], config[dbpass], config[dbname])
	db, err = sql.Open("postgres", psqlInfo)
	if err != nil {
		panic(err)
	}
	err = db.Ping()
	if err != nil {
		panic(err)
	}
	fmt.Println("Connected to PostgreSQL")
}

func getConfig() map[string]string {
	conf := make(map[string]string)
	host, _ := os.LookupEnv("DBHOST")
	port, _ := os.LookupEnv("DBPORT")
	user, _ := os.LookupEnv("DBUSER")
	password, _ := os.LookupEnv("DBPASS")}
	name, _ := os.LookupEnv("DBNAME")
	conf["DBHOST"] = host
	conf["DBPORT"] = port
	conf["DBUSER"] = user
	conf["DBPASS"] = password
	conf["DBNAME"] = name
	return conf
}

Realize here that InitializeDatabase() has it’s first letter capitalized. In golang, this means we are exporting this functioin. No, it doesn’t this is a convention and you have to manually export all the functions that are like this. Go will do this for you. In fact, if you have a function that has a lower case for the first letter of it’s name, it will be considered private. As in, this function will not be usable outside this package’s scope.

What Now?

We are done, right? Now that we finally initialized the database, and gave it a config, we can finally go and create tables and insert rows right? Almost, but not quite. You can for sure do the following.

type User struct {
	Firstname string `json:"firstname"`
	Lastname  string `json:"lastname"`
	Email     string `json:"email"`
	Password  string `json:"password"`
}

func main() {
	initDb()
	var createTable = `
		CREATE TABLE IF NOT EXIST users (
			firstname varchar(255), 
			lastname varchar(255), 
			email varchar(255), 
			password varchar(255)
		);
	`
	var insertUser = `
		INSERT INTO users(firstname, lastname, email, password) 
		VALUES($1, $2, $3, $4);
	`
	var getUser = `
		SELECT * 
		FROM users
		WHERE email=$1;
	`
	var user User
	db.Query(createTable)
	db.Query(insertUser, "hello", "world", "email", "123456")
	row := db.QueryRow(getUser, "email")
	err := row.Scan(
		&user.Firstname,
		&user.Lastname,
		&user.Email,
		&user.Password,
	)
	if err != nil {
		fmt.Println("fail to get user", err)
	}
	fmt.Println("User is ", user.Firstname)
}

But have you realized that we have not talked about what the db variable is? Well, it’s very important. This variable is the database handler from the generic driver. We will need to “declare” it every time we need to use it. I put “declare” in quote because we don’t realy create this variable. This is a variable in the driver, we only use it’s pointer to point to the exact same handler every time we use it. So… Put this line in every file you want to access db.

var db *sql.DB

Conclusion

Here we have it. Now you know how to use sql database in golang. This tutorial included how to import a generic driver, a implementation, config and initialize a db and how to execute a query. This a one of my first blog here. It sure feels good writing about things I’m interested in. Looking forward to how this blog thing turn out for me.

 

 

Like the content? Buy me a coffee!

$2.99

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s