top

Package endpoints will let you write Cloud Endpoints backend in Go.

Usage

Declare structs which describe your data. For instance:

// Greeting is a datastore entity that represents a single greeting.
// It also serves as (a part of) a response of GreetingService.
type Greeting struct {
  Key     *datastore.Key `json:"id" datastore:"-"`
  Author  string         `json:"author"`
  Content string         `json:"content" datastore:",noindex" endpoints:"req"`
  Date    time.Time      `json:"date"`
}

// GreetingsList is a response type of GreetingService.List method
type GreetingsList struct {
  Items []*Greeting `json:"items"`
}

// Request type for GreetingService.List
type GreetingsListReq struct {
  Limit int `json:"limit" endpoints:"d=10"`
}

Then, a service:

// GreetingService can sign the guesbook, list all greetings and delete
// a greeting from the guestbook.
type GreetingService struct {
}

// List responds with a list of all greetings ordered by Date field.
// Most recent greets come first.
func (gs *GreetingService) List(c context.Context, r *GreetingsListReq) (*GreetingsList, error) {
  if r.Limit <= 0 {
    r.Limit = 10
  }

  q := datastore.NewQuery("Greeting").Order("-Date").Limit(r.Limit)
  greets := make([]*Greeting, 0, r.Limit)
  keys, err := q.GetAll(c, &greets)
  if err != nil {
    return nil, err
  }

  for i, k := range keys {
    greets[i].Key = k
  }
  return &GreetingsList{greets}, nil
}

Last step is to make the above available as a discoverable API and leverage all the juicy stuff Cloud Endpoints are great at.

import "github.com/GoogleCloudPlatform/go-endpoints/endpoints"

func init() {
  greetService := &GreetingService{}
  api, err := endpoints.RegisterService(greetService,
    "greeting", "v1", "Greetings API", true)
  if err != nil {
    panic(err.Error())
  }

  info := api.MethodByName("List").Info()
  info.Name, info.HTTPMethod, info.Path, info.Desc =
    "greets.list", "GET", "greetings", "List most recent greetings."

  endpoints.HandleHTTP()
}

Don't forget to add URL matching in app.yaml:

application: my-app-id
version: v1
threadsafe: true

runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app

# Important! Even though there's a catch all routing above,
# without these two lines it's not going to work.
# Make sure you have this:
- url: /_ah/spi/.*
  script: _go_app

That's it. It is time to start dev server and enjoy the discovery doc: http://localhost:8080/_ah/api/explorer

Custom types

You can define your own types and use them directly as a field type in a service method request/response as long as they implement json.Marshaler and json.Unmarshaler interfaces.

Let's say we have this method:

func (s *MyService) ListItems(c context.Context, r *ListReq) (*ItemsList, error) {
  // fetch a list of items
}

where ListReq and ItemsList are defined as follows:

type ListReq struct {
    Limit  int        `json:"limit,string" endpoints:"d=10,max=100"`
    Page *QueryMarker `json:"cursor"`
}

type ItemsList struct {
    Items []*Item      `json:"items"`
    Next  *QueryMarker `json:"next,omitempty"`
}

What's interesting here is ListReq.Page and ItemsList.Next fields which are of type QueryMarker:

import "appengine/datastore"

type QueryMarker struct {
    datastore.Cursor
}

func (qm *QueryMarker) MarshalJSON() ([]byte, error) {
    return []byte(`"` + qm.String() + `"`), nil
}

func (qm *QueryMarker) UnmarshalJSON(buf []byte) error {
    if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' {
        return errors.New("QueryMarker: bad cursor value")
    }
    cursor, err := datastore.DecodeCursor(string(buf[1 : len(buf)-1]))
    if err != nil {
        return err
    }
    *qm = QueryMarker{cursor}
    return nil
}

Now that our QueryMarker implements required interfaces we can use ListReq.Page field as if it were a `datastore.Cursor` in our service method, for instance:

func (s *MyService) ListItems(c context.Context, r *ListReq) (*ItemsList, error) {
    list := &ItemsList{Items: make([]*Item, 0, r.Limit)}

    q := datastore.NewQuery("Item").Limit(r.Limit)
    if r.Page != nil {
        q = q.Start(r.Page.Cursor)
    }

    var iter *datastore.Iterator
    for iter := q.Run(c); ; {
        var item Item
        key, err := iter.Next(&item)
        if err == datastore.Done {
            break
        }
        if err != nil {
          return nil, err
        }
        item.Key = key
        list.Items = append(list.Items, &item)
    }

    cur, err := iter.Cursor()
    if err != nil {
        return nil, err
    }
    list.Next = &QueryMarker{cur}
    return list, nil
}

A serialized ItemsList would then look something like this:

{
  "items": [
    {
      "id": "5629499534213120",
      "name": "A TV set",
      "price": 123.45
    }
  ],
  "next": "E-ABAIICImoNZGV2fmdvcGhtYXJrc3IRCxIEVXNlchiAgICAgICACgwU"
}

Another nice thing about this is, some types in appengine/datastore package already implement json.Marshal and json.Unmarshal.

Take, for instance, datastore.Key. I could use it as an ID in my JSON response out of the box, if I wanted to:

