Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Full-Stack Web Development with Go

You're reading from   Full-Stack Web Development with Go Build your web applications quickly using the Go programming language and Vue.js

Arrow left icon
Product type Paperback
Published in Feb 2023
Publisher Packt
ISBN-13 9781803234199
Length 302 pages
Edition 1st Edition
Languages
Arrow right icon
Authors (2):
Arrow left icon
Nick Glynn Nick Glynn
Author Profile Icon Nick Glynn
Nick Glynn
Nanik Tolaram Nanik Tolaram
Author Profile Icon Nanik Tolaram
Nanik Tolaram
Arrow right icon
View More author details
Toc

Table of Contents (21) Chapters Close

Preface 1. Part 1: Building a Golang Backend
2. Chapter 1: Building the Database and Model FREE CHAPTER 3. Chapter 2: Application Logging 4. Chapter 3: Application Metrics and Tracing 5. Part 2:Serving Web Content
6. Chapter 4: Serving and Embedding HTML Content 7. Chapter 5: Securing the Backend and Middleware 8. Chapter 6: Moving to API-First 9. Part 3:Single-Page Apps with Vue and Go
10. Chapter 7: Frontend Frameworks 11. Chapter 8: Frontend Libraries 12. Chapter 9: Tailwind, Middleware, and CORS 13. Chapter 10: Session Management 14. Part 4:Release and Deployment
15. Chapter 11: Feature Flags 16. Chapter 12: Building Continuous Integration 17. Chapter 13: Dockerizing an Application 18. Chapter 14: Cloud Deployment 19. Index 20. Other Books You May Enjoy

Generating CRUD with sqlc

CRUD stands for Create, Read, Update, and Delete, which refers to all the major functions that are inherent to relational databases. In this section, we will do the following for the application:

  • Complete the sqlc configuration file
  • Create SQL query files

Once done, we will be able to autogenerate the different files required to allow us to perform CRUD operations to the database from the application. First, open sqlc.yaml and enter the following configuration:

---
version: '1'
packages:
 - name: chapter1
   path: gen
   schema: db/
   queries: queries/
   engine: postgresql
   emit_db_tags: true
   emit_interface: false
   emit_exact_table_names: false
   emit_empty_slices: false
   emit_exported_queries: false
   emit_json_tags: true
   json_tags_case_style: camel
   output_files_suffix: _gen
   emit_prepared_queries: false

Our application is now complete with all that we need for the database, and sqlc will autogenerate the .go files. The following is how the application directory and files will look:

.
├── db
│   └── schema.sql
├── go.mod
├── queries
│   └── query.sql
└── sqlc.yaml

We can run sqlc to generate the .go files using the following command:

sqlc generate 

By default, sqlc will look for the sqlc.yaml file. If the filename is different, you can specify it using the -f flag as follows:

sqlc generate -f sqlc.yaml 

Once the operation completes, there will be no output; however, a new directory called gen will be generated as shown here:

./gen/
├── db.go
├── models.go
└── query.sql_gen.go

We have completed the auto-generation process using sqlc; now, let’s take a look at the schema and queries that sqlc uses to generate the code.

The following is a snippet of the schema.sql file that is used by sqlc to understand the structure of the database:

CREATE SCHEMA IF NOT EXISTS gowebapp;
CREATE TABLE gowebapp.users (
User_ID        BIGSERIAL PRIMARY KEY,
User_Name      text NOT NULL,
....
);
....
CREATE TABLE gowebapp.sets (
Set_ID      BIGSERIAL PRIMARY KEY,
Exercise_ID BIGINT NOT NULL,
Weight      INT NOT NULL DEFAULT 0
);

The other file sqlc uses is the query file. The query file contains all the relevant queries that will perform CRUD operations based on the database structure given here. The following is a snippet of the query.sql file:

-- name: ListUsers :many
-- get all users ordered by the username
SELECT *
FROM gowebapp.users
ORDER BY user_name;
...
-- name: DeleteUserImage :exec
-- delete a particular user's image
DELETE
FROM gowebapp.images i
WHERE i.user_id = $1;
...
-- name: UpsertExercise :one
-- insert or update exercise of a particular id
INSERT INTO gowebapp.exercises (Exercise_Name)
VALUES ($1) ON CONFLICT (Exercise_ID) DO
UPDATE
    SET Exercise_Name = EXCLUDED.Exercise_Name
    RETURNING Exercise_ID;
