In this article, you will learn how to connect to a Couchbase cluster to create, read, update, and delete documents and how to write simple parametrized N1QL queries.
To run this prebuilt project, you will need to have Couchbase Server locally installed OR have a Couchbase Capella account :
git clone https://github.com/couchbase-examples/golang-quickstart
Any dependencies will be installed by running the go run command, which installs any dependencies required from the go.mod file.
All configuration for communication with the database is stored in the .env
file. This includes the Connection string, username, password, bucket name, collection name and scope name. The default username is assumed to be Administrator
and the default password is assumed to be Password1$
. If these are different in your environment you will need to change them before running the application.
When running Couchbase using Capella, the application requires the bucket and the database user to be setup from Capella Console. The directions for creating a bucket can be found on the documentation website. When following the directions, name the bucket "user_profile". Next, follow the directions for Configure Database Credentials and name the username Administrator
and password Password1$
.
Next, open the .env
file. Locate CONNECTION_STRING and update it to match your Wide Area Network name found in the Capella Portal UI Connect Tab. Note that Capella uses TLS so the Connection string must start with couchbases://. Note that this configuration is designed for development environments only.
CONNECTION_STRING=couchbases://yourhostname.cloud.couchbase.com
BUCKET=user_profile
COLLECTION=default
SCOPE=default
USERNAME=Administrator
PASSWORD=Password1$
For local installation and Docker users, follow the directions found on the documentation website for creating a bucket called user_profile
. Next, follow the directions for Creating a user; name the username Administrator
and password Password1$
. For this tutorial, make sure it has Full Admin
rights so that the application can create collections and indexes.
Next, open the .env
file and validate that the configuration information matches your setup.
NOTE: For docker and local Couchbase installations, Couchbase must be installed and running on localhost (http://127.0.0.1:8091) prior to running the the Golang application.
At this point the application is ready. Make sure you are in the src directory. You can run it with the following command from the terminal/command prompt:
go run .
Once the site is up and running, you can launch your browser and go to the Swagger start page to test the APIs.
A simple REST API using Golang, Gin Gonic, and the Couchbase SDK version 2.x with the following endpoints:
We will be setting up a REST API to manage some profile documents. Our profile document will have an auto-generated UUID for its key, first and last name of the user, an email, and hashed password. For this demo, we will store all profile information in just one document in a collection named profile
:
{
"Pid": "b181551f-071a-4539-96a5-8a3fe8717faf",
"FirstName": "John",
"LastName": "Doe",
"Email": "john.doe@couchbase.com",
"Password": "$2a$10$tZ23pbQ1sCX4BknkDIN6NekNo1p/Xo.Vfsttm.USwWYbLAAspeWsC"
}
As we can see, we want our user's password to be encrypted in the database too, we can achieve this simply with bcrypt
, an implementation of the crypto library we have installed.
To begin, open the code in the IDE of your choice to learn how to create, read, update, and delete documents.
For CRUD operations, we will use the Key Value operations that are built into the Couchbase SDK to create, read, update, and delete a document. Every document will need an ID (similar to a primary key in other databases) to save it to the database.
Open the profile_controller
file found in the controllers
folder and navigate to the Insertprofile method. Let's break this code down. This brings in the information passed to the controller in the body of the request. Rather than saving the password received as plain text, we hash it with Bcrypt GenerateFromPassword
function. Note that the GenerateFromPassword
function requires a byte array. The function returns a byte array which must be converted to a string while inserting the document.
key := uuid.New().String()
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(data.Password), bcrypt.DefaultCost)
from post method of controllers/profile_controller.go
Next, we create a new_profile document with the data received from the request.
new_profile := models.RequestBody{
Pid: key,
FirstName: data.FirstName,
LastName: data.LastName,
Email: data.Email,
Password: string(hashedPassword),
}
from post method of controllers/profile_controller.go
Our new_profile document is ready to be persisted to the database. We call the collection using the variable col
and then call the insert method by passing the profile_key which is the generated UUID, as the key. Once the document is inserted we then return the document saved and the result all as part of the same object back to the user.
result, err := col.Insert(profile_key, new_profile, nil)
getResult, err := col.Get(profile_key, nil)
err = getResult.Content(&getDoc)
context.JSON(http.StatusOK, responses.ProfileResponse{Status: http.StatusOK, Message: "Document successfully inserted", Profile: getDoc})
from insert method of controllers/profile_controller.go
Navigate to the GetProfile
method of the profile_controller
file in the controllers
folder. We only need the profile ID (Pid
) or our Key
from the user to retrieve a particular profile document using a basic key-value operation which is passed in the method signature as a string.
getResult, err := col.Get(Pid, nil)
context.JSON(http.StatusOK, responses.ProfileResponse{Status: http.StatusOK, Message: "Successfully fetched Document", Profile: getDoc})
from GetProfile method of controllers/profile_controller.go
If the document wasn't found in the database, we return the NotFound
method.
Update a Profile by Profile ID (pid)
Let's navigate to the UpdateProfile
function of the profile_controller
file. The entire document gets replaced except for the document key which is the pid field.
Rather than saving the password as plain text, we hash it with Bcrypt GenerateFromPassword
function.
profile_id := context.Param("id")
data.Pid = profile_id
hashedPassword, err_password := bcrypt.GenerateFromPassword([]byte(data.Password), bcrypt.DefaultCost)
from UpdateProfile method of controllers/profile_controller.go
We create a call to the collection using the upsert method and then return the document saved and the result just as we did in the previous endpoint.
_, err := col.Upsert(profile_id, data, nil)
if errors.Is(err, gocb.ErrDocumentNotFound) {
context.JSON(http.StatusNotFound, responses.ProfileResponse{Status: http.StatusNotFound, Message: "Error, Document with the key does not exist", Profile: err.Error()})
return
}
getResult, err := col.Get(profile_id, nil)
err = getResult.Content(&getDoc)
context.JSON(http.StatusOK, responses.ProfileResponse{Status: http.StatusOK, Message: "Successfully Updated the document", Profile: getDoc})
from UpdateProfile method of controllers/profile_controller.go
Navigate to the DeleteProfile
method in the profile_controller
class. We only need the Key or id from the user to remove a document using a basic key-value operation.
result, err := col.Remove(profile_id, nil)
from DeleteProfile method of controllers/profile_controller.go
N1QL is a powerful query language based on SQL, but designed for structured and flexible JSON documents. We will use an N1QL query to search for profiles with Skip, Limit, and Search options.
Navigate to the SearchProfile
method in the profile_controller
file. This endpoint is different from all of the others because it makes the N1QL query rather than a key-value operation. This usually means more overhead because the query engine is involved. We did create an index specific for this query, so it should be performant.
First, the query string is used to get the individual skip, limit, and search values using the context. Then, we build our N1QL query using the parameters that were passed in.
Take notice of the N1QL syntax and how it targets the bucket
.scope
.collection
.
Finally, we pass that query to the cluster.query method and return the result. We create an interface{} type to iterate over the results and save the results in profiles which will be returned to the user.
#get search word,limit and skip
search := context.Query("search")
limit := context.Query("limit")
skip := context.Query("skip")
#create query
query:= fmt.Sprintf("SELECT p.* FROM %s.%s.%s p WHERE lower(p.FirstName) LIKE '%s' OR lower(p.LastName) LIKE '%s' LIMIT %s OFFSET %s%s",bucket_name,scope_name,collection_name,search_query,search_query,limit,skip,";")
results, _ := cluster.Query(query, nil)
#loop through results
for results.Next() {
err := results.Row(&s)
if err != nil {
panic(err)
}
profiles = append(profiles, s)
}
from SearchProfile method of controllers/profile_controller.go
To run the standard integration tests, use the following commands from the src directory:
cd test
go test -v
Setting up a basic REST API in Golang and Gin Gonic with Couchbase is fairly simple,this project when run will showcase basic CRUD operations along with creating an index for our parameterized N1QL query which is used in most applications.