type User struct {
    Key *datastore.Key `json:"id" datastore:"-"`
    Name string        `json:"name" datastore:"name"`
    Role string        `json:"role" datastore:"role"`
    Email string       `json:"email" datastore:"email"`
}

type GetUserReq struct {
    Key *datastore.Key `json:"id"`
}

// defined with "users/{id}" path template
func (s *MyService) GetUser(c context.Context, r *GetUserReq) (*User, error) {
  user := &User{}
  if err := datastore.Get(c, r.Key, user); err != nil {
    return nil, err
  }
  user.Key = r.Key
  return user, nil
}

JSON would then look something like this:

GET /_ah/api/myapi/v1/users/ag1kZXZ-Z29waG1hcmtzchELEgRVc2VyGICAgICAgIAKDA

{
  "id": "ag1kZXZ-Z29waG1hcmtzchELEgRVc2VyGICAgICAgIAKDA",
  "name": "John Doe",
  "role": "member",
  "email": "user@example.org"
}

Field tags

Go Endpoints has its own field tag "endpoints" which you can use to let your clients know what a service method data constraints are (on input):

- req, means "required".
- d, default value, cannot be used together with req.
- min and max constraints. Can be used only on int and uint (8/16/32/64 bits).
- desc, a field description. Cannot contain a "," (comma) for now.

Let's see an example:

type TaggedStruct struct {
    A int    `endpoints:"req,min=0,max=100,desc=An int field"`
    B int    `endpoints:"d=10,min=1,max=200"`
    C string `endpoints:"req,d=Hello gopher,desc=A string field"`
}

- A field is required and has min & max constrains, is described as "An int field"
- B field is not required, defaults to 10 and has min & max constrains
- C field is required, defaults to "Hello gopher", is described as "A string field"

JSON tag and path templates

You can use JSON tags to shape your service method's response (the output).

Endpoints will honor Go's encoding/json marshaling rules (http://golang.org/pkg/encoding/json/#Marshal), which means having this struct:

type TaggedStruct struct {
    A       int
    B       int    `json:"myB"`
    C       string `json:"c"`
    Skipped int    `json:"-"`
}

a service method path template could then look like:

some/path/{A}/other/{c}/{myB}

Notice, the names are case-sensitive.

Naturally, you can combine json and endpoints tags to use a struct for both input and output:

type TaggedStruct struct {
    A       int    `endpoints:"req,min=0,max=100,desc=An int field"`
    B       int    `json:"myB" endpoints:"d=10,min=1,max=200"`
    C       string `json:"c" endpoints:"req,d=Hello gopher,desc=A string field"`
    Skipped int    `json:"-"`
}

Long integers (int64, uint64)

As per Type and Format Summary (https://developers.google.com/discovery/v1/type-format):

a 64-bit integer cannot be represented in JSON (since JavaScript and JSON
support integers up to 2^53). Therefore, a 64-bit integer must be
represented as a string in JSON requests/responses

In this case, it is sufficient to append ",string" to the json tag:

type Int64Struct struct {
  Id int64 `json:",string"`
}

Generate client libraries

Once an app is deployed on appspot.com, we can use the discovery doc to generate libraries for different clients.

Android

$ URL='https://my-app-id.appspot.com/_ah/api/discovery/v1/apis/greeting/v1/rest'
$ curl -s $URL > greetings.rest.discovery

# Optionally check the discovery doc
$ less greetings.rest.discovery

$ GO_SDK/endpointscfg.py gen_client_lib java greetings.rest.discovery

You should be able to find ./greetings.rest.zip file with Java client source code and its dependencies.

Once you have that, follow the official guide https://developers.google.com/appengine/docs/python/endpoints/consume_android.

iOS

# Note the rpc suffix in the URL:
$ URL='https://my-app-id.appspot.com/_ah/api/discovery/v1/apis/greeting/v1/rpc'
$ curl -s $URL > greetings.rpc.discovery

# optionally check the discovery doc
$ less greetings.rpc.discovery

Then, feed greetings.rpc.discovery file to the library generator on OS X as described in the official guide: https://developers.google.com/appengine/docs/python/endpoints/consume_ios

JavaScript

There's really nothing to generate for JavaScript, you just use it!

Here's the official guide: https://developers.google.com/appengine/docs/python/endpoints/consume_js

Other docs

Wiki pages on the github repo: https://github.com/crhym3/go-endpoints/wiki

Samples

Check out TicTacToe sample: https://github.com/crhym3/go-tictactoe

Or play it on the live demo app at https://go-endpoints.appspot.com/tictactoe

Running tests

We currently use aet tool (https://github.com/crhym3/aegot) to simplify running tests on files that have "appengine" or "appengine_internal" imports.

Check out the readme of that tool but, assuming you cloned this repo (so you can reach ./endpoints dir), the initial setup process is pretty simple:

- go get github.com/crhym3/aegot/aet
- aet init ./endpoints

That's it. You should be able to run tests with "aet test ./endpoints" now.

Imports 8 package(s) ΒΆ

  1. google.golang.org/appengine/internal
  2. golang.org/x/net/context
  3. google.golang.org/appengine/memcache
  4. google.golang.org/appengine/user
  5. google.golang.org/appengine/log
  6. google.golang.org/appengine/urlfetch
  7. google.golang.org/appengine/internal/user
  8. google.golang.org/appengine