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
}
Methods related to Database Struct
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.