In this post, we’ll explore one workflow of API development using TypeSpec and OpenAPI, Golang/Gin, Java/Spring Boot, and Postman, covering the steps from describing our API to testing it.
We will follow these steps:
- Use TypeSpec to describe our API
- Generate OpenAPI specification
- Use code-gen tools to create server-side boilerplate code and add our business logic
- Use Postman to test our API
Before we start, make sure you have:
-
Typespec:
npm install -g @typespec/compiler
-
oapi-codegen:
go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@latest
-
Postman
Use TypeSpec to describe our API
TypeSpec is a robust tool designed to simplify the API development process by allowing you to define your API specifications in a clear and structured manner. With TypeSpec, you can describe your API endpoints, request parameters, responses, and more in a standardized format.
It is a highly extensible language with primitives that can describe API shapes common among REST, OpenAPI, gRPC, and other protocols. Checkout their documentation and sample specifications.
Our example is an API for managing Events:
@resource("events")
model Event {
@key
id: string;
@doc("The name of the event")
name: string;
@doc("The scheduled date and time of the event")
date: utcDateTime;
@doc("Information about the venue where the event will be held")
location: Location;
}
model Location {
name: string;
}
@doc("Error")
@error
model EventError {
code: int32;
message: string;
}
@service({
title: "Event Service",
})
@versioned(Versions)
namespace EventService;
@route("/events")
@tag("Events")
interface Events {
@added(Versions.v1)
op create(@body event: Event): Event | EventError;
@added(Versions.v1)
op findByNameFuzzy(@query name: string): Event[];
@added(Versions.v1)
op read(@path id: string): Event;
@delete
@added(Versions.v1)
op delete(@path id: string): void;
}
enum Versions {
v1,
}
As we can see, reading TypeSpec is straightforward, and even though writing the spec in VS Code is all right, I’m missing other editors’ support. Hopefully, this will change in the future. Here, in the Events interface, I used basic CRUD operations.
What is also interesting is that TypeSpec has Interfaces and Operations templates that can be used to describe the APIs even faster. Check this example from the petstore sample.
Generate OpenAPI specification
Having described our API spec, we can use the TypeSpec compiler/CLI to generate an OpenAPI spec file. We want
OpenAPIv3, so we will use the OpenAPI3 emitter. Let’s run: tsp compile
and see our OpenAPIV3 specification.
openapi: 3.0.0
info:
title: Event Service
version: v1
tags:
- name: Events
paths:
/events:
post:
tags:
- Events
operationId: Events_create
parameters: []
responses:
"200":
description: The request has succeeded.
content:
application/json:
schema:
$ref: "#/components/schemas/Event"
default:
description: An unexpected error response.
content:
application/json:
schema:
$ref: "#/components/schemas/EventError"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Event"
get:
tags:
- Events
operationId: Events_findByNameFuzzy
parameters:
- name: name
in: query
required: true
schema:
type: string
responses:
"200":
description: The request has succeeded.
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Event"
/events/{id}:
get:
tags:
- Events
operationId: Events_read
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: The request has succeeded.
content:
application/json:
schema:
$ref: "#/components/schemas/Event"
delete:
tags:
- Events
operationId: Events_delete
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"204":
description: "There is no content to send for this request, but the headers may be useful. "
components:
schemas:
Event:
type: object
required:
- id
- name
- date
- location
properties:
id:
type: string
name:
type: string
description: The name of the event
date:
type: string
format: date-time
description: The scheduled date and time of the event
location:
allOf:
- $ref: "#/components/schemas/Location"
description: Information about the venue where the event will be held
EventError:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
description: Error
Location:
type: object
required:
- name
properties:
name:
type: string
Versions:
type: string
enum:
- v1
Use code-gen tools to create server-side boilerplate code and add our business logic
We will use code generation tools to reduce the boilerplate required to create code based on OpenAPI and instead focus on writing the business logic. Examples are in Golang/Gin and Java/Spring Boot. Here I’ll write the building blocks while you can find the complete code in the github repository.
Golang/Gin
First install oapi-code gen: go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
and create the config file.
events.gen.yml
package: api
generate:
models: true
gin-server: true
output: events.gen.go
Use go generate directive:
events.go
//go:generate oapi-codegen --config=events.gen.yml ../../tsp-output/@typespec/openapi3/openapi.v1.yaml
package api
....
Run: go generate ./…
to generate the code.
After boilerplate code is generated, we need to implement our server interface (our handlers):
// ServerInterface represents all server handlers.
type ServerInterface interface {
// (GET /events)
EventsFindByNameFuzzy(c *gin.Context, params EventsFindByNameFuzzyParams)
// (POST /events)
EventsCreate(c *gin.Context)
// (DELETE /events/{id})
EventsDelete(c *gin.Context, id string)
// (GET /events/{id})
EventsRead(c *gin.Context, id string)
}
Check the example implementation. I used Intellij for a quick “implement this interface” function.
Java/Spring Boot
In the Maven project, first add these two dependencies:
<!-- https://mvnrepository.com/artifact/org.openapitools/jackson-databind-nullable -->
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
<version>0.2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.8.0</version>
</dependency>
And use the openapi-generator-maven-plugin
:
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>7.6.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>../tsp-output/@typespec/openapi3/openapi.v1.yaml</inputSpec>
<generatorName>spring</generatorName>
<apiPackage>com.productdock.openapi.api</apiPackage>
<modelPackage>com.productdock.openapi.model</modelPackage>
<generateSupportingFiles>false</generateSupportingFiles>
<configOptions>
<skipDefaultInterface>true</skipDefaultInterface>
<delegatePattern>false</delegatePattern>
<interfaceOnly>true</interfaceOnly>
<library>spring-boot</library>
<oas3>true</oas3>
<useSpringController>false</useSpringController>
<!-- javax.* to jakarta.* -->
<useSpringBoot3>true</useSpringBoot3>
<useSpringfox>false</useSpringfox>
<apiFirst>false</apiFirst>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
Run: ./mvnw clean compile
to generate the code.
For the implementation, we can create a Spring boot controller by implementing the EventsAPI interface.
Use Postman to test our API
Before you start testing the API, remember to run the server API, which will be running on port 8084.
Using portman run:
npx @apideck/portman -l tsp-output/@typespec/openapi3/openapi.v1.yaml -o events-postman-collection.json -b http://localhost:8084 -t false
This will generate an events-postman-collection.json file which you can import to Postman and start testing your APIs.
The straightforward workflow has helped me build proof-of-concepts and walking skeletons. Besides editor support, I really enjoyed this design-first approach with TypeSpec. Looking forward to their next milestones.
Originally published in the ProductDock blog section