Go Add ServiceStack Reference

ServiceStack's Add ServiceStack Reference feature allows clients to generate Native Types for Go - providing a simple way to give Go clients typed access to your ServiceStack Services.

First class development experience

Go has become the language of choice for cloud infrastructure, CLI tooling and high-throughput network services thanks to its fast compile times, first-class concurrency and single-binary deployments. To maximize the experience for calling ServiceStack APIs from these environments, Go is supported as a 1st class Add ServiceStack Reference language which gives Go developers an end-to-end typed API for consuming ServiceStack APIs, with DTOs generated from a single command-line.

Ideal idiomatic Typed Message-based API

Go DTOs are generated as plain structs with json tags following Go's naming conventions, so they'll naturally fit into existing Go code bases. Generated DTOs are gofmt-formatted, use time.Time for Dates and embed the built-in types in the servicestack-go library.

Here's a sample of generated Go DTOs containing a string Enum, a data model with an embedded base type and an AutoQuery Request:

package dtos

import (
	ss "github.com/ServiceStack/servicestack-go"
	"time"
)

type RoomType string

const (
	RoomTypeSingle RoomType = "Single"
	RoomTypeDouble          = "Double"
	RoomTypeQueen           = "Queen"
	RoomTypeTwin            = "Twin"
	RoomTypeSuite           = "Suite"
)

/** @description Booking Details */
type Booking struct {
	ss.AuditBase
	Id               int        `json:"id,omitempty"`
	Name             string     `json:"name"`
	RoomType         RoomType   `json:"roomType,omitempty"`
	BookingStartDate time.Time  `json:"bookingStartDate,omitempty"`
	Discount         Coupon     `json:"discount"`
}

type HelloResponse struct {
	Result string `json:"result"`
}

// @Route("/hello/{Name}")
type Hello struct {
	Name *string `json:"name,omitempty"`
}

func (Hello) CreateResponse() (r HelloResponse) { return }
func (Hello) HttpMethod() string                { return "GET" }

/** @description Find Bookings */
// @Route("/bookings", "GET")
type QueryBookings struct {
	ss.QueryDb
	Id *int `json:"id,omitempty"`
}

func (QueryBookings) CreateResponse() (r ss.QueryResponse[Booking]) { return }
func (QueryBookings) HttpMethod() string                            { return "GET" }

The generated CreateResponse() and HttpMethod() methods are what enable the end-to-end typed API. Since Go 1.21 can infer type arguments from a method's return type, CreateResponse() lets the client resolve each API's Response Type from its Request DTO - so no explicit type arguments are needed when sending a Request.

Installation

The only requirements for Go Apps to perform typed API Requests are the generated Go DTOs and the generic Client in the servicestack-go module, which only uses the Go standard library:

go get github.com/ServiceStack/servicestack-go

Requires Go 1.21+.

Simple command-line utility for Go

Go DTOs can be generated from the command-line with the cross-platform x command line utility.

To install first install the latest .NET SDK for your OS then install the x dotnet tool with:

dotnet tool install --global x

Alternative (without .NET): npx get-dtos

Alternatively API consumers can use npx get-dtos to Add/Update ServiceStack References without needing .NET installed, where any command starting with:

x <lang>

Can be replaced with:

npx get-dtos <lang>

To instead Add / Update ServiceStack references using the npm get-dtos package.

Adding a ServiceStack Reference

Generated Go DTOs use the dtos package by default, so they're typically generated into their own folder:

mkdir dtos && cd dtos && x go https://blazor-vue.web-templates.io

Result:

Saved to: dtos.go

Calling x go with just a URL will save the DTOs using the Host name, you can override this by specifying a FileName as the 2nd argument:

Result:

Saved to: Bookings.dtos.go

Use the GlobalNamespace option to generate DTOs in a different Go package.

Updating a ServiceStack Reference

To Update an existing ServiceStack Reference, call x go with the Filename:

x go dtos.go

Result:

Updated: dtos.go

Which will update the File with the latest Go Server DTOs. You can also customize how DTOs are generated by uncommenting the Go DTO Customization Options and updating them again.

Updating all Go DTOs

Calling x go without any arguments will update all Go DTOs in the current directory:

x go

Smart Generic Client

