website logo

Last Updated:

A Better Way to Share Database Connection in Golang

Golang provides a database/sql package to interact with any SQL like database. This database/sql package gives a standard interface and independence to choose various database drivers.

In this blog, you will learn how to access the database in your Golang Project and pass the database connection to your request handler, efficiently.

Use Internal Directory in your Golang project

In your Golang project, you can use the internal directory to store structs and methods private to your project. If someone uses your go module in their code, they can’t use structs and methods defined inside the internal directory.

This is the reason I want you to use the internal directory. You may choose to use any directory as you like.

Define Database Models

Create a models directory and create a models.go file inside it.

Define the models package inside that file.

package models

Assume, you want to add data from the user sign-up form. The form has 3 fields e.g, Username, Email, and Password. Let’s define a User Struct for this purpose.

type User struct {
	Name     string
	Email    string
	Password string
}

Now, define a DB struct which contain the actual sql.DB connection.

type DB struct {
	db *sql.DB
}

First, make a Connect method to the DB struct. This is a helper method to connect to our database. This method assign the database connection to the DB.db pointer.

func (d *DB) Connect(address string) {
	db, err := sql.Open("pgx", address)
	if err != nil {
		log.Fatal(err)
	}
	d.db = db
}

Similarly, make a SignUp method to add new user to your database.

func (d *DB) SignUp(name string, email string, password string) error {
	_, err := d.db.Exec("INSERT INTO users(name, email, password) VALUES($1,$2,$3)", name, email, password)
	if err != nil {
		return err
	}
	return nil
}

You can make other methods (e.g., login) similarly.

Access Database from the rest of your Go Application

Now inside your main.go file, create an Application struct which holds your DB struct. Every handler in your application should be a method to this App struct to share the database connection.

type Application struct {
	db *DB
}

func (app *Application) loginHandlerPost(w http.ResponseWriter, r *http.Request) {

	err := r.ParseForm()
	if err != nil {
		http.Error(w, "Form parsing error", http.StatusInternalServerError)
	}

	lf := LoginFields{
		Username: r.PostForm.Get("username"),
		Email:    r.PostForm.Get("email"),
		Password: r.PostForm.Get("password"),
		Err:      map[string]string{},
	}

	if _, err := validateEmail(lf.Email); err != nil {
		lf.Err["Email"] = err.Error()
	}

	if _, err := validateUserName(lf.Username); err != nil {
		lf.Err["Username"] = err.Error()
	}

	if _, err := validatePassword(lf.Password); err != nil {
		lf.Err["Password"] = err.Error()
	}

	if len(lf.Err) > 0 {
		t, ok := app.templateCache["login.html"]
		if !ok {
			http.Error(w, "Can't get template cache", http.StatusInternalServerError)
			return
		}
		t.ExecuteTemplate(w, "base", lf)
		return
	}

	hashedPW, err := bcrypt.GenerateFromPassword([]byte(lf.Password), 10)
	if err != nil {
		http.Error(w, "Password hashing error", http.StatusInternalServerError)
		return
	}
	err = app.db.signUp(lf.Username, lf.Email, string(hashedPW))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, "/", http.StatusSeeOther)
}

Now, inside your main function, initialize the database connection.


func main(){
	db := &DB{}
	db.Connect("postgres://username:password@hostname:5432/dbname")

	app := &Application{db: db}
}

Conclusion

If you follow this guide in your project, the database connection can’t be accessible outside of the models package. They have to use the methods you defined in this package.

This way, all your database logic will present in one location and in the long term, it helps you to maintain your golang project better.

One more benefit of this structure is, you can easily swap the database. If you want to use another database, just change the connection string and you are good to go.

See Also