1. Building an API using Golang and PostgreSQL
Project link:
https://github.com/IamFrost/api-2-build-a-restapi-postgres-golang
Pre-requirements:
PostgreSQL must be installed on your computer. You must have pgAdmin (a part of
PostgreSQL – mine is pgAdmin4)
Go (Golang) must be installed. You must know the Go directory (for my case – C:Go)
and you have to place your project under src folder of Go directory. (mine is C:Gosrc)
When you start building go project, make a. env file under your root directory, then run
go mod init in terminal, to initialize vendor file for dependencies.
Building database:
Connection parameters:
For PostgreSQL. Here are my connection parameter values:
Default postgres admin username: postgres
Default postgres admin password: postgres
Host name: localhost
Database name: items
Just remember this (your) parameter values, because you need to use it in your Golang code.
SQL:
create database items;
create table purchases
(
item_id int unique,
item_name varchar(255),
item_quantity float,
item_rate float,
item_purchase_date date
);
2. insert into purchases values(1,'pencil',20,5.5,'2020-05-09');
insert into purchases values(2,'pen',10,5.5,'2020-04-04');
insert into purchases values(3,'rubber',5,5.5,'2019-01-01');
Building Golang API
Imports
We need these following imports
package main
import (
"database/sql"
"encoding/json"
"fmt"
"github.com/gorilla/mux"
_ "github.com/lib/pq"
"github.com/rs/cors"
"log"
"net/http"
"strconv"
)
Note:
write package name before import
"database/sql"
for queries
"encoding/json"
for data (string) to JSON
"fmt"
for printing anything
"github.com/gorilla/mux"
for router
_ "github.com/lib/pq"
for PostgreSQL queries
"github.com/rs/cors"
for accepting CORS request
"log"
for logging error
"net/http"
for read http request and write http response
3. "strconv"
for int (or something to string)
Make a structure to format data
Remember 3 things:
You must use attribute name (id/name/…)
Try to always declare string type, even if you need others (int/float/…). You will see I
converted later. You can easily convert it later anywhere. As we are using JSON, other
than string will need to check a lot of conditions and will make code complex
Watch out for this `json:"item_id"`. You need to follow capitalization here exactly. Also
don’t forget the quote marks
type Purchases struct {
ID string `json:"item_id"`
Name string `json:"item_name"`
Quantity string `json:"item_quantity"`
Rate string `json:"item_rate"`
Date string `json:"item_purchase_date"`
}
Make an array of structure to save data
var allPurchases []Purchases
Create a database connection function and return the connection
func createConnection() *sql.DB {
db, err := sql.Open("postgres", "postgres://postgres:postgres@localhost/items?sslmode=disabl
e")
if err != nil {
fmt.Println(`Could not connect to db`)
panic(err)
}
err = db.Ping()
if err != nil {
panic(err)
}
return db
}
4. Note:
Watch out for this: "postgres://postgres:postgres@localhost/items?sslmode=disable")
Like I said before, you need to use your own values of connection parameters here.
Format: username:password@hostname/databaseName
By default, keep sslmode disable.
CRUD, Let’s get into it
A short summary about crud in API
Basically, you need to make 5 API functions for CRUD. 4 for create, read, update, delete. Well 1
more for read also. 2 types of read :
read all: select * from x
read one: select * from x where id=1
The second type of crud can be customized in many ways. You can also use it in many ways like
search.
Read All
func getPurchases(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
db := createConnection()
defer db.Close()
rows, err := db.Query(`SELECT * FROM purchases`)
if err != nil {
panic(err)
}
fmt.Println(rows)
var col1 string
var col2 string
var col3 string
var col4 string
var col5 string
allPurchases = nil
for rows.Next() {
rows.Scan(&col1, &col2, &col3, &col4, &col5)
allPurchases = append(allPurchases, Purchases{ID: col1, Name: col2, Quantity: col3, Rate:
col4, Date: col5})
}
5. json.NewEncoder(w).Encode(allPurchases)
}
Note:
In crud, all the functions are almost identical, so I will explain 1 function, others are less
complex.
Btw, make your columns string, you are working with JSON, so don’t use other type, it
will need a lot of conversion and will make code complex
For each row, I am appending all values to a variable after getting from database, then I
need to make it to JSON
So here are the steps: (you [the Golang coder / Golang API] are the server)
i) let’s set up your server for accepting json
w.Header().Set("Content-Type", "application/json")
ii) cross platform origin request
w.Header().Set("Content-Type", "Access-Control-Allow-Origin")
A client (angular or others) requested you JSON data
Then you get his request on Golang [watch this in code: r *http.Request]
Then you understand his request [this is not in Golang code, this is in client code (get,
put, post, delete methods)], but your Golang API will know by which method it is called]
Then you execute query according to your client request.
rows, err: = db.Query(`SELECT * FROM purchases`)
Then you get query result row by row and save it to a variable
Finally, you send the data to client making JSON, remember client requested you JSON,
so whatever datatype it is, make it to string, save it to a string variable and then encode
(convert) it to JSON
Follow the steps serially
Summary is, client request for JSON data and we as server send JSON data. All other
steps are intermediate
The steps are also in other functions as well, so I will not repeat
Read One
func getPurchase(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
db := createConnection()
defer db.Close()
params := mux.Vars(r)
id, err := strconv.Atoi(params["id"])
if err != nil {
log.Fatalf("Unable to convert the string into int. %v", err)
}
6. rows := db.QueryRow(`SELECT * FROM purchases WHERE item_id=$1`, id)
var col1 string
var col2 string
var col3 string
var col4 string
var col5 string
allPurchases = nil
rows.Scan(&col1, &col2, &col3, &col4, &col5)
// fmt.Println(col1, col2, col3, col4)
allPurchases = append(allPurchases, Purchases{ID: col1, Name: col2, Quantity: col3, Rate: co
l4, Date: col5})
json.NewEncoder(w).Encode(allPurchases)
}
Note:
1. Almost the same, just a where condition on query
Create
func createPurchase(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
var p Purchases
err := json.NewDecoder(r.Body).Decode(&p)
fmt.Println("from api : in create purchase : this is post p : ", p)
fmt.Println("from api : in create purchase : this is error: ", err)
db := createConnection()
defer db.Close()
row, err := db.Exec(`INSERT INTO purchases (item_id, item_name, item_quantity, item_rate,
item_purchase_date) VALUES ($1, $2, $3, $4, $5)`, p.ID, p.Name, p.Quantity, p.Rate, p.Date)
if err != nil {
log.Fatalf("Unable to execute the query. %v", err)
}
fmt.Println(row)
fmt.Printf("Inserted a single record %v", p.ID)
}
7. Note:
JSON decoder decodes (gets) data from client, here in p variable, we get it and then
extract each value and push each value to query.
Update
// Update post
func updatePurchase(w http.ResponseWriter, r *http.Request) {
// w.Header().Set("Content-Type", "application/json")
// w.Header().Set("Access-Control-Allow-Origin", "*")
var p Purchases
err := json.NewDecoder(r.Body).Decode(&p)
fmt.Println("this is post p : ", p)
fmt.Println("this is error: ", err)
// useridConv, err := strconv.Atoi(p.Userid)
// if err != nil {
// panic(err)
// }
// idConv, err := strconv.Atoi(p.ID)
// if err != nil {
// panic(err)
// }
// fmt.Println("here is id1: ", useridConv)
// fmt.Println("here is id2: ", idConv)
//get the postid from the request params, key is "id"
params := mux.Vars(r)
//convert the id type from string to int
id, err := strconv.Atoi(params["id"])
if err != nil {
log.Fatalf("Unable to convert the string into int. %v", err)
}
fmt.Println("this is id: ", id)
// fmt.Println()
//create the postgres db connection
db := createConnection()
//close the db connection
defer db.Close()
8. // fmt.Println(`from api : update purchase : UPDATE purchases SET item_id=$1, item_name=
$2, item_quantity=$3, item_rate=$4, item_purchase_date=$5 WHERE item_id=$6`, p.ID, p.Na
me, p.Quantity, p.Rate, p.Date, id)
row, err := db.Exec(`UPDATE purchases SET item_id=$1, item_name=$2, item_quantity=$3,
item_rate=$4, item_purchase_date=$5 WHERE item_id=$6`, p.ID, p.Name, p.Quantity, p.Rate,
p.Date, id)
if err != nil {
log.Fatalf("Unable to execute the query. %v", err)
}
fmt.Println(row)
fmt.Printf("Inserted a single record %v", p.ID)
}
Note:
Our first and final output is JSON string, but we might need type conversion in the
middle of it. You can see I convert string to int.
Delete
func deletePurchase(w http.ResponseWriter, r *http.Request) {
// w.Header().Set("Access-Control-Allow-Origin", "*")
// w.Header().Set("Access-Control-Allow-Methods", "DELETE")
// w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
// w.Header().Add("Content-Type", "application/json")
// create the postgres db connection
db := createConnection()
// close the db connection
defer db.Close()
// get the postid from the request params, key is "id"
params := mux.Vars(r)
// convert the id type from string to int
id, err := strconv.Atoi(params["id"])
fmt.Println(id)
if err != nil {
log.Fatalf("Unable to convert the string into int. %v", err)
}
9. rows, err := db.Exec(`DELETE FROM purchases WHERE item_id=$1`, id)
if err != nil {
panic(err)
}
fmt.Println(rows)
}
Main function
// Main function
func main() {
hostname, port, username, password, database)
// Init router
r := mux.NewRouter()
// Route handles & endpoints
r.HandleFunc("/purchases", getPurchases).Methods("GET")
r.HandleFunc("/purchases/{id}", getPurchase).Methods("GET")
r.HandleFunc("/purchases", createPurchase).Methods("POST")
r.HandleFunc("/purchases/{id}", updatePurchase).Methods("PUT")
r.HandleFunc("/purchases/{id}", deletePurchase).Methods("DELETE")
// Start server
handler:= cors.AllowAll().Handler(r)
// handler:= cors.Default().Handler(r)
log.Fatal(http.ListenAndServe(":3000", handler))
Note:
So in main function, we need to setup router in API, here we are setting route for 5 of our
crud functions
Then we allow all routes to pass through CORS
Finally, we run it in 3000 port, here, if error found, it will let us know
10. Execute
Note:
Your project must be under you [go installation folder]/src
(for my case : C:/Go/src)
For the first time, run
i) go mod vendor (for downloading dependencies)
ii) then go build (for building exe file)
iii) then go run main.go (for executing main.go file)
Next time, you just need to run “go run main.go”,