DEV Community

Matija Krajnik
Matija Krajnik

Posted on • Updated on • Originally published at letscode.blog

Connecting with database

So far our backend is working very well. We can create user and login, but as soon we restart our server, that user data is lost, since it is saved only in memory while app is running. Of course that's not realistic case and we want to save all user data even if server is restarted. For that purpose all data will be saved to database. Database that we will use is PostgreSQL as mentioned in intro. We will not cover PostgreSQL installation as there is documentation about that on official page. For next steps we will assume that PostgreSQL is installed and configured. In all examples we will use default postgres user with password postgres.

Let's first connect to PostgreSQL to create new database. If PostgreSQL service is not running you will have to start it first, and then connect with postgres user:

sudo service postgresql start
sudo -u postgres psql
Enter fullscreen mode Exit fullscreen mode

Once you are connected, create new database:

CREATE DATABASE rgb;
Enter fullscreen mode Exit fullscreen mode

For database communication we will use go-pg module. You can install it by running go get github.com/go-pg/pg/v10. This will install version 10 of go-pg module, which is latest version at the time of writing this guide. Now create new directory internal/database/ and file database.go inside of it.

package database

import (
  "github.com/go-pg/pg/v10"
)

func NewDBOptions() *pg.Options {
  return &pg.Options{
    Addr:     "localhost:5432",
    Database: "rgb",
    User:     "postgres",
    Password: "postgres",
  }
}
Enter fullscreen mode Exit fullscreen mode

That will only create new options used for connecting to database. Create new file internal/store/store.go where we will setup database connection to be used by our store package which will be the only one communicating with database.

package store

import (
  "log"

  "github.com/go-pg/pg/v10"
)

// Database connector
var db *pg.DB

func SetDBConnection(dbOpts *pg.Options) {
  if dbOpts == nil {
    log.Panicln("DB options can't be nil")
  } else {
    db = pg.Connect(dbOpts)
  }
}

func GetDBConnection() *pg.DB { return db }
Enter fullscreen mode Exit fullscreen mode

Here we have one variable db which will serve as a database connector for store package. We also have two functions, one to set that database connector, and one to get it. Function SetDBConnection() will be used immediately because we need to set our database connector. As already said, only store package will connect to database and db variable is available in whole package. So, what do we need GetDBConnection() for? Well, we actually don't need it yet, but we will need it in next section to run migrations, because that will be happening in separate small app, as you will see soon. For now, let's only set database connector inside of internal/server/server.go file by adding line store.SetDBConnection(database.NewDBOptions()):

package server

import (
  "rgb/internal/database"
  "rgb/internal/store"
)

func Start() {
  store.SetDBConnection(database.NewDBOptions())

  router := setRouter()

  // Start listening and serving requests
  router.Run(":8080")
}
Enter fullscreen mode Exit fullscreen mode

With that in place, we are ready to implement methods for creating and authenticating users in internal/store/users.go file:

package store

import "errors"

type User struct {
  ID       int
  Username string `binding:"required,min=5,max=30"`
  Password string `binding:"required,min=7,max=32"`
}

func AddUser(user *User) error {
  _, err := db.Model(user).Returning("*").Insert()
  if err != nil {
    return err
  }
  return nil
}

func Authenticate(username, password string) (*User, error) {
  user := new(User)
  if err := db.Model(user).Where(
    "username = ?", username).Select(); err != nil {
    return nil, err
  }
  if password != user.Password {
    return nil, errors.New("Password not valid.")
  }
  return user, nil
}
Enter fullscreen mode Exit fullscreen mode

Functions above will be used in internal/server/user.go:

package server

import (
  "net/http"
  "rgb/internal/store"

  "github.com/gin-gonic/gin"
)

func signUp(ctx *gin.Context) {
  user := new(store.User)
  if err := ctx.Bind(user); err != nil {
    ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    return
  }
  if err := store.AddUser(user); err != nil {
    ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    return
  }
  ctx.JSON(http.StatusOK, gin.H{
    "msg": "Signed up successfully.",
    "jwt": "123456789",
  })
}

func signIn(ctx *gin.Context) {
  user := new(store.User)
  if err := ctx.Bind(user); err != nil {
    ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    return
  }
  user, err := store.Authenticate(user.Username, user.Password)
  if err != nil {
    ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Sign in failed."})
    return
  }

  ctx.JSON(http.StatusOK, gin.H{
    "msg": "Signed in successfully.",
    "jwt": "123456789",
  })
}
Enter fullscreen mode Exit fullscreen mode

If we try to create new account now we will get error. That's expected since we are trying to add our data to users table which doesn't exist yet. We will fix that in next section by using migrations.

Discussion (0)