loading...
Cover image for Basic Go HTTP server with Postgres

Basic Go HTTP server with Postgres

bmpickford profile image Benjamin ・4 min read

I've found myself creating simple Golang microservices using the same basic design to start out. In this post I'll go through the steps from scratch to help create a simple starting point for a Go microservice or API with the ability to create a customer

Throughout this brief tutorial, I have purposely tried to keep it as minimal as possible to get to know the native packages (net/http, encoding/json)


Basic HTTP server

So presuming you have to go environment all setup, let's start by creating a main.go file in our new project with a basic http server

package main

import (
    "log"
    "net/http"
)

const port = "8080"

func main() {
    log.Fatal(http.ListenAndServe(":8080", nil))
}

From here, if you run go run . you shouldn't get any errors and should be a good indicator your go setup is correct

So now we have a basic server, it's time to setup a handler to check our configurations are all good. Here we will create a simple response for GET /. To do this, add this handler function to main.go:

func handleHomePage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, the site is running :)")
}

And then this to your main() function:

http.HandleFunc("/", homePage)

Your main.go file should now look like:

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", handleHomePage)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func handleHomePage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, the site is running :)")
}

Now to test this works, hit the server with curl using curl -X GET 0.0.0.0:8080/ and your response should be Hello, the site is running :)


Postgres setup

So now we have a basic HTTP server, it's time to add our postgres DB. Assuming you have postgres already installed, lets add a Database and a table. To do this, open postgres in the command line and execute:

CREATE DATABASE go_project;
CREATE TABLE customer (id SERIAL, username VARCHAR NOT NULL);

Go/Postgres connection

To setup our connection, we will be using the pq package for golang (use go get github.com/lib/pq to download the package). We will not create a new file called postgres.go, that will contain a singleton that will be used in our subsequent files to access the database.

package main

import (
    "database/sql"
    "log"

    _ "github.com/lib/pq"
)

var db *sql.DB

func GetDB() *sql.DB {
    var err error

    if db == nil {
        connStr := "user=postgres dbname=go_project"
        db, err = sql.Open("postgres", connStr)
        if err != nil {
            panic(err)
        }
    }

    return db
}

What we are doing here, is creating a connection using the name of our database and a user for our postgres instance. This might change depending on your setup, and may need some authentication


Finishing our handlers

Now it's time to expand our application to include some real logic. To start, we have to create our Customer struct. Firstly though, we have to create a new file for our handlers. I have created one called handler.go but you can name it anything you like. In this file, add the package name and our new struct as follows:

package main

type Customer struct {
    ID       int
    Username string
}

Now it's time to add our handler. For this, we need to create a decoder that will decode the body of our request (which will be JSON format) into our struct. This can be done using the following snippet:

func handleCustomerPost(w http.ResponseWriter, r *http.Request) {
    var customer Customer

    decoder := json.NewDecoder(r.Body)
    err := decoder.Decode(&customer)
    if err != nil {
        panic(err)
    }
    w.WriteHeader(201)
}

Now we have the ability to decode a request body into a Customer model, it's time to add our handler to main.go as we did with our root response. This time though, we have to filter the POST request because the native http package doesn't have the functionality for that. If you add gorilla mux, this can be handled for you, but for this case I want to keep it as minimal as possible. So we need to add another handler in main, as follows:

func handleCustomerRequest(w http.ResponseWriter, r *http.Request) {
    if r.Method == http.MethodPost {
        handleCustomerRequest(w, r)
    }
    w.WriteHeader(201)
}

and a call to it in the main() function as such:

http.HandleFunc("/customer", handleCustomerRequest)

Linking it all together

Now we need to create our query for saving a customer. To do this, we need to create a new file (i called mine database.go), and add the following function that takes in a username

package main

func SaveCustomer(username string) error {
    db := GetDB()
    _, err := db.Exec("INSERT INTO customer (username) VALUES($1)", username)
    return err
}

And now in handler.go, call this function using the customers username as follows:

err = SaveCustomer(customer.Username)
if err != nil {
    panic(err)
}

Now it's time to test our functionality so far. So start up your go service using go run . again. Now if you hit your endpoint, you should get a 201, and a customer with an ID and username in your database

Conclusion

I hope you have learnt something about creating a basic http server in golang. If you want to see the full codebase, see https://github.com/bmpickford/goHttpServer

Discussion

pic
Editor guide
Collapse
mmasouras profile image
Michael

Who is closing the connection?