In this post, I’ll be using Swagger to build a REST API with Java and Spring Boot. Swagger is an API framework. It uses a YAML-based language to define an API and it has a code generator that supports multiple languages.
Tooling
Swagger offers an online editor which is useful to start playing with the language and get familiar with the syntax. But in order to generate some code, it’s handy to have the code generator locally.
On the left side of the editor you can see the YAML syntax. On the right side, a preview of the API, which is updated real-time as you modify the YAML definition:
Blog REST API
The example REST API will be an API for blogging.
We’re gonna have the following operations:
- POST on /post will create a new blog post
- GET on /post will get a list of posts
- GET on /post/postId (e.g. /post/42) will get that blog post
- POST on /post/postId will update that blog post
- DELETE on /post/postId will delete that blog post
- POST on /post/{postId}/comment creates a new comment
(I hope it’s not too confusing that one of the models is called Post, which is also an HTTP verb)
YAML file
The entire YAML file looks like this:
swagger: "2.0"
info:
description: "A blog API."
version: "1.0.0"
title: "Blog API"
license:
name: "Apache 2.0"
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
host: "blog.acme.io"
basePath: "/v1"
tags:
- name: "post"
description: "Managing posts"
- name: "comment"
description: "Managing comments"
schemes:
- "http"
paths:
/post:
get:
tags:
- "post"
summary: "Get most recent posts"
responses:
200:
description: "List of posts"
schema:
type: array
items:
$ref: '#/definitions/Post'
post:
tags:
- "post"
summary: "Add a new blog post"
parameters:
- in: "body"
name: "body"
description: "Blog post information"
required: true
schema:
$ref: "#/definitions/Post"
responses:
405:
description: "Invalid input"
/post/{postId}:
get:
tags:
- "post"
summary: "Find blog post by ID"
description: "Returns a single blog post"
parameters:
- name: "postId"
in: "path"
description: "ID of post to return"
required: true
type: "integer"
format: "int64"
responses:
200:
description: "successful operation"
schema:
$ref: "#/definitions/Post"
400:
description: "Invalid ID supplied"
404:
description: "Pet not found"
post:
tags:
- "post"
summary: "Updates a blog post in the store"
parameters:
- name: "postId"
in: "path"
description: "ID of pet that needs to be updated"
required: true
type: "integer"
format: "int64"
- in: "body"
name: "body"
description: "Blog post information"
required: true
schema:
$ref: "#/definitions/Post"
responses:
405:
description: "Invalid input"
delete:
tags:
- "post"
summary: "Deletes a blog post"
parameters:
- name: "postId"
in: "path"
description: "ID of blog post to delete"
required: true
type: "integer"
format: "int64"
responses:
400:
description: "Invalid ID supplied"
404:
description: "Blog post not found"
/post/{postId}/comment:
post:
summary: "Create new comment"
tags:
- "comment"
responses:
404:
description: "Post not found"
parameters:
- name: "postId"
in: "path"
required: true
type: "integer"
format: "int64"
definitions:
Post:
type: "object"
properties:
id:
type: "integer"
format: "int64"
title:
type: "string"
body:
type: "string"
type: "string"
createdAt:
type: "string"
format: "date-time"
comments:
type: "array"
items:
$ref: "#/definitions/Comment"
Comment:
type: "object"
properties:
id:
type: "integer"
format: "int64"
body:
type: "string"
type: "string"
createdAt:
type: "string"
format: "date-time"
It takes a while to get familiar with it, so let’s highlight some interesting stuff.
- base path (
basePath: "/v1"
). That means that the entire API is under the "/v1" path. This can be useful if you want to do a major upgrade in your API in a backwards incompatible way. - tags are used to logically group related operations together. It only affects the documentation of the API and how it's presented to the user.
- paths is the place where you define your operations. For example, see the operation that creates a new blog post:
paths:
/post: # the path is /post
post: # the HTTP verb is POST
tags:
- "post"
summary: "Add a new blog post"
parameters:
- in: "body"
name: "body"
description: "Blog post information"
required: true
schema:
$ref: "#/definitions/Post"
responses:
405:
description: "Invalid input"
- definitions is where you define your models. For example, this is how the blog post model is defined:
definitions:
Post:
type: "object"
properties:
id:
type: "integer"
format: "int64"
title:
type: "string"
body:
type: "string"
createdAt:
type: "string"
format: "date-time"
comments:
type: "array"
items:
$ref: "#/definitions/Comment"
The best way to get familiar with this is to read the documentation and play with it.
Generating code
You can see the latest version of swagger-codegen
on the releases page on GitHub, which is 2.3.1 at this time. The jar is not published on GitHub. Here’s a direct link to version 2.3.1. You can find all versions here. Download that jar and put it in a folder (in my case it lives in C:\opt\swagger
).
If you run the code generator without any arguments, you get the list of languages it supports:
PS> java -jar C:\opt\swagger\swagger-codegen-cli-2.3.1.jar
Available languages: [ada, ada-server, akka-scala, android, apache2, apex, aspnetcore, bash, csharp, clojure, cwiki, cpprest, csharp-dotnet2, dart, elixir, elm, eiffel, erlang-client, erlang-server, finch, flash, python-flask, go, go-server, groovy, haskell-http-client, haskell, jmeter, jaxrs-cxf-client, jaxrs-cxf, java, inflector, jaxrs-cxf-cdi, jaxrs-spec, jaxrs, msf4j, java-pkmst, java-play-framework, jaxrs-resteasy-eap, jaxrs-resteasy, javascript, javascript-closure-angular, java-vertx, kotlin, lua, lumen, nancyfx, nodejs-server, objc, perl, php, powershell, pistache-server, python, qt5cpp, r, rails5, restbed, ruby, rust, rust-server, scala, scala-lagom-server, scalatra, scalaz, php-silex, sinatra, slim, spring, dynamic-html, html2, html, swagger, swagger-yaml, swift4, swift3, swift, php-symfony, tizen, typescript-aurelia, typescript-angular, typescript-angularjs, typescript-fetch, typescript-jquery, typescript-node, undertow, ze-ph]
The language we’ll be using is spring.
To see information about how to generate the code, you need to pass help generate
as arguments. To see information about a specific language, you need to pass config-help -l
e.g. config-help -l spring
.
Let’s create an empty folder and save the YAML file in there as swagger.yaml
. At a very minimum, you need to specify the language (in our case that is spring) and the YAML file:
PS> java -jar C:\opt\swagger\swagger-codegen-cli-2.3.1.jar generate -l spring -i .\swagger.yaml
This will generate a maven project in the same location (i.e. pom.xml
and swagger.yaml
will be on the same folder).
By default, the generated code is using Java 7 and a third party library for dates (threetenbp). To use Java 8 and the built-in java.time API, we need to pass an extra flag:
PS> java -jar C:\opt\swagger\swagger-codegen-cli-2.3.1.jar generate -l spring -i .\swagger.yaml --additional-properties dateLibrary=java8
Some obvious things that you’d want to override are the default group and artifact IDs, as well as the packages of the generated source code:
PS> java -jar C:\opt\swagger\swagger-codegen-cli-2.3.1.jar generate -l spring -i .\swagger.yaml --additional-properties dateLibrary=java8,groupId=com.acme,artifactId=blog,basePackage=com.acme.blog,configPackage=com.acme.blog.configuration,apiPackage=com.acme.blog.api,modelPackage=com.acme.blog.model
In any case, at this point you’ve got a ready to run server. Build the package with mvn package
and run it with java -jar target/blog-1.0.0.jar
. You’ll be able to access the server at http://localhost:8080/v1.
Before going any further, I’ll upgrade the pom dependencies to use Spring Boot 2 (from 1.5.9 to 2.0.1) and the latest SpringFox (which changes the appearance of the UI a bit, from 2.7.0 to 2.8.0).
This requires a change in application.properties
(part of breaking changes between Spring Boot 1 and Spring Boot 2):
# old style, Spring Boot 1.5.x
server.contextPath=/v1
# new style, Spring Boot 2
server.servlet.contextPath=/v1
Every time you run the code generation tool, it will overwrite the code it had generated previously. Luckily, it offers a way of leaving certain files intact. That’s that .swagger-codegen-ignore
file, which works just like a .gitignore
file. For now, I add the pom.xml
and application.properties
there:
pom.xml
src/**/application.properties
Advantages
What I like about using swagger is that you get a lot of boilerplate code generated automatically, while it is still just regular Java/Spring code (with some extra Swagger annotations which only affect documentation). This gives you for free a UI which documents your API and offers a playground for people to experiment with it.