The generic Client is a 1st class client with the same rich featureset of the smart ServiceClients in other 1st class supported languages sporting a terse, typed flexible API with support for additional untyped params, custom URLs and HTTP Methods and raw Response bodies.

It includes built-in support for a number of ServiceStack Auth options including HTTP Basic Auth and stateless Bearer Token Auth Providers like API Key and JWT Auth as well as stateful Sessions used by the popular credentials Auth Provider, whose Session Cookies are maintained in the client's cookie jar. Refresh Tokens are also supported, where expired JWT Bearer Tokens are transparently refreshed behind-the-scenes before automatically retrying the failed Request.

As Go's Generics can't be used on methods, the typed APIs are implemented as functions accepting the *Client as their first argument:

// Client configuration
func NewClient(baseUrl string) *Client            // sends Requests to /api
func NewJsonServiceClient(baseUrl string) *Client // sends Requests to /json/reply
func (c *Client) SetBasePath(basePath string) *Client
func (c *Client) SetBearerToken(token string) *Client
func (c *Client) SetRefreshToken(token string) *Client
func (c *Client) SetCredentials(userName, password string) *Client
func (c *Client) SetHeader(name, value string) *Client
func (c *Client) SetTimeout(timeout time.Duration) *Client
func (c *Client) SetFollowRedirects(follow bool) *Client
func (c *Client) Authenticate(userName, password string) (AuthenticateResponse, error)

// Typed API
func Send[T any](client *Client, request IReturn[T]) (T, error)
func Get[T any](client *Client, request IReturn[T], args ...map[string]any) (T, error)
func Post[T any](client *Client, request IReturn[T], args ...map[string]any) (T, error)
func Put[T any](client *Client, request IReturn[T], args ...map[string]any) (T, error)
func Patch[T any](client *Client, request IReturn[T], args ...map[string]any) (T, error)
func Delete[T any](client *Client, request IReturn[T], args ...map[string]any) (T, error)
func SendVoid(client *Client, request IReturnVoid, args ...map[string]any) error
func SendAs[T any](client *Client, request any, args ...map[string]any) (T, error)
func Api[T any](client *Client, request IReturn[T]) ApiResult[T]

// Batched and one-way Requests
func SendAll[TRequest IReturn[TResponse], TResponse any](client *Client, requests []TRequest) ([]TResponse, error)
func Publish(client *Client, request any) error
func PublishAll[T any](client *Client, requests []T) error

// URL API
func GetUrl[T any](client *Client, path string, args ...map[string]any) (T, error)
func PostUrl[T any](client *Client, path string, body any, args ...map[string]any) (T, error)
func SendUrl[T any](client *Client, method, path string, body any, args ...map[string]any) (T, error)

// File Uploads
func PostFileWithRequest[T any](client *Client, request IReturn[T], file UploadFile) (T, error)
func PostFilesWithRequest[T any](client *Client, request IReturn[T], files []UploadFile) (T, error)

Every API also has a *Ctx variant accepting a context.Context as its first argument, e.g. SendCtx, GetCtx, ApiCtx.

Making Typed API Requests

Making API Requests in Go is the same as all other ServiceStack's Service Clients by sending a populated Request DTO using a Client which returns a typed Response DTO:

package main

import (
	"fmt"

	ss "github.com/ServiceStack/servicestack-go"

	"myapp/dtos"
)

func main() {
	client := ss.NewClient("https://blazor-vue.web-templates.io")

	res, err := ss.Send(client, dtos.Hello{Name: "World"}) // res is a dtos.HelloResponse
	if err != nil {
		panic(err)
	}
	fmt.Println(res.Result)
}

Send uses the HTTP Method the API is annotated with, use Get, Post, Put, Patch or Delete to send a Request DTO with a specific HTTP Method:

res, err := ss.Post(client, dtos.CreateBooking{Name: "Booking"})

APIs that don't return a Response Body are sent with SendVoid:

err := ss.SendVoid(client, dtos.DeleteBooking{Id: 1})

AutoQuery Requests

AutoQuery APIs return a typed QueryResponse[T], with the query params of their base type available on the embedded ss.QueryDb:

