A newer version of this documentation is available.

View Latest

Go Sample App Backend Tutorial

    The Go SDK tutorial bridges the gap between simple and advanced concepts by walking through a complete web application.

    The full source code for the tutorial is available on GitHub couchbaselabs/try-cb-golang. The primary focus of the tutorial is to explain the function and theory behind the Couchbase Java client and how it works together with Couchbase Server, and especially new features in versions 4.0/4.5 like N1QL, FTS and sub-document. It makes use of the travel-sample data set. The code that generates the web application is provided with the source code, but is not discussed in this tutorial.

    Specific Go prerequisites and set up

    In addition to the prerequisites mentionned in Sample Application, you’ll need:

    • Go 1.6+

    To get set up for the tutorial proper, follow these steps:

    • mkdir try-cb-golang

    • cd try-cb-golang

    • export GOPATH=$PWD

    • go get github.com/couchbaselabs/try-cb-golang

    • cd src/github.com/couchbaselabs/try-cb-golang

    • if you don’t want to connect to localhost, change the configuration in main.go (eg. cbConnStr) .

    • go run main.go

    • You should now be able to access the UI on localhost:8080

    If you want to code it yourself, there’s currently no "fill-in-the-blanks" branch so you’ll have to delete method bodies and try to take it from there.
    This tutorial focuses on querying through N1QL and FTS rather than views. If you want information about using views, see Views.

    Walking Through the API

    The following sections lead you through the primary functions of the sample application. All of the REST API application code is in the main.go file. The following shows you how to program with the various features and services of Couchbase including: connecting to a cluster and bucket, key/value iteraction, document query through N1QL and full text searches.

    Configure and Bootstrap the SDK

    Where: main()()

    Goals: Connecting to the Cluster and getting a reference to a Bucket, learn to reuse it.

    The first step is to let the application connect to your cluster and obtain a reference to a Bucket (the Bucket is your entry point for the whole storage API). The cbConnStr variable defines the connection string including hostname and protocol for your cluster. The cbBucket and cbPassword variables are used separately and get passed to OpenBucket to actually establish a connection to your bucket.

    Connecting to the Cluster and Bucket

    globalCluster, err = gocb.Connect(cbConnStr)
    // error handling elided...
      Username: "Administrator",
      Password: "something",
    // For RBAC, in Couchbase Server 5.0 and up
    globalBucket, err = globalCluster.OpenBucket(cbBucket, cbPassword)
    // error handling elided...

    Both bucket and cluster can be managed through the SDK as well (eg. add views or create new buckets), see Managing Clusters Using the Go SDK with Couchbase Server for more information.

    Managing Users using Key/Value API

    Where: UserLogin() and UserSignup()

    Goals: Use Bucket operations and discover the Document API.

    Couchbase is a document oriented database which provides access to your data both through its document ID (for high performance access), as well as through views and N1QL (as powerful query languages).

    This is noticeable in the API, where the methods reflect Key/Value operations (RetrieveIn, Upsert, etc...) and work with a Sub Document interface that has an id() and requests specific content from the document. The default Document implementation accepts a simple representation of JSON as its content.

    Creating New Users

    A new user first goes through the UserSignup() method which recieves a POST of username and password encoded as a JSON object. After registering the user to Couchbase, it uses a JWT encoded version of the username to maintain the session. The JWT token is later used through further interaction to validate the user session and is created using a custom function createJwtToken().

    The "user::" prefix is arbitrary to this application, this is just a convention that the application uses to obtain unique keys and have additional information in it, but the key could have been anything else (even sequence numbers or UUIDs).

    Here comes the part where the Couchbase API is used to store the document. It’s rather simple, the application uses an Insert method which stores the document, if the record already exists, an error is returned instead:

    globalBucket.Insert(userKey, user, 0).

    The resulting JSON data and confirmation is sent back to the client with a success, or an exception if it failed.

    When it comes to storing a document, you have a choice of three methods:

    • Insert will only work if no document currently exists for the given ID, otherwise a ErrKeyExists will be returned.

    • Replace on the contrary will only work if the document does already exist (otherwise a ErrKeyNotFound is thrown).

    • Upsert will always work, replacing or creating the document as needed.

    So the result just contains a JWT (Json WebToken) to identify the new user. If there is a problem, an error and non-successful status code will be returned to the application. But what is this narration object in the Result?

    The frontend understands this second part of the Result, the narration, as something that it can display in a console, so that users of the application can directly get an idea of what is going on on the server side while browsing the app. It is similar to a log, but transmitted to the frontend.

    Checking Login by Getting the User’s Document

    In the UserLogin() method, the application checks a User’s credential and for that it needs to retrieve the corresponding document. Since user documents are identified by prefixing their username with user::, this is pretty simple. Using the sub-document LookupIn(…).Get("password") the application can request only the precise values it needs from the document, in this case the password:


    If that particular key doesn’t exist, the LookupIn() method returns ErrKeyNotFound. This is useful to check whether the user exists at all.

    Otherwise it’s just a matter of checking the hashed password with the one provided by the user, and responding accordingly.

    A First N1QL Query: Finding Airports

    Where: FlightSearch()

    Goals: Use N1QL and the DSL to perform your first SELECT on Couchbase Server.

    Relevant Documentation Topics: N1QL Queries Using the Go SDK with Couchbase Server.

    In the SDK, there is an ExecuteN1qlQuery method, which accepts a N1qlQuery structure and performs a N1QL query against the cluster.

    N1QL is a super-set of SQL, so if you’re familiar with SQL you’ll feel at ease.

    Statements can be provided either in string form or using the DSL.

    The application needs to select just the airport name from relevant documents in the bucket. Since it wants to filter relevant document on a criteria that depends on the input length, the query starts with "SELECT airportname FROM `travel-sample`".

    Then the application can choose the correct fields to look into depending on the length of the input. The user can enter either a ICAO or FAA code or a full name of an airport to search for, so the application accommodates each scenario as it builds the N1QL statement. It also uses wildcards (%) in the statement to give a free form expression:

    var queryStr string
    if len(searchKey) == 3 {
      queryStr = fmt.Sprintf("SELECT airportname FROM `travel-sample` WHERE faa='%s'", strings.ToUpper(searchKey))
    } else if len(searchKey) == 4 && searchKey == strings.ToUpper(searchKey) {
      queryStr = fmt.Sprintf("SELECT airportname FROM `travel-sample` WHERE icao ='%s'", searchKey)
    } else {
      queryStr = fmt.Sprintf("SELECT airportname FROM `travel-sample` WHERE airportname like '%s%%'", searchKey)

    The statement is ready! You can execute this statement by wrapping it in a N1qlQuery() using gocb.NewN1qlQuery(queryStr) and invoking globalBucket.ExecuteN1qlQuery(). Here it is very simple, no placeholders and no particular tuning of the query is necessary, so the simple method: globalBucket.ExecuteN1qlQuery(q, nil) will be used.

    The results of the query are return in a result object, which can then be iterated over to build an array of the results:

    respData.Data = []jsonAirport{}
    var airport jsonAirport
    for rows.Next(&airport) {
    	respData.Data = append(respData.Data, airport)
    	airport = jsonAirport{}

    More Complex Queries: Finding Routes

    Where: FlightSearch()

    Goals: Let the DSL guide you into making more complex N1QL queries.

    Relevant Documentation Topics: N1QL Queries Using the Go SDK with Couchbase Server.

    In this class, there are two more complex queries. The first aims at transforming the human-readable airport name for the departure and arrival airports to FAA codes:

    SELECT faa AS fromAirport FROM `travel-sample` WHERE airportname = "Los Angeles Intl"
      UNION SELECT faa AS toAirport FROM `travel-sample` WHERE airportname = "San Francisco Intl"

    The second aims at constructing the result set of available flight paths that connect the two airports:

    SELECT a.name, s.flight, s.utc, r.sourceairport, r.destinationairport, r.equipment
      FROM `travel-sample` AS r
      UNNEST r.schedule AS s
      JOIN `travel-sample` AS a ON KEYS r.airlineid
      WHERE r.sourceairport = "LAX" AND r.destinationairport = "SFO" AND s.day = 6
      ORDER BY a.name ASC
    Yes, you read that right, N1QL can do joins (on a single bucket or on several). It works as long as the "foreign key" described by ON KEYS clause can be mapped to a document’s key in the joined bucket.

    A specificity of N1QL that can be seen in the second statement is UNNEST. It extracts a sub-JSON and puts it at the same root level as the bucket (so its possible to do joins on each element in this sub-JSON as if they were entries in a left-hand side bucket).

    For this final step, try to obtain the equivalent of these statements via the DSL and see how it guides you through the possibilities of the query language.

    Indexing the Data: N1QL & GSI

    Goals: Use the Index DSL to make sure data is indexed for N1QL to query it.

    Index management is a bit more advanced (and is already done when loading the sample), so now that you’ve learned about N1QL, you can have a look at it. There is no code example in this application, but some samples are provided below for your reference.

    For N1QL to work, you must first ensure that at least a Primary Index has been created. For that you can use the DSL from the Index class:

    q := gocb.NewN1qlQuery("CREATE PRIMARY INDEX ON `travel-sample`")
    _, err := globalBucket.ExecuteN1qlQuery(q, nil)

    You can also create secondary indexes on specific fields of the JSON, for better performance:

    q := gocb.NewN1qlQuery("CREATE INDEX `def_username` ON `travel-sample`(username)")
    _, err := globalBucket.ExecuteN1qlQuery(q, nil)

    In this case, give a name to your index, specify the target bucket AND the field(s) in the JSON to index.

    Full Text Search: Finding Hotels

    Where: HotelSearch()

    Goals: Use FTS to search for matching Hotels. Use sub-document API to fetch the relevant data for each hit.

    In this service, hotels are searched for using more fuzzy criterias, like the content of the address or the description of an hotel. This is done using Full Text Search (FTS). When some results match the specified criteria, only the relevant data for each result to be displayed in the UI is fetched using the subdocument API.

    Let’s have a look at the HotelSearch() method. It accepts two parameters via HTTP, location and description, which are the two possible refining criteria for a hotel search.

    qp := cbft.NewConjunctionQuery(cbft.NewTermQuery("hotel").Field("type"))

    A ConjunctionQuery allows you to combine multiple FTS queries into one, as an AND operation. That search always includes an exact match criteria that restricts it to the hotel data type (as reflected in the type field of the JSON document).

    If the user provided a location keyword, a second component is added to the FTS query that will look for that keyword in several address-related fields of the document. This is done in an OR fashion, using a Disjunction this time:

    if location != "" && location != "*" {

    Similarly, if a description keyword was provided by the user, the freeform text is inspected of the description field and name field of the document:

    if description != "" && description != "*" {

    The MatchPhraseQuery can contain several words and will search for variations of the words (eg. including plural forms or words with the same root...).

    The compound FTS query is now ready to be executed. A SearchQuery object is built out of it, which also determines which FTS index to use ("hotel") and allows you to set various parameters (like a limit of maximum 100 hits to return). The query is logged (and kept for narration) then executed, returning an SearchQueryResult object:

    q := gocb.NewSearchQuery("travel-search", qp).
    rows, err := globalBucket.ExecuteSearchQuery(q)

    The FTS results are then iterated over, and the document corresponding to each result is fetched. In actuality, only the parts of the document that will be displayed in the UI are required. This is where the sub-document API comes in.

    The sub-document API allows you to fetch or mutate only a set of paths inside a JSON document, without having to send the whole document back and forth. This can save network bandwidth if the document is large and the parts that the application is interested in are small. So here the results of the FTS search are iterated over and appropriate subdoc calls are triggered:

    for _, hit := range rows.Hits() {
    	res, _ := globalBucket.LookupIn(hit.Id).
    	// We ignore errors here since some hotels are missing various
    	//  pieces of data, but every key exists since it came from FTS.
    	var hotel jsonHotel
    	res.Content("country", &hotel.Country)
    	res.Content("city", &hotel.City)
    	res.Content("state", &hotel.State)
    	res.Content("address", &hotel.Address)
    	res.Content("name", &hotel.Name)
    	res.Content("description", &hotel.Description)
    	respData.Data = append(respData.Data, hotel)

    Each FTS result is represented as an SearchRowHit which exposes the document’s Id. The sub-document API can then be used to fetch data (bucket.LookupIn(documentId)) and specify what parts it wants: name, description, address, city, state and country. The application then Execute() the sub-document query. In the rest of the code, the address-related fields are aggregated together and the data obtained is returned as a JSON object for use in the front-end.