Back to Blog
ProductDock: Jovica Zorić

3 minutes read

Building APIs made easy: From specification to code generation and testing

Jovica Zorić

Chief Technology Officer

APIs (Application Programming Interface) have become one of the most important components of modern digital applications. Whether you’re developing a mobile app that needs to interact with a server, integrating third-party services, or building microservices, a robust and well-designed API is essential. Building APIs can be a challenging task, often involving complex workflows.

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:

  1. Use TypeSpec to describe our API
  2. Generate OpenAPI specification
  3. Use code-gen tools to create server-side boilerplate code and add our business logic 
  4. 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

> What’s your approach to defining APIs? What tools are you using?

Jovica Zoric

Jovica Zorić

Chief Technology Officer

Jovica is a techie with more than ten years of experience. His job has evolved throughout the years, leading to his present position as the CTO at ProductDock. He will help you to choose the right technology for your business ideas and focus on the outcomes.


Related posts.