Automate SDK generation for Goa APIs
Supported SDK languages:
TypeScript / Javascript | Java / Kotlin | Python | C# | Go | PHP |
---|---|---|---|---|---|
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
- Before getting started make sure you have a liblab account and the liblab CLI installed.
- If you don't have an existing project but want to try out liblab then check out our standalone tutorials.
Providing an SDK for your API can significantly ease the development process for users, ensuring quicker integration and encouraging wider adoption due to the streamlined development experience.
In this doc you'll learn how to take your existing Goa API and generate user-friendly SDKs for most major programming languages using the liblab SDK generator. After a short bit of setup you'll have a pipeline that will automatically generate SDKs for your API whenever you make changes.
Initializing the SDK
First, create a new directory to store your SDK. This directory should be separate from your existing project directory:
mkdir Goa-sdk
cd Goa-sdk
And initialize your project to create a liblab.config.json
file:
liblab init -y
Getting an OpenAPI Spec File
In order to auto-generate SDKs from a Goa project you need an OpenAPI Spec File. The OpenAPI file contains information about your API, such as servers, paths, operations, parameters, responses, and security schemas.
Goa automatically generates an OpenAPI spec for your API. However, you need to use its declarative DSL to define the API structure and ensure a complete spec.
Configuration
The first step to generating your OpenAPI Spec file is to define your service's metadata. This includes information like Title
, Description
, Version
, and Server
:
Title
: The name for your API.Description
: An explanation of what your API does.Version
: The API version following semantic versioning.Server
: Defines the base URL for your server. You can use variables or define multiple servers.
The following code snippet shows an example of metadata configuration for a greeting service:
// ...
var _ = API("greeting", func() {
Title("Greeting Service")
Description("The Greeting Service offers a simple API that returns personalized greeting messages based on the provided name. ")
Version("1.0.0")
Server("greeting", func() {
Host("localhost", func() {
URI("http://localhost:8080")
})
})
})
// ...
You then define your endpoints using Goa's annotations. Some common endpoint annotations include:
Description
: Provides information about methods and services.Payload
: Defines the input data, including the request body or parameters, that a method receives.Attribute
: Defines a field within aPayload
,Result
, orType
, specifying field’s name, type, description, and optional constraints.Required
: Marks specific attributes as mandatory.Result
: Defines the output data structure of aMethod
.Response
: Specifies the HTTP response details for aMethod
, including the status code and the response body format.
The following code snippet shows an example of a greeting
service with a greet
method that receives a name and returns a greeting message.
// ...
var _ = Service("greeting", func() {
Description("Clients can request a greeting by specifying a name, and the service will respond with a personalized message.")
Method("greet", func() {
Description("Generates a personalized greeting message for the given name.")
Payload(func() {
Attribute("name", String, "The name of the person to greet.", func() {
Example("John")
})
Required("name")
})
Result(String, func() {
Example("Hello, John!")
})
HTTP(func() {
GET("/greeting/{name}")
Response(StatusOK)
})
})
})
// ...
To explore more configuration options with Goa and OpenAPI check out Goa's Design Language documentation:
- Data Modeling: Define your service’s data structures.
- API Definition: Configure global API properties.
- Services & Methods: Specify services and their methods.
- Security: Implement authentication and authorization.
Generate Server Code
After defining your API metadata and services, generate your server's code by running the following command from your project root:
goa gen github.com/yourorg/your-goa-project/design
Your OpenAPI specification files will be in gen/http/
. Goa automatically creates several specification files, including OpenAPI 3.0 (e.g., openapi3.json
) which is the one we'll use for this guide.
Build the Server
Once the server code is generated, create a main file to build and run the server. In this file, you'll reference the generated services and specify how to serve the static files from the API.
The following code snippet shows a complete server implementation that provides the greeting service and serves all generated specification files from the docs
directory via the /docs/
endpoint.
//...
// greetingSvc implements the generated greeting.Service interface
type greetingSvc struct{}
// Greet handles requests by returning a personalized greeting
func (s *greetingSvc) Greet(ctx context.Context, p *greeting.GreetPayload) (string, error) {
return "Hello, " + p.Name, nil
}
func main() {
//...
// Serve static OpenAPI documentation
docsHandler := http.StripPrefix("/docs/", http.FileServer(http.Dir("./gen/http")))
mainMux.Handle("/docs/", docsHandler)
// Start the HTTP server
srv := &http.Server{
Addr: ":8080",
Handler: mainMux,
}
log.Println("Server is running on http://localhost:8080")
log.Println("OpenAPI docs are available at http://localhost:8080/docs/openapi3.json (or .yaml)")
log.Fatal(srv.ListenAndServe())
}
After specifying the main server code, build the project running:
go build -o greeting ./cmd/greeting
To start the server, run the generated binary file:
./greeting
You should see the following message on your terminal:
Server is running on http://localhost:8080
OpenAPI docs are available at http://localhost:8080/docs/openapi3.json (or .yaml)
Complete Example
Putting the above steps together produces a complete example of generating an Openapi spec using the Goa Framework:
- File Tree
- Main
- Design
├── cmd
│ └── greeting
│ └── main.go
├── design
│ └── design.go
├── gen
│ ├── greeting
│ │ ├── client.go
│ │ ├── endpoints.go
│ │ └── service.go
│ └── http
│ ├── cli
│ │ └── greeting
│ │ └── cli.go
│ ├── greeting
│ │ ├── client
│ │ │ ├── client.go
│ │ │ ├── cli.go
│ │ │ ├── encode_decode.go
│ │ │ ├── paths.go
│ │ │ └── types.go
│ │ └── server
│ │ ├── encode_decode.go
│ │ ├── paths.go
│ │ ├── server.go
│ │ └── types.go
│ ├── openapi3.json
│ ├── openapi3.yaml
│ ├── openapi.json
│ └── openapi.yaml
├── go.mod
├── go.sum
└── greeting
package main
import (
"context"
"log"
"net/http"
"github.com/yourusername/greeting-service/gen/greeting"
greetingsvr "github.com/yourusername/greeting-service/gen/http/greeting/server"
goahttp "goa.design/goa/v3/http"
)
// greetingSvc implements the generated greeting.Service interface.
type greetingSvc struct{}
// Greet now receives a pointer to greeting.GreetPayload.
func (s *greetingSvc) Greet(ctx context.Context, p *greeting.GreetPayload) (string, error) {
return "Hello, " + p.Name, nil
}
func main() {
// Create the service implementation.
svc := &greetingSvc{}
// Create endpoints from the service implementation.
endpoints := greeting.NewEndpoints(svc)
// Create HTTP server components.
muxer := goahttp.NewMuxer()
decoder := goahttp.RequestDecoder
encoder := goahttp.ResponseEncoder
// Define an error handler with the required signature.
errorHandler := func(ctx context.Context, w http.ResponseWriter, err error) {
log.Printf("Error: %v", err)
}
// Define a status handler (returns nil in this simple example).
statusHandler := func(ctx context.Context, err error) goahttp.Statuser {
return nil
}
// Create the generated HTTP server by supplying all required parameters.
serverInstance := greetingsvr.New(
endpoints,
muxer,
decoder,
encoder,
errorHandler,
statusHandler,
)
// Mount the generated server on the muxer.
serverInstance.Mount(muxer)
// Create a new ServeMux to combine the GOA API and a static file server.
mainMux := http.NewServeMux()
// Use the GOA muxer for the API endpoints.
mainMux.Handle("/", muxer)
// Serve static files from the "gen/http" directory at the "/docs/" URL path.
docsHandler := http.StripPrefix("/docs/", http.FileServer(http.Dir("./gen/http")))
mainMux.Handle("/docs/", docsHandler)
// Create and start the HTTP server.
srv := &http.Server{
Addr: ":8080",
Handler: mainMux,
}
log.Println("Server is running on http://localhost:8080")
log.Println("OpenAPI docs are available at http://localhost:8080/docs/openapi3.json (or .yaml)")
log.Fatal(srv.ListenAndServe())
}
package design
import (
. "goa.design/goa/v3/dsl"
)
var _ = API("greeting", func() {
Title("Greeting Service")
Description("The Greeting Service offers a simple API that returns personalized greeting messages based on the provided name. ")
Version("1.0.0")
Server("greeting", func() {
Host("localhost", func() {
URI("http://localhost:8080")
})
})
})
var _ = Service("greeting", func() {
Description("lients can request a greeting by specifying a name, and the service will respond with a personalized message.")
Method("greet", func() {
Description("Generates a personalized greeting message for the given name.")
Payload(func() {
Attribute("name", String, "The name of the person to greet.", func() {
Example("John")
})
Required("name")
})
Result(String, func() {
Example("Hello, John!")
})
HTTP(func() {
GET("/greeting/{name}")
Response(StatusOK)
})
})
})
Click to see the OpenAPI spec example
{
"openapi": "3.0.3",
"info": {
"title": "Greeting Service",
"description": "The Greeting Service offers a simple API that returns personalized greeting messages based on the provided name. ",
"version": "1.0.0"
},
"servers": [
{
"url": "http://localhost:8080"
}
],
"paths": {
"/greeting/{name}": {
"get": {
"tags": [
"greeting"
],
"summary": "greet greeting",
"description": "Generates a personalized greeting message for the given name.",
"operationId": "greeting#greet",
"parameters": [
{
"name": "name",
"in": "path",
"description": "The name of the person to greet.",
"required": true,
"schema": {
"type": "string",
"description": "The name of the person to greet.",
"example": "John"
},
"example": "John"
}
],
"responses": {
"200": {
"description": "OK response.",
"content": {
"application/json": {
"schema": {
"type": "string",
"example": "Hello, John!"
},
"example": "Hello, John!"
}
}
}
}
}
}
},
"components": {
},
"tags": [
{
"name": "greeting",
"description": "lients can request a greeting by specifying a name, and the service will respond with a personalized message."
}
]
}
Copy Your Spec
Now that your openapi.json
file is generated and available through the endpoint, you're ready to proceed. Save your openapi3.json
file in the liblab project directory created during the initialization step:
cd ../Goa-sdk
curl -o openapi.json http://localhost:8080/docs/openapi3.json
Configuring liblab
Now you'll need to make some minor updates to your liblab.config.json
file in your Goa-sdk folder:
- Point the
specFilePath
parameter to the location of your OpenAPI spec file (ex../openapi.json
). - Specify the
baseUrl
of your API. This is the URL that the SDK will use to make requests to your API.
The top of the file should then looks something like this:
{
"sdkName": "Goa-sdk",
"apiVersion": "1.0.0",
"apiName": "Goa-api",
"specFilePath": "./openapi.json",
"baseUrl": "http://localhost:PORT",
"languages": [
"go",
"java",
"python",
"typescript",
"csharp",
"php"
],
"auth": [
"bearer"
]
}
liblab's SDK generator supports many more advanced URL and environment configuration options than the basic configuration shown here.
Explore the configuration documentation to discover all the available settings and enhancements or review the SDK customization options for tailored adjustments.
Generate the SDK
During build you might see warnings about the OpenAPI spec. These are often minor issues that can be fixed later.
Now that you have an OpenAPI spec file and have finished setting the liblab.config.json
file, it's time to generate our SDK:
liblab build -y
The CLI will validate the OpenAPI spec and notify you about any issues with it or the liblab.config.json
.
The output will look something like this:
✓ No issues detected in the liblab config file.
No hooks found, SDKs will be generated without hooks.
⚠ Validation succeeded with warnings
Created /Users/username/projects/Goa-sdk/output/api-schema-validation.json with the full linting results
Next you'll see the builds started and once they're done you'll see a message like this:
Your SDKs are being generated. Visit the liblab portal (https://app.liblab.com/apis/Goa-sdk/builds/1234) to view more details on your build(s).
✓ C# built
✓ Go built
✓ Java built
✓ PHP built
✓ Python built
✓ TypeScript built
✓ Generate package-lock.json for TypeScript
Successfully generated SDKs for Python, Java, Go, TypeScript, C#, PHP. ♡ You can find them inside: /Users/username/projects/Goa-sdk/output
If we go inside the output
directory, we will see a directory for each of our SDKs:
ls output/
api-schema-validation.json go php typescript
csharp java python
Try out your SDK
The following instructions assume you have already set up the respective development environment for the language you are testing. If necessary refer to each language's official documentation before proceeding.
Learn more about the language versions liblab generated SDKs support.
The generated SDKs are intended to be deployed to package managers for end users. The instructions below to test your SDK locally will differ from user-facing instructions.
- TypeScript / JavaScript
- Python
- Java
- C#
cd output/typescript/examples
npm run setup
npm run start
cd output/python/examples
chmod +x install.sh
./install.sh
source .venv/bin/activate
python3 sample.py
cd output/java/example
chmod +x run.sh
./run.sh
cd output/csharp/Example
dotnet run --framework net9.0
Next Steps
Now that you've packaged your SDKs you can learn how to integrate them with your CI/CD pipeline and publish them to their respective package manager repositories.
We currently have guides for: