I like to continuously refine my skills and knowledge as a software engineer, dipping my toes into the huge variety of software solutions available, and I've decided to put something together making use of the following tools:
- OpenAPI, for the specification.
- oapi-codegen, to generate a server based off the specification.
- Testify, for straightforward test assertions.
- Codecov.
- Docker.
- PostgreSQL.
- GORM (mostly because I make seldom use of ORMs and I'm curious).
- Flyway, for version controlled database migrations.
- Kubernetes.
- Terraform.
- Caddy (probably?).
The API Spec
I'm starting off simple, creating an OpenAPI specification for a user. The user will initially have a name, an email address, and an ID:
components:
schemas:
User:
type: object
properties:
id:
type: string
example: 123
readOnly: true
email:
type: string
example: me@example.com
writeOnly: true
name:
type: string
example: Alice
This schema is used for a POST
to /users
with a 201
response, and a Problem Details JSON Object is used for a 400
response.
Generating The Code
Moving on to oapi-codegen, a tools.go
file has been created as exemplified in its repository. I have also created the file internal/handlers/server.go
:
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=server.config.yaml ../../openapi.yaml
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=types.config.yaml ../../openapi.yaml
package handlers
server.config.yaml
is configured for a strict net/http
server, and types.config.yaml
for the models. A go mod init
, followed by a go mod tidy
and go generate ./...
produces most of what we need to get the new API up and running.
Writing and Testing The Handler
The next steps are adding the following to server.go
, creating a server struct which abides by the generated strict server interface:
var _ StrictServerInterface = (*Server)(nil)
type Server struct{}
func NewServer() Server {
return Server{}
}
Subsequently, a user.go
file is created, where a handler for the POST request is created to match the generated interface:
func (*Server) PostUser(ctx context.Context, request PostUserRequestObject) (PostUserResponseObject, error) {
return nil, nil
}
nil
is returned initially, as we're creating user_test.go
and using Testify to create tests as we shape the behavior of PostUser
. We don't have a DB wired up yet, so the responses will simply be hard-coded structs of what we expect the API to respond with for now to pass the tests.
I'm going to continue hacking away as I write my next blog post. The next steps will likely be building the main.go
and Dockerfile
to fire up the server, and hooking up Codecov to the repository. The repository can be found here if you would like more context to the snippets given in this post: https://github.com/m-dango/demo-api/tree/1c86f975fc451e50c2bedbfeb6bc4317e6c7be21
Top comments (0)