Create a server with PostgreSQL in Go - Part[1/3] of Go Authentication series

mdfaizan7

Faizan

Posted on October 31, 2020

Create a server with PostgreSQL in Go - Part[1/3] of Go Authentication series

In this tutorial, you will learn how to make an authentication boilerplate with Go. We will use GoFiber and PostgreSQL. We will go from the ground up to build this project.

Getting Started

You need to have at least go 1.11 installed on your system we will use go mod to support versioning.

1. Create a Simple Server

Let us first create a simple Go API to start with. We will use Fiber which is an Express-inspired web framework written in Go.

So first of all, create a new directory called go-authentication-boilerplate and then cd into it.
After that run go mod init to build the go modules inside our app. More information on go modules can be found here.

Now, create a file named: main.go inside our home directory.

package main

import (
    "log"

    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
)

// CreateServer creates a new Fiber instance
func CreateServer() *fiber.App {
    app := fiber.New()

    return app
}


func main() {
    app := CreateServer()

    app.Use(cors.New())

    app.Get("/hello", func(c *fiber.Ctx) {
            return c.SendString("Hello, World!")
    }

    // 404 Handler
    app.Use(func(c *fiber.Ctx) error {
        return c.SendStatus(404) // => 404 "Not Found"
    })

    log.Fatal(app.Listen(":3000"))
}
Enter fullscreen mode Exit fullscreen mode

Now, run this server with the command go run main.go and go to http://localhost:3000/hello. You will see a Hello, World! text there.

2. Connect PostgreSQL with the server.

First of all, we need to create a database. Let us name our database go-auth. We can create a database either with bash/zsh or with psql by running the following commands:

createdb go-auth # in bash/zsh

CREATE DATABASE go-auth; # in psql 
Enter fullscreen mode Exit fullscreen mode

We are going to use Gorm to connect our server with our database. Gorm is an ORM library for Golang.

Now we will store some environment variables inside a .env. A sample .env file for this project would look like:

PSQL_USER=postgres
PSQL_PASS=postgres
PSQL_DBNAME=go-auth
PSQL_PORT=5432
Enter fullscreen mode Exit fullscreen mode

Now, we create a new file called postgres.go inside a new directory called database. Here, we will create our database session and export it from this package for other packages to use. We are also going to use godotenv to use the environment variables inside this file. Then we will call this function from our main function to instantiate the database.

database/postgres.go

package database

import (
    "fmt"
    "go-authentication-boilerplate/models"
    "log"
    "os"

    "github.com/joho/godotenv"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

// DB represents a Database instance
var DB *gorm.DB

// ConnectToDB connects the server with database
func ConnectToDB() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading env file \n", err)
    }

    dsn := fmt.Sprintf("host=localhost user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Kolkata",
        os.Getenv("PSQL_USER"), os.Getenv("PSQL_PASS"), os.Getenv("PSQL_DBNAME"), os.Getenv("PSQL_PORT"))

    log.Print("Connecting to PostgreSQL DB...")
    DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })

    if err != nil {
        log.Fatal("Failed to connect to database. \n", err)
        os.Exit(2)

    }
    log.Println("connected")
}
Enter fullscreen mode Exit fullscreen mode

Now that we are done with connecting our database to our server, let's start with database models.

3. Creating the models

Now go ahead and create a new folder named models. Inside that folder create a new file called models.go. This file will contain the base struct that will contain common columns for all other models.

models/models.go

package models

import (
    "time"

    "github.com/google/uuid"
    "gorm.io/gorm"
)

// GenerateISOString generates a time string equivalent to Date.now().toISOString in JavaScript
func GenerateISOString() string {
    return time.Now().UTC().Format("2006-01-02T15:04:05.999Z07:00")
}

// Base contains common columns for all tables
type Base struct {
    ID        uint      `gorm:"primaryKey"`
    UUID      uuid.UUID `json:"_id" gorm:"primaryKey;autoIncrement:false"`
    CreatedAt string    `json:"created_at"`
    UpdatedAt string    `json:"updated_at"`
}

// BeforeCreate will set Base struct before every insert
func (base *Base) BeforeCreate(tx *gorm.DB) error {
    // uuid.New() creates a new random UUID or panics.
    base.UUID = uuid.New()

    // generate timestamps
    t := GenerateISOString()
    base.CreatedAt, base.UpdatedAt = t, t

    return nil
}

// AfterUpdate will update the Base struct after every update
func (base *Base) AfterUpdate(tx *gorm.DB) error {
    // update timestamps
    base.UpdatedAt = GenerateISOString()
    return nil
}
Enter fullscreen mode Exit fullscreen mode

Now let us create a new file inside models folder called user.go. This file will contain all the models which are relevant to user routes.

models/user.go:

package models

// User represents a User schema
type User struct {
    Base
    Email    string `json:"email" gorm:"unique"`
    Username string `json:"username" gorm:"unique"`
    Password string `json:"password"`
}
Enter fullscreen mode Exit fullscreen mode

Now we will modify our main.go file to connect our database with the server.

main.go

package main

import (
    "log"

++  "go-authentication-boilerplate/database"

    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
)

// CreateServer creates a new Fiber instance
func CreateServer() *fiber.App {
    app := fiber.New()

    return app
}

func main() {
++  // Connect to Postgres
++  database.ConnectToDB()
    app := CreateServer()

    app.Use(cors.New())

    // 404 Handler
    app.Use(func(c *fiber.Ctx) error {
        return c.SendStatus(404) // => 404 "Not Found"
    })

    log.Fatal(app.Listen(":3000"))
}
Enter fullscreen mode Exit fullscreen mode

NOTE: ++ represents the added or modified lines

4. Setup Routes

Now we need to set up the needed routes for our application. We will start by making a new folder called router. Inside that folder, create a new file called setup.go. Here we will setup all our routes.

router/setup.go

package router

import (
    "github.com/gofiber/fiber/v2"
)

func hello(c *fiber.Ctx) error {
    return c.SendString("Hello World!")
}

// SetupRoutes setups all the Routes
func SetupRoutes(app *fiber.App) {
    api := app.Group("/api")

    api.Get("/", hello)
}
Enter fullscreen mode Exit fullscreen mode

Now we will call this SetupRoutes function inside our main.go file.

main.go

package main

import (
    "log"

    "go-authentication-boilerplate/database"
++  "go-authentication-boilerplate/router"

    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
)

// CreateServer creates a new Fiber instance
func CreateServer() *fiber.App {
    app := fiber.New()

    return app
}

func main() {
    // Connect to Postgres
    database.ConnectToDB()
    app := CreateServer()

    app.Use(cors.New())

++  router.SetupRoutes(app)

    // 404 Handler
    app.Use(func(c *fiber.Ctx) error {
        return c.SendStatus(404) // => 404 "Not Found"
    })

    log.Fatal(app.Listen(":3000"))
}
Enter fullscreen mode Exit fullscreen mode

NOTE: ++ represents the added or modified lines

Now that we have scaffolded our Server, we will now create SignUp and SignIn route. We will do this in the next part of this tutorial.

Thanks for reading! If you liked this article, please let me know and share it!

💖 💪 🙅 🚩
mdfaizan7
Faizan

Posted on October 31, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related