If you are not a medium member, you can still read this story here
Part 5: Link
Part 7: Link
Prerequisites
- Basic knowledge of Go.
- Basic SQL knowledge and understanding of databases
Table of Contents:
- Serialization
- Interacting With Databases in Golang
- Relational Databases
- Go Object Relational Mapper (ORM)
- NoSQL Databases
Serialization
Serialization is the process of converting an object into a format that can be easily stored or transmitted and then reconstructed later. In Go, serialization is commonly done using the JSON format due to its simplicity and readability.
JSON in Go
JSON (JavaScript Object Notation) is a lightweight data interchange format that is easy to read and write for humans and machines.
Go provides a built-in package, encoding/json, which includes functionalities for JSON encoding (marshaling) and decoding (unmarshaling).
Encoding JSON
Encoding is the process of converting Go data structures into JSON format.
- Define a struct that you want to encode into JSON. For example, let’s create a
Personstruct:
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
Address string `json:"address,omitempty"` // omitempty omits this field if it's empty
}2. Use the json.Marshal function to encode a Go struct into JSON format:
func main() {
person := Person{Name: "John Doe", Age: 30, Email: "john.doe@example.com"}
jsonData, err := json.Marshal(person)
if err != nil {
fmt.Println("Error encoding JSON:", err)
return
}
fmt.Println(string(jsonData)) // Output: {"name":"John Doe","age":30,"email":"john.doe@example.com"}
}Decoding JSON
Decoding is the process of converting JSON data into Go data structures.
To decode JSON into a struct, use the json.Unmarshal function. Here's an example of decoding a JSON string into the Person struct:
func main() {
jsonString := `{"name":"Jane Doe","age":25,"email":"jane.doe@example.com"}`
var person Person
err := json.Unmarshal([]byte(jsonString), &person)
if err != nil {
fmt.Println("Error decoding JSON:", err)
return
}
fmt.Printf("Decoded Person: %+v\n", person) // Output: Decoded Person: &{Name:Jane Doe Age:25 Email:jane.doe@example.com}
}The same encoding and decoding can be done for array/ slices as well.
Custom JSON Marshaling
You can implement the json.Marshaler interface to customize how a type is marshaled into JSON.
type CustomPerson struct {
Name string
Age int
Email string
}
func (p CustomPerson) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
FullName string `json:"full_name"`
Age int `json:"age"`
}{
FullName: p.Name,
Age: p.Age,
})
}
func main() {
person := CustomPerson{Name: "Grace", Age: 31, Email: "grace@example.com"}
jsonData, err := json.Marshal(person)
if err != nil {
fmt.Println("Error encoding JSON:", err)
return
}
fmt.Println(string(jsonData)) // Output: {"full_name":"Grace","age":31}
}We have written a method over the CustomPerson struct that is used to marshal it instead.
Interacting With Databases in Golang:
Databases are a fundamental component of many applications, providing a way to store, retrieve, and manipulate data efficiently. In the world of Go (Golang), interacting with databases — both SQL (relational) and NoSQL (non-relational) — is streamlined through powerful libraries and tools that facilitate seamless integration and high performance.
Relational Databases:
SQL databases, such as PostgreSQL, MySQL, and SQLite, use structured query language (SQL) for defining and manipulating data. These relational databases enforce a schema that organizes data into tables, where relationships between data entities can be explicitly defined through foreign keys. In Go, the database/sql package serves as a generic interface for SQL databases, enabling developers to execute queries, manage transactions, and handle connections with ease.
Additionally, popular libraries such as pgx and gorm provide advanced features like connection pooling, ORM capabilities, and built-in support for migrations, making it simpler to work with SQL databases in Go applications.
We will be using Postgres to for our use cases. PostgreSQL, often referred to as “Postgres,” is an open-source relational database management system (RDBMS) that emphasizes extensibility and SQL compliance. It’s known for its reliability, robustness, and performance, making it a popular choice for various applications.
You can install your own instance of Postgres (or MySQL etc.), or use a cloud hosted one available here.
To connect to PostgreSQL and perform database operations in Golang, you typically follow a series of steps: install the necessary packages, establish a connection, create a database, and define tables. Here’s a detailed guide to help you through the process.
Install the PostgreSQL Driver
First, you need to install the PostgreSQL driver for Go. The most commonly used driver is pgx, but you can also use the pq driver. Here’s how to install the pgx driver:
go get github.com/jackc/pgx/v4Establish a Connection
To connect to a PostgreSQL database, you’ll need to use the driver to create a connection pool. Here’s how you can establish a connection:
package main
import (
"context"
"fmt"
"log"
"github.com/jackc/pgx/v4"
)
func main() {
// Database connection parameters
connString := "postgres://username:password@localhost:5432"
// Connect to PostgreSQL
conn, err := pgx.Connect(context.Background(), connString)
if err != nil {
log.Fatalf("Unable to connect to database: %v\n", err)
}
defer conn.Close(context.Background())
fmt.Println("Successfully connected to PostgreSQL!")
}Connection String:
username: Your PostgreSQL username.password: Your PostgreSQL password.localhost: The server where your database is hosted (use an IP address or hostname).5432: The port number for PostgreSQL (default is 5432).
Create a Database
To create a new database in PostgreSQL, you need to execute a SQL command. Here’s how to do that in Go:
// Create a new database
dbName := "mydatabase"
_, err = conn.Exec(context.Background(), fmt.Sprintf("CREATE DATABASE %s", dbName))
if err != nil {
log.Fatalf("Failed to create database: %v\n", err)
}
fmt.Printf("Database %s created successfully!\n", dbName)Connect to the New Database
After creating the database, you need to connect to it. Update your connection string to include the database name:
// Connect to the new database
newConnString := fmt.Sprintf("postgres://username:password@localhost:5432/%s", dbName)
newConn, err := pgx.Connect(context.Background(), newConnString)
if err != nil {
log.Fatalf("Unable to connect to database %s: %v\n", dbName, err)
}
defer newConn.Close(context.Background())
fmt.Printf("Successfully connected to database %s!\n", dbName)Create Tables
Once connected to the database, you can create tables using SQL commands:
// Create a new table
createTableSQL := `
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
`
_, err = newConn.Exec(context.Background(), createTableSQL)
if err != nil {
log.Fatalf("Failed to create table: %v\n", err)
}
fmt.Println("Table 'users' created successfully!")CRUD Operations:
Now let’s implement CRUD (Create, Read, Update, Delete) operations in Golang using the pgx driver. We’ll use the users table that we previously created for these operations.
// Create a new user
func createUser(conn *pgx.Conn, name string, email string) error {
_, err := conn.Exec(context.Background(), "INSERT INTO users (name, email) VALUES ($1, $2)", name, email)
return err
}
// Read users
func readUsers(conn *pgx.Conn) error {
rows, err := conn.Query(context.Background(), "SELECT id, name, email, created_at FROM users")
if err != nil {
return err
}
defer rows.Close()
fmt.Println("Users:")
for rows.Next() {
var id int
var name, email string
var createdAt string
if err := rows.Scan(&id, &name, &email, &createdAt); err != nil {
return err
}
fmt.Printf("ID: %d, Name: %s, Email: %s, Created At: %s\n", id, name, email, createdAt)
}
return nil
}
// Update a user's email
func updateUserEmail(conn *pgx.Conn, id int, newEmail string) error {
_, err := conn.Exec(context.Background(), "UPDATE users SET email = $1 WHERE id = $2", newEmail, id)
return err
}
// Delete a user
func deleteUser(conn *pgx.Conn, id int) error {
_, err := conn.Exec(context.Background(), "DELETE FROM users WHERE id = $1", id)
return err
}Go Object Relational Mapper:
Using an Object-Relational Mapping (ORM) library can greatly simplify database interactions in Golang. In this guide, we will use GORM, a popular ORM for Go, to perform CRUD operations on a PostgreSQL database. GORM abstracts the SQL syntax and allows us to interact with the database using Go structs and methods.
Install GORM and PostgreSQL Driver
First, you need to install GORM and the PostgreSQL driver for GORM. You can do this using the following commands:
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgresDefine Your User Model
Next, you need to define a Go struct that represents your users table. GORM will use this struct to map to the database table.
package main
import (
"time"
"gorm.io/gorm"
)
// User represents the user model
type User struct {
ID uint `gorm:"primaryKey"` // Auto-incrementing primary key
Name string `gorm:"not null"` // Name field, cannot be null
Email string `gorm:"unique;not null"` // Unique email, cannot be null
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"` // Created timestamp
}Establish a Database Connection
Now, let’s set up a connection to the PostgreSQL database using GORM:
package main
import (
"fmt"
"log"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func main() {
// Database connection parameters
dsn := "host=localhost user=username password=password dbname=mydatabase port=5432 sslmode=disable"
// Connect to the database
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalf("Failed to connect to database: %v\n", err)
}
fmt.Println("Successfully connected to PostgreSQL!")
}Migrate the Schema
GORM can automatically create the users table based on the User struct you defined. This is called "migrating the schema."
// Migrate the schema
err = db.AutoMigrate(&User{})
if err != nil {
log.Fatalf("Failed to migrate database: %v\n", err)
}
fmt.Println("Database migrated successfully!")CRUD Operations
Now, let’s implement the CRUD operations using GORM.
// Create a new user
func createUser(db *gorm.DB, name string, email string) error {
user := User{Name: name, Email: email}
result := db.Create(&user)
return result.Error
}
// Read users
func readUsers(db *gorm.DB) ([]User, error) {
var users []User
result := db.Find(&users)
return users, result.Error
}
// Update a user's email
func updateUserEmail(db *gorm.DB, id uint, newEmail string) error {
var user User
if err := db.First(&user, id).Error; err != nil {
return err
}
user.Email = newEmail
return db.Save(&user).Error
}
// Delete a user
func deleteUser(db *gorm.DB, id uint) error {
return db.Delete(&User{}, id).Error
}NoSQL Databases
NoSQL databases like MongoDB, Cassandra, and Redis on the other hand prioritize flexibility and scalability, often allowing for unstructured or semi-structured data storage. They are designed to handle large volumes of data across distributed systems and support various data models, including document, key-value, graph, and column-family.
In Golang, there are specialized drivers and libraries available for interacting with NoSQL databases. For instance, the mongo-go-driver enables easy communication with MongoDB, while go-redis facilitates interactions with Redis. These libraries typically abstract the complexities of database interactions, providing simple methods to perform CRUD operations and query data.
MongoDB Cloud also offers a free tier of hosted MongoDB, which you can use for your learning without installing anything locally.
Install MongoDB Go Driver
First, you need to install the MongoDB Go driver. You can do this by running the following command:
go get go.mongodb.org/mongo-driver/mongo
go get go.mongodb.org/mongo-driver/mongo/optionsConnect to MongoDB
To interact with MongoDB, you first need to establish a connection. Below is how to connect to a MongoDB database:
package main
import (
"context"
"fmt"
"log"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
// Define MongoDB connection URI
clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
// Connect to MongoDB
client, err := mongo.Connect(context.TODO(), clientOptions)
if err != nil {
log.Fatalf("Failed to connect to MongoDB: %v\n", err)
}
// Ping the primary
err = client.Ping(context.TODO(), nil)
if err != nil {
log.Fatalf("Failed to ping MongoDB: %v\n", err)
}
fmt.Println("Successfully connected to MongoDB!")
defer client.Disconnect(context.TODO())
}Define a MongoDB Document Model
In MongoDB, documents are typically represented as BSON (Binary JSON) objects. Below is a Go struct that represents a user in our MongoDB collection:
// User represents the user document in MongoDB
type User struct {
ID string `bson:"_id,omitempty"` // MongoDB ID (optional, will be generated)
Name string `bson:"name"` // User name
Email string `bson:"email"` // User email
}Performing CRUD Operations
Now, let’s implement CRUD operations for our User model.
// CreateUser inserts a new user into the users collection
func CreateUser(collection *mongo.Collection, user User) error {
_, err := collection.InsertOne(context.TODO(), user)
return err
}
// ReadUsers retrieves all users from the users collection
func ReadUsers(collection *mongo.Collection) ([]User, error) {
var users []User
cursor, err := collection.Find(context.TODO(), bson.M{})
if err != nil {
return nil, err
}
defer cursor.Close(context.TODO())
for cursor.Next(context.TODO()) {
var user User
if err := cursor.Decode(&user); err != nil {
return nil, err
}
users = append(users, user)
}
return users, nil
}
// UpdateUser updates a user's email in the users collection
func UpdateUser(collection *mongo.Collection, id string, newEmail string) error {
filter := bson.M{"_id": id}
update := bson.M{"$set": bson.M{"email": newEmail}}
_, err := collection.UpdateOne(context.TODO(), filter, update)
return err
}
// DeleteUser deletes a user from the users collection
func DeleteUser(collection *mongo.Collection, id string) error {
filter := bson.M{"_id": id}
_, err := collection.DeleteOne(context.TODO(), filter)
return err
}Conclusion:
Database management systems are integral to modern application development, enabling us to efficiently manage and manipulate data to meet diverse requirements. We will be utilizing these and other databases in our future projects, ensuring we leverage the strengths of each to build robust, scalable, and efficient applications. Happy coding!
“The happiest people are not the ones who achieve the most. They are the ones who spend more time than others in a state of flow of work.”
― Hector Garcia Puigcerver, Ikigai: The Japanese Secret to a Long and Happy Life




