12 minutes read

You can find a lot of useful applications to make, modify and interact with a database on the Internet: Microsoft SQL Server, MySQL, SQLite, Oracle, and so on. You can also use Go for that. In this topic, you'll be introduced to GORM, a full-featured object-relation-mapping (ORM) library for Go.

In particular, you'll learn how to install GORM, connect to a SQLite database using GORM, declare a GORM model, and retrieve data from a database using GORM.

What is GORM?

In simple terms, GORM is an open-source ORM library that allows you to match and synchronize the data flow between two different types of systems — relational databases and the Go programming language.

gorm-logo.svg
GORM logo by Jinzhu/the GORM team

Even though Go is not a pure object-oriented language, it has the struct data type, methods, and interfaces that allow an object-oriented programming style. Furthermore, these features will enable you to use GORM to create models, which are struct types representing a specific database table.

GORM provides other ways to interact with tables and perform queries, such as using a map[string]interface{}. However, GORM's preferred convention is to use model structs to represent tables.

Finally, GORM provides official support for SQLite, MySQL, PostgreSQL, and Microsoft SQL Server databases. It also allows you to create a custom database driver if it implements the Dialector interface. An example of a custom database driver is the GORM Oracle Driver.

How to install GORM

Before you can use GORM in your Go project, you'll need to initialize Go modules and install the required GORM packages.

For an easier understanding of the code snippets in this topic, you can look at the example-gorm project. It contains a main.go file with the required code to import the gorm package and the sqlite database driver to connect to the chinook database.

After you've downloaded the example-gorm project files, open the main.go file and take a look at the import statement:

import (
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

When working with GORM, you'll need to import the base GORM package "gorm.io/gorm" — it contains all the dependencies required to create models and perform queries to the database. You'll also need to import a database driver that allows you to connect to the database; in this case "gorm.io/driver/sqlite" to connect to a SQLite DB.

The next step is to initialize Go modules for the example-gorm project and then execute go mod tidy to install the gorm and sqlite packages:

$ go mod init example-gorm
$ go mod tidy

Good going! You're set to start using GORM to interact with the chinook database.

Declaring models

GORM allows you to declare a new model and then create a new table using it or declare a model and map it to an existing database table. In this introductory topic, you'll only look at the latter: you'll map the artists table of the chinook database to the Artist model struct within main.go.

By convention, GORM requires model names and field names within the model structs to follow PascalCase naming conventions, with the first letter of each word capitalized. Additionally, GORM requires pluralized table names, for example artists, and expects both table names and table columns to follow snake_case naming conventions. If the table columns do not have snake_case names, you can use the gorm:"column:x" struct tag to override the snake_case naming convention requirement.

The artists table has two columns, ArtistId and Name, so you can add the ArtistId and Name fields respectively to the Artist model struct. Since the artists table column names do not follow snake_case naming conventions, you'll need to use the column struct tag to override the snake_case name requirement:

type Artist struct {
    ArtistId uint `gorm:"column:artistid"`
    Name     string
}

Take notice that if you didn't add the `gorm:"column:artistid"` struct tag to the ArtistId field, GORM would then use the column ArtistId as artist_id and since the artist_id column doesn't exist; you would get the no such column: error when executing queries:

2022/06/27 20:51:46 ... no such column: artists.artist_id
[0.000ms] [rows:0] SELECT * FROM artists ORDER BY artists.artist_id LIMIT 1

Now you might be wondering why it is not required to add the `gorm:"column:name"` struct tag to the Name field. Since Name is a simple field name and not a compound field name like ArtistId, it doesn't require any additional struct tags because it will just be used as name in lowercase (without any _ underscores).

Naming conventions

When working with an ORM library such as GORM, certain naming conventions can be used interchangeably between ORM and database elements.

GORM Naming Convention

SQL Naming Convention

Model(s) Table(s)
Field(s) Column(s)
Object(s) Record(s)

To better understand the interchangeable naming conventions, let's take a look at some examples:

  • When referring to the Artist model, it is the same as referring to the artists table;
  • When referring to the Name field, it is the same as referring to the name column;
  • When referring to an object in the Artist model, it is the same as referring to a record in the artists table.

Connecting to an existing database

Now that you've learned about interchangeable naming conventions and declared the Artist model struct, the next step is to use the gorm.Open() function to connect to the chinook database:

...

func main() {
    db, err := gorm.Open(sqlite.Open("chinook.db"), &gorm.Config{})
    if err != nil {
        log.Fatal(err)
    }
}

The gorm.Open() function takes two arguments: a Dialector interface that the sqlite database driver sqlite.Open() function returns, and an Option interface, which, by GORM conventions, is a pointer to the gorm.Config struct.

In short, the gorm.Config struct has a series of fields that allow you to enable or disable configuration options for the GORM connection.

Retrieving records from the database

Now that you have connected to the chinook database, it is time to perform your first query and retrieve data from the artists table. GORM provides three simple methods to retrieve a single record from the database:

  • db.First() returns the first record, ordered by the primary key;

  • db.Take() returns one record with no specified order;

  • db.Last() returns the last record, ordered by the primary key in descending order.

Now go ahead and use these methods within your Go program:

...

func main() {
    ... // Connect to the chinook database using gorm.Open()

    var firstArtist, takeArtist, lastArtist Artist
    db.First(&firstArtist) // SELECT * FROM artists ORDER BY artistid LIMIT 1;
    db.Take(&takeArtist)   // SELECT * FROM artists LIMIT 1;
    db.Last(&lastArtist)   // SELECT * FROM artists ORDER BY artistid DESC LIMIT 1;

    fmt.Println(firstArtist)  // {1 AC/DC}
    fmt.Println(takeArtist)   // {1 AC/DC}
    fmt.Println(lastArtist)   // {275 Philip Glass Ensemble}
}

As you can see, db.First(), db.Take(), and db.Last() represent different SQL query statements that retrieve a single record from the artists table.

And to retrieve all records from the artists table, you can use the db.Find() method:

...

func main() {
    ... // Connect to the chinook database using gorm.Open()

    var artists []Artist
    db.Find(&artists) // SELECT * FROM artists;

    for _, artist := range artists {
        fmt.Println(artist)
    }
}

// Output:
// {1 AC/DC}
// {2 Accept}
// {3 Aerosmith}
// ...

Conclusion

This topic has introduced you to the basics of GORM. Now you can do many things:

  • Install GORM into your Go project and connect to an existing SQLite, MySQL, PostgreSQL, or SQL Server database;
  • Declare a model struct and map it to a table from an existing database;
  • Retrieve a single record or all records from a table;

If you want to learn more, please look at GORM's official docs. Now, go ahead and solve some theory and coding tasks!

10 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo