Skip to main content

Generate API Boilerplate (for Go)

Wouldn't it be cool if you could also generate all the boilerplate code for your API? You are about to do just that using Go.

info

You will need to have Go installed and in your PATH. Additionally, you need protoc and protoc-gen-go installed.

Protocol Buffer Compiler (protoc) Installation

Install protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Add Go API code generators

Update apex.yaml with the contents below.

apex.yaml
spec: apex.axdl
config:
package: urlshortener
module: github.com/apexlang/getting-started
logger:
import: github.com/go-logr/logr
interface: logr.Logger
generates:
openapi.yaml:
module: 'https://deno.land/x/apex_codegen@v0.1.9/openapiv3/mod.ts'
visitorClass: OpenAPIV3Visitor
proto/service.proto:
module: 'https://deno.land/x/apex_codegen@v0.1.9/proto/mod.ts'
visitorClass: ProtoVisitor
config:
options:
go_package: 'github.com/myorg/myapps/pkg/urlshortener'
runAfter:
- command: |
protoc
--go_out=.
--go_opt=paths=source_relative
--go-grpc_out=.
--go-grpc_opt=paths=source_relative
proto/service.proto
pkg/urlshortener/interfaces.go:
module: 'https://deno.land/x/apex_codegen@v0.1.9/go/mod.ts'
visitorClass: InterfacesVisitor
pkg/urlshortener/api.go:
module: 'https://deno.land/x/apex_codegen@v0.1.9/go/mod.ts'
visitorClass: GoVisitor
config:
protoPackage: github.com/apexlang/getting-started/proto
append:
- module: 'https://deno.land/x/apex_codegen@v0.1.9/go/mod.ts'
visitorClass: FiberVisitor
- module: 'https://deno.land/x/apex_codegen@v0.1.9/go/mod.ts'
visitorClass: GRPCVisitor
pkg/urlshortener/services.go:
ifNotExists: true
module: 'https://deno.land/x/apex_codegen@v0.1.9/go/mod.ts'
visitorClass: ScaffoldVisitor
config:
types:
- service
pkg/urlshortener/repositories.go:
ifNotExists: true
module: 'https://deno.land/x/apex_codegen@v0.1.9/go/mod.ts'
visitorClass: ScaffoldVisitor
config:
names:
- Repository
cmd/main.go:
ifNotExists: false # Change to true to control dependency injection
module: 'https://deno.land/x/apex_codegen@v0.1.9/go/mod.ts'
visitorClass: MainVisitor
info

Now we are using some more options:

  • config - Configuration object that is available to code generation classes. Can be set globally and overridden for each file.
  • ifNotExists - When true, only generates this file if it does not exist.
  • runAfter - Runs commands after code generation. Can be used to call other tools such as protoc.

Run these commands to regenerate the code, initialize the go module, and download the necessary Go dependencies.

apex generate apex.yaml
go mod init github.com/apexlang/getting-started
go mod tidy

Implement the Service Logic

At this point, you have a fully stubbed out URL Shortener service with all the boilerplate code generated. You only need to implement the core logic. To complete the project, update services.go and repositories.go under pkg/urlshortener with the contents below.

package urlshortener

import (
"context"
"errors"
"math/rand"
"time"

"github.com/apexlang/api-go/errorz"
"github.com/apexlang/api-go/transport/httpresponse"
"github.com/go-logr/logr"
)

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

var (
randNum = rand.New(rand.NewSource(time.Now().UnixNano()))
)

type ShortenerImpl struct {
log logr.Logger
repository Repository
}

func NewShortener(log logr.Logger, repository Repository) *ShortenerImpl {
return &ShortenerImpl{
log: log,
repository: repository,
}
}

func (s *ShortenerImpl) Shorten(ctx context.Context, url string) (*URL, error) {
urlResp, err := s.repository.LoadByURL(ctx, url)
if err != nil {
var e *errorz.Error
if !(errors.As(err, &e) && e.Code == errorz.NotFound) {
return nil, err
}
} else if urlResp != nil {
// Return already stored URL
return urlResp, nil
}

newURL := URL{
ID: generateID(8),
URL: url,
}

if err = s.repository.StoreURL(ctx, &newURL); err != nil {
return nil, err
}

return &newURL, nil
}

func (s *ShortenerImpl) Lookup(ctx context.Context, id string) (*URL, error) {
url, err := s.repository.LoadByID(ctx, id)
if err != nil {
return nil, err
}

resp := httpresponse.FromContext(ctx)
if resp != nil {
// HTTP specific logic to perform browser redirect.
resp.Header.Add("Location", url.URL)
resp.Status = 302 // StatusFound
}

return url, nil
}

func generateID(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[randNum.Intn(len(letterBytes))]
}
return string(b)
}

Run and Test the Service

Finally, run the service and test with your favorite API testing tool, like Postman or Rest Client for VS Code.

go run cmd/main.go