- DE
- Services
- Our service portfolio
We bring your digital product vision to life, from crafting real-world testable prototypes to delivering comprehensive product solutions.
- Collaboration models
Explore collaboration models customized to your specific needs: Complete nearshoring teams, Local heroes from partners with the nearshoring team, or Mixed tech teams with partners.
- Way of working
Through close collaboration with your business, we create customized solutions aligned with your specific requirements, resulting in sustainable outcomes.
- Our service portfolio
- About Us
- Who we are
We are a full-service nearshoring provider for digital software products, uniquely positioned as a high-quality partner with native-speaking local experts, perfectly aligned with your business needs.
- Meet our team
ProductDock’s experienced team proficient in modern technologies and tools, boasts 15 years of successful projects, collaborating with prominent companies.
- Our locations
We are ProductDock, a full-service nearshoring provider for digital software products, headquartered in Berlin, with engineering hubs in Lisbon, Novi Sad, Banja Luka, and Doboj.
- Why nearshoring
Elevate your business efficiently with our premium full-service software development services that blend nearshore and local expertise to support you throughout your digital product journey.
- Who we are
- Our work
- Career
- Life at ProductDock
We’re all about fostering teamwork, creativity, and empowerment within our team of over 120 incredibly talented experts in modern technologies.
- Open positions
Do you enjoy working on exciting projects and feel rewarded when those efforts are successful? If so, we’d like you to join our team.
- Candidate info guide
How we choose our crew members? We think of you as a member of our crew. We are happy to share our process with you!
- Life at ProductDock
- Newsroom
- News
Stay engaged with our most recent updates and releases, ensuring you are always up-to-date with the latest developments in the dynamic world of ProductDock.
- Events
Expand your expertise through networking with like-minded individuals and engaging in knowledge-sharing sessions at our upcoming events.
- News
- Blog
- Get in touch
25. Jul 2024 •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:
- 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.
> What’s your approach to defining APIs? What tools are you using?
Tags:
Jovica Zorić
Chief Technology OfficerJovica 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.