take := 5
res, err := ss.Send(client, dtos.QueryBookings{
    QueryDb: ss.QueryDb{QueryBase: ss.QueryBase{Take: &take, OrderByDesc: "id"}‎},
})

for _, booking := range res.Results { // booking is a dtos.Booking
    fmt.Println(booking.Id, booking.Name, booking.CreatedBy)
}

Sending additional arguments with Typed API Requests

Many AutoQuery Services utilize implicit conventions to query fields that aren't explicitly defined on AutoQuery Request DTOs, these can be queried by specifying additional arguments with the typed Request DTO, e.g:

res, err := ss.Get(client, dtos.QueryBookings{}, map[string]any{"nameStartsWith": "A"})

Making API Requests with URLs

In addition to making Typed API Requests you can also call Services using relative or absolute urls:

res, err := ss.GetUrl[dtos.HelloResponse](client, "/hello/World")

res, err := ss.GetUrl[dtos.HelloResponse](client, "/api/Hello", map[string]any{"name": "World"})

res, err := ss.PostUrl[dtos.HelloResponse](client, "/custom-path", request)

Raw Data Responses

Requesting a string or []byte Response Type returns the raw Response Body, useful for APIs returning content like CSV:

csv, err := ss.GetUrl[string](client, "/api/QueryBookings.csv")

data, err := ss.GetUrl[[]byte](client, "/api/QueryBookings.csv")

Batched Requests

Multiple Request DTOs of the same Type can be sent together in a single Request with SendAll, which returns all their Responses:

responses, err := ss.SendAll(client, []dtos.Hello{‎{Name: "A"}, {Name: "B"}‎})

Or send them to a one-way endpoint that ignores their Responses:

err := ss.Publish(client, dtos.Hello{Name: "World"})

Error Handling

Failed API Requests return a *WebServiceException containing the HTTP Status Code and the API's structured ResponseStatus error:

_, err := ss.Send(client, dtos.CreateBooking{})
if webEx, ok := ss.AsWebServiceException(err); ok {
    fmt.Println(webEx.StatusCode)          // 400
    fmt.Println(webEx.ErrorCode())         // "NotEmpty"
    fmt.Println(webEx.ErrorMessage())      // "'Name' must not be empty."
    fmt.Println(webEx.FieldError("Name"))  // "'Name' must not be empty."
    fmt.Println(webEx.IsUnauthorized())    // false
}

Which also works with the standard errors package:

var webEx *ss.WebServiceException
if errors.As(err, &webEx) { /* ... */ }

Alternatively Api returns errors in its result instead of a separate error, which can be preferable when handling validation errors is part of normal control flow:

api := ss.Api(client, dtos.CreateBooking{})
if api.Failed() {
    fmt.Println(api.ErrorCode(), api.FieldError("Name"))
} else {
    fmt.Println(api.Response.Id)
}

Authenticating using Basic Auth

Basic Auth support is implemented in Client and follows the same API made available in the C# Service Clients:

client.SetCredentials(user, pass)

res, err := ss.Send(client, dtos.SecureRequest{})

Authenticating using Credentials

Alternatively you can authenticate using userName/password credentials with the Authenticate API:

authRes, err := client.Authenticate(userName, password)

This will populate the Client with Session Cookies which will transparently be sent on subsequent requests to make authenticated requests, as well as using any Bearer Token the Server returns.

Authenticating using JWT

Use SetBearerToken to Authenticate with a ServiceStack JWT Provider using a JWT Token:

client.SetBearerToken(jwt)

Alternatively you can use just a Refresh Token instead:

client.SetRefreshToken(refreshToken)

Where the client will automatically fetch a new JWT Bearer Token using the Refresh Token for authenticated requests.

Authenticating using an API Key

Use SetBearerToken to Authenticate with an API Key:

client.SetBearerToken(apiKey)

Transparently handle 401 Unauthorized Responses

If the server returns a 401 Unauthorized Response either because the client was Unauthenticated or the configured Bearer Token or API Key used had expired or was invalidated, you can use the OnAuthenticationRequired callback to re-configure the client before automatically retrying the original request, e.g:

client.OnAuthenticationRequired = func(c *ss.Client) error {
    _, err := c.Authenticate(userName, password)
    return err
}

// Automatically retries requests returning 401 Responses
res, err := ss.Send(client, dtos.Secured{})