-- name: CreateUserImage :one
-- insert a new image
INSERT INTO gowebapp.images (User_ID, Content_Type,
                             Image_Data)
values ($1,
        $2,
        $3) RETURNING *;
...

Using query.sql and schema.sql, sqlc will automatically generate all the relevant .go files, combining information for these two files together and allowing the application to perform CRUD operations to the database by accessing it like a normal struct object in Go.

The last piece that we want to take a look at is the generated Go files. As shown previously, there are three auto-generated files inside the gen folders: db.go, models.go, and query.sql_gen.go.

Let’s take a look at each one of them to understand what they contain and how they will be used in our application:

  • db.go:

This file contains an interface that will be used by the other auto-generated files to make SQL calls to the database. It also contains functions to create a Go struct that is used to do CRUD operations.

A new function is used to create a query struct, passing in a DBTX struct. The DBTX struct implementation is either sql.DB or sql.Conn.

The WithTx function is used to wrap the Queries object in a database transaction; this is useful in situations where there could be an update operation on multiple tables that need to be committed in a single database transaction:

func New(db DBTX) *Queries {
  return &Queries{db: db}
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
  return &Queries{
    db: tx,
  }
}
  • models.go:

This file contains the struct of the tables in the database:

type GowebappExercise struct {
  ExerciseID   int64  `db:"exercise_id"
    json:"exerciseID"`
  ExerciseName string `db:"exercise_name"
    json:"exerciseName"`
}
...
type GowebappWorkout struct {
  WorkoutID int64     `db:"workout_id"
    json:"workoutID"`
  UserID    int64     `db:"user_id" json:"userID"`
  SetID     int64     `db:"set_id" json:"setID"`
  StartDate time.Time `db:"start_date"
    json:"startDate"`
}
  • query.sql_gen.go:

This file contains CRUD functions for the database, along with the different parameters struct that can be used to perform the operation:

const deleteUsers = `-- name: DeleteUsers :exec
DELETE FROM gowebapp.users
WHERE user_id = $1
`
func (q *Queries) DeleteUsers(ctx context.Context,
userID int64) error {
  _, err := q.db.ExecContext(ctx, deleteUsers, userID)
  return err
}
...
const getUsers = `-- name: GetUsers :one
SELECT user_id, user_name, pass_word_hash, name, config, created_at, is_enabled FROM gowebapp.users
WHERE user_id = $1 LIMIT 1
`
func (q *Queries) GetUsers(ctx context.Context, userID int64) (GowebappUser, error) {
  row := q.db.QueryRowContext(ctx, getUsers, userID)
  var i GowebappUser
  err := row.Scan(
           &i.UserID,
           &i.UserName,
           &i.PassWordHash,
           &i.Name,
           &i.Config,
           &i.CreatedAt,
           &i.IsEnabled,
  )
  return i, err
}
...

Now that the database and auto-generated data to perform CRUD operations are complete, let’s try all this by doing a simple insert operation into the user table.

The following is a snippet of main.go:

package main
import (
  ...
)
func main() {
  ...
  // Open the database
  db, err := sql.Open("postgres", dbURI)
  if err != nil {
    panic(err)
  }
  // Connectivity check
  if err := db.Ping(); err != nil {
    log.Fatalln("Error from database ping:", err)
  }
  // Create the store
  st := chapter1.New(db)
  st.CreateUsers(context.Background(),
  chapter1.CreateUsersParams{
    UserName:     "testuser",
    PassWordHash: "hash",
    Name:         "test",
  })
}

The app is doing the following:

  1. Initializing the URL and opening the database
  2. Pinging the database
  3. Creating a new user using the CreateUsers(..) function

Make sure you are in the chapter1 directory and build the application by running the following command:

go build -o chapter1

The compiler will generate a new executable called chapter1. Execute the file, and on a successful run, you will see the data inserted successfully into the users table:

2022/05/15 16:10:49 Done!
Name : test, ID : 1

We have completed setting up everything from the database and using sqlc to generate the relevant Go code. In the next section, we are going to put everything together for ease of development.

You have been reading a chapter from
Full-Stack Web Development with Go
Published in: Feb 2023
Publisher: Packt
ISBN-13: 9781803234199
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime