GoLang API + Postgres for Beginners: Step 2

GoLang API + Postgres for Beginners: Step 2

Hello folks... Let's start the second lesson of this great tutorial for beginners. If you accidentally landed here and don't know where we started, read the post where we started our project at the following link:

https://phantodev.hashnode.dev/golang-api-postgres-for-beginners-step-1

GIT REPO: https://github.com/phantodev/api-with-go-postgres
BRANCH: step-1

Let's remember our structure

api-with-go-postgres/
├── database/
│   ├── connection.go
├── products/
│   ├── create_product.go
│   ├── delete_product_by_id.go
│   ├── update_product_by_id.go
│   ├── get_all_products.go
├── auth/
│   ├── forgot_password.go
│   ├── login.go
│   ├── reset_password.go
├── main.go

1º Step: Now, we need to create the code to connect to local PostgreSQL, right? So, please install PostgreSQL on your local machine to follow this tutorial. We are going to use the pgAdmin GUI to manage our local PostgreSQL, okay? Please with pgAdmin create a database with this name: api-with-go

2º Step: Now you need to create a table in Postgres with this fields folks:

3º Step: After saving the table creation, we need install a new module to connect in PostgreSQL with go. Run this command line below in your terminal to install.

go get github.com/lib/pq

4º Step: Now go to your connectio.go file and please put this code inside:

package database

import (
    "database/sql" // Package to use SQL DATABASE in GO.
    "fmt" // Package to print a message in terminal

    _ "github.com/lib/pq" // Package to work with PostgreSQL
)

func Connect() {
    // ATENTION HERE: You need change for your postgres user and password below, okay?
    connStr := "user=postgres password=123456 dbname=api-with-go sslmode=disable"
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        panic(err)
    }
    defer db.Close()

    // Setting Pool Connections
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(0)

    // Testing Connection
    err = db.Ping()
    if err != nil {
        panic(err)
    }

    fmt.Println("Connected in PostgreSQL...")
}

5º Step: Now we need create models folder in root folder, with the file user.go inside with the code below. In applications, creating a model allows developers to define the structure and behavior of the data used within the application. Models serve as blueprints for the data, specifying the attributes and relationships that the data should have. By creating a model, developers can organize and manage data more effectively, ensuring consistency and facilitating interactions with the data throughout the application's lifecycle. Models help separate concerns within the application, making it easier to understand, maintain, and extend over time.

// We are creating a package called models
package models
// And we are creating a struct type called Products
// ATTENTION: For this tutorial we are creating a very simple model. 
// You can create a model with many fields if you want.
type Products struct {
    ID          int    `json:"id"`
    Name        string `json:"name"`
    Description string `json:"description"`
}

Observation: The struct tags json:"field_name" are used to specify how the fields of the struct are mapped to corresponding fields in JSON. For example, in the ID field, the tag json:"id" indicates that when serializing or deserializing the struct to/from JSON, the ID field will be mapped to the key "id" in the JSON. The same applies to the Name and Description fields, which will be mapped to the keys "name" and "description" in the JSON, respectively

6º Step: Now let's make a simple adjustment to our connection.go file, we need to return an open db connection to main.go and it will pass it on to the rest of the application. Last line in this code: return db

package database

import (
    "database/sql"
    "fmt"

    _ "github.com/lib/pq"
)

func Connect() *sql.DB {
    connStr := "user=postgres password=123456 dbname=api-with-go sslmode=disable"
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        panic(err)
    }
    defer db.Close()

    // Setting Pool Connections
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(0)

    // Testing Connection
    err = db.Ping()
    if err != nil {
        panic(err)
    }

    fmt.Println("Connected in PostgreSQL...")

    return db // WE ARE ADDING NOW THIS LINE
}

7º Step: Now we gonna create all the Create Product logic function, please open the file create_product.go inside product folder and put this code below:

package products

import (
    "api-with-go-postgres/models" // Importing models here
    "database/sql"
    "encoding/json"
    "net/http"
)

func CreateProduct(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Create a variable to store the data from the request body
        var product models.Products
        // Decode the data from the request body into the product variable
        err := json.NewDecoder(r.Body).Decode(&product)
        if err != nil {
            http.Error(w, "Error to decode the request", http.StatusBadRequest)
            return
        }

        // Execute SQL query to insert the product into the database
        _, err = db.Exec("INSERT INTO products (name, description) VALUES ($1, $2)", product.Name, product.Description)
        if err != nil {
            http.Error(w, "Error to insert Product in database", http.StatusInternalServerError)
            return
        }

        // Response with status code 201 (Created) and a message indicating that the product was inserted successfully
        w.WriteHeader(http.StatusCreated)
        w.Write([]byte("Product created in database"))
    }
}

Check my explanation about the code above:

a) The CreateProduct function takes a db database connection as a parameter and returns an HTTP handler (http.HandlerFunc).

b) Inside the HTTP handler, you decode the request body data into a Product structure.

c) Then you run a SQL query to insert the product into the database using the db connection.

d) If any errors occur during the decoding or insertion process, you return an appropriate HTTP error.

f) If the insertion is successful, you respond with an HTTP status code of 201 (Created) and a message indicating the success of the operation.

The process is very simple, right my friends?

8º Step: Now we need make some changes in main.go file. We for now remove auth package, we gonna use later.

package main

import (
    "api-with-go-postgres/database"
    "api-with-go-postgres/products"
    "log" // Add this package to use if we have some error to start server
    "net/http" // Add this package to use http Handlers
)

func main() {
    db := database.Connect()

    // Creating a Handler Route to create a product
    http.HandleFunc("/products/create", products.CreateProduct(db))

    // Starting the server and if we have some error print a log.Fatal()
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal("Error to start HTTP SERVER:", err)
    }
}

9º Step: Next pass... let's run our program in terminal:

go run main.go

10º Step: I'm using a THUNDER CLIENT in VSCODE to test a REQUEST in my route. It's very simples like POSTMAN and INSOMNIA. You just need send a POST REQUEST in our route with JSON in body:

11º Step: If everything went well you should see the message "Product created in database" when returning your request. Now you can open pgAdmin and view your data inserted in the Products table.

So that's it for today folks. Follow the next steps here. We will learn how to create large, complete projects using Javascript technology for the Front-End and GoLang technology for the Back-End.

Until next time!

GIT REPO: github.com/phantodev/api-with-go-postgres
BRANCH: step-2

Eduardo Burko - Fullstack Developer

Get in touch in phantodev.com.br