Uploading Files

Use PostFileWithRequest to upload a file with an API Request:

file, _ := os.Open("photo.png")
defer file.Close()

res, err := ss.PostFileWithRequest(client, dtos.UploadPhoto{Album: "Holiday"}, ss.UploadFile{
    FieldName:   "file",
    FileName:    "photo.png",
    ContentType: "image/png",
    Reader:      file,
})

To upload multiple files use PostFilesWithRequest.

context.Context

Every API has a *Ctx variant accepting a context.Context for cancellation and timeouts:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

res, err := ss.SendCtx(ctx, client, dtos.Hello{Name: "World"})

Client Configuration

client := ss.NewClient("https://example.org")
client.SetHeader("X-Custom", "Value")
client.SetTimeout(10 * time.Second)
client.SetFollowRedirects(false)
client.UserAgent = "my-app/1.0"

// Inspect or modify each Request and Response
client.RequestFilter = func(req *http.Request) { log.Println(req.Method, req.URL) }
client.ResponseFilter = func(res *http.Response) { log.Println(res.Status) }

// Replace the underlying *http.Client to customize transports, proxies or TLS
client.HttpClient = &http.Client{Timeout: 30 * time.Second}

NewClient sends Requests to ServiceStack's pre-defined /api route. Use NewJsonServiceClient for older ServiceStack instances that only have the /json/reply routes enabled, or SetBasePath for a custom base path.

DTO Customization Options

In most cases you'll just use the generated Go DTOs as-is, however you can further customize how the DTOs are generated by overriding the default options.

The header in the generated DTOs show the different options Go native types support with their defaults. To override a value, remove the // and specify the value to the right of the :. Any uncommented value will be sent to the server to override any server defaults.

/* Options:
Date: 2026-08-06 15:38:36
Version: 10.09
Tip: To override a DTO option, remove "//" prefix before updating
BaseUrl: https://blazor-vue.web-templates.io

//GlobalNamespace:
//MakePropertiesOptional: False
//AddServiceStackTypes: True
//AddResponseStatus: False
//AddImplicitVersion:
//AddDescriptionAsComments: True
//IncludeTypes:
//ExcludeTypes:
//DefaultImports:
*/

GlobalNamespace

Changes the Go package the DTOs are generated in, which defaults to dtos:

package myapi

AddResponseStatus

Automatically add a ResponseStatus property on all Response DTOs, regardless if it wasn't already defined:

type GetTechnologyResponse struct {
	ResponseStatus *ss.ResponseStatus `json:"responseStatus,omitempty"`
}

IncludeTypes

Is used as a Whitelist to specify only the types you would like to have code-generated:

/* Options:
IncludeTypes: GetTechnology,GetTechnologyResponse
*/

To include a Request DTO and all its dependent types, use the .* suffix:

/* Options:
IncludeTypes: GetTechnology.*
*/

Or include all types within a Tag Group with:

/* Options:
IncludeTypes: {tag}
*/

ExcludeTypes

Is used as a Blacklist to specify which types you would like excluded from being generated:

/* Options:
ExcludeTypes: GetTechnology,GetTechnologyResponse
*/

Change Default Server Configuration

The above defaults are also overridable on the ServiceStack Server by modifying the default config on the NativeTypesFeature Plugin, e.g:

//Server example in C#
var nativeTypes = this.GetPlugin<NativeTypesFeature>();
nativeTypes.MetadataTypesConfig.AddResponseStatus = true;

Go specific functionality can be added by the GoGenerator, e.g. to change the module generated DTOs reference:

GoGenerator.LibraryPackage = "github.com/myorg/servicestack-go";

Customize DTO Type generation

Additional Go specific customization can be statically configured like PreTypeFilter, InnerTypeFilter & PostTypeFilter (available in all languages) which can be used to inject custom code in the generated DTOs output, e.g:

GoGenerator.PreTypeFilter = (sb, type) => {
    if (type.IsInterface != true)
    {
        sb.AppendLine("// Generated DTO");
    }
};

There's also PrePropertyFilter & PostPropertyFilter for generating source before and after properties.

Whilst the generic [EmitCode] attribute lets you emit the same code in multiple languages with the same syntax.