For the last five years, I’ve had the quote “everything begins with an idea” on the wall of my office.
My wife found this product on Etsy shortly after I started developing an API collection for a fitness application. I love this statement because it captures the passion that consumes me during the creation stages of a new project. This is still my favorite aspect of being an engineer, even three decades into my career.
What I’ve learned during this time is that an idea only matters if someone has the opportunity to experience it. If an idea takes too long to become a reality, you end up with a missed opportunity as someone else beats you to the punch. This is why startups are always racing to get their ideas to market as quickly as possible.
Let’s walk through how we can make an idea a reality … quickly.
Assumptions
For this article, we’ll keep things simple. We’ll use Java 17 and Spring Boot 3 to create a RESTful API. In this example, we’ll use Gradle for our build automation.
While the service idea we plan to take to market would normally use a persistence layer, we’ll set that aside for this example and statically define our data within a repository class.
We won’t worry about adding any security for this example, simply allowing anonymous access for this proof of concept.
The Motivational Quotes API
Let’s assume our idea is a motivational quotes API. To make sure we are racing as fast as possible, I asked ChatGPT to create an OpenAPI spec for me.
Within seconds, ChatGPT provided the response:
Here’s the OpenAPI specification, in YAML, that ChatGPT generated:
<span>openapi</span><span>:</span> <span>3.0.0</span><span>info</span><span>:</span><span>title</span><span>:</span> <span>Motivational Quotes API</span><span>description</span><span>:</span> <span>An API that provides motivational quotes.</span><span>version</span><span>:</span> <span>1.0.0</span><span>servers</span><span>:</span><span>-</span> <span>url</span><span>:</span> <span>https://api.example.com</span><span>description</span><span>:</span> <span>Production server</span><span>paths</span><span>:</span><span>/quotes</span><span>:</span><span>get</span><span>:</span><span>summary</span><span>:</span> <span>Get all motivational quotes</span><span>operationId</span><span>:</span> <span>getAllQuotes</span><span>responses</span><span>:</span><span>'</span><span>200'</span><span>:</span><span>description</span><span>:</span> <span>A list of motivational quotes</span><span>content</span><span>:</span><span>application/json</span><span>:</span><span>schema</span><span>:</span><span>type</span><span>:</span> <span>array</span><span>items</span><span>:</span><span>$ref</span><span>:</span> <span>'</span><span>#/components/schemas/Quote'</span><span>/quotes/random</span><span>:</span><span>get</span><span>:</span><span>summary</span><span>:</span> <span>Get a random motivational quote</span><span>operationId</span><span>:</span> <span>getRandomQuote</span><span>responses</span><span>:</span><span>'</span><span>200'</span><span>:</span><span>description</span><span>:</span> <span>A random motivational quote</span><span>content</span><span>:</span><span>application/json</span><span>:</span><span>schema</span><span>:</span><span>$ref</span><span>:</span> <span>'</span><span>#/components/schemas/Quote'</span><span>/quotes/{id}</span><span>:</span><span>get</span><span>:</span><span>summary</span><span>:</span> <span>Get a motivational quote by ID</span><span>operationId</span><span>:</span> <span>getQuoteById</span><span>parameters</span><span>:</span><span>-</span> <span>name</span><span>:</span> <span>id</span><span>in</span><span>:</span> <span>path</span><span>required</span><span>:</span> <span>true</span><span>schema</span><span>:</span><span>type</span><span>:</span> <span>integer</span><span>responses</span><span>:</span><span>'</span><span>200'</span><span>:</span><span>description</span><span>:</span> <span>A motivational quote</span><span>content</span><span>:</span><span>application/json</span><span>:</span><span>schema</span><span>:</span><span>$ref</span><span>:</span> <span>'</span><span>#/components/schemas/Quote'</span><span> </span><span>'</span><span>404'</span><span>:</span><span>description</span><span>:</span> <span>Quote not found</span><span>components</span><span>:</span><span>schemas</span><span>:</span><span>Quote</span><span>:</span><span>type</span><span>:</span> <span>object</span><span>required</span><span>:</span><span>-</span> <span>id</span><span>-</span> <span>quote</span><span>properties</span><span>:</span><span>id</span><span>:</span><span>type</span><span>:</span> <span>integer</span><span>quote</span><span>:</span><span>type</span><span>:</span> <span>string</span><span>openapi</span><span>:</span> <span>3.0.0</span> <span>info</span><span>:</span> <span>title</span><span>:</span> <span>Motivational Quotes API</span> <span>description</span><span>:</span> <span>An API that provides motivational quotes.</span> <span>version</span><span>:</span> <span>1.0.0</span> <span>servers</span><span>:</span> <span>-</span> <span>url</span><span>:</span> <span>https://api.example.com</span> <span>description</span><span>:</span> <span>Production server</span> <span>paths</span><span>:</span> <span>/quotes</span><span>:</span> <span>get</span><span>:</span> <span>summary</span><span>:</span> <span>Get all motivational quotes</span> <span>operationId</span><span>:</span> <span>getAllQuotes</span> <span>responses</span><span>:</span> <span>'</span><span>200'</span><span>:</span> <span>description</span><span>:</span> <span>A list of motivational quotes</span> <span>content</span><span>:</span> <span>application/json</span><span>:</span> <span>schema</span><span>:</span> <span>type</span><span>:</span> <span>array</span> <span>items</span><span>:</span> <span>$ref</span><span>:</span> <span>'</span><span>#/components/schemas/Quote'</span> <span>/quotes/random</span><span>:</span> <span>get</span><span>:</span> <span>summary</span><span>:</span> <span>Get a random motivational quote</span> <span>operationId</span><span>:</span> <span>getRandomQuote</span> <span>responses</span><span>:</span> <span>'</span><span>200'</span><span>:</span> <span>description</span><span>:</span> <span>A random motivational quote</span> <span>content</span><span>:</span> <span>application/json</span><span>:</span> <span>schema</span><span>:</span> <span>$ref</span><span>:</span> <span>'</span><span>#/components/schemas/Quote'</span> <span>/quotes/{id}</span><span>:</span> <span>get</span><span>:</span> <span>summary</span><span>:</span> <span>Get a motivational quote by ID</span> <span>operationId</span><span>:</span> <span>getQuoteById</span> <span>parameters</span><span>:</span> <span>-</span> <span>name</span><span>:</span> <span>id</span> <span>in</span><span>:</span> <span>path</span> <span>required</span><span>:</span> <span>true</span> <span>schema</span><span>:</span> <span>type</span><span>:</span> <span>integer</span> <span>responses</span><span>:</span> <span>'</span><span>200'</span><span>:</span> <span>description</span><span>:</span> <span>A motivational quote</span> <span>content</span><span>:</span> <span>application/json</span><span>:</span> <span>schema</span><span>:</span> <span>$ref</span><span>:</span> <span>'</span><span>#/components/schemas/Quote'</span> <span> </span><span>'</span><span>404'</span><span>:</span> <span>description</span><span>:</span> <span>Quote not found</span> <span>components</span><span>:</span> <span>schemas</span><span>:</span> <span>Quote</span><span>:</span> <span>type</span><span>:</span> <span>object</span> <span>required</span><span>:</span> <span>-</span> <span>id</span> <span>-</span> <span>quote</span> <span>properties</span><span>:</span> <span>id</span><span>:</span> <span>type</span><span>:</span> <span>integer</span> <span>quote</span><span>:</span> <span>type</span><span>:</span> <span>string</span>openapi: 3.0.0 info: title: Motivational Quotes API description: An API that provides motivational quotes. version: 1.0.0 servers: - url: https://api.example.com description: Production server paths: /quotes: get: summary: Get all motivational quotes operationId: getAllQuotes responses: '200': description: A list of motivational quotes content: application/json: schema: type: array items: $ref: '#/components/schemas/Quote' /quotes/random: get: summary: Get a random motivational quote operationId: getRandomQuote responses: '200': description: A random motivational quote content: application/json: schema: $ref: '#/components/schemas/Quote' /quotes/{id}: get: summary: Get a motivational quote by ID operationId: getQuoteById parameters: - name: id in: path required: true schema: type: integer responses: '200': description: A motivational quote content: application/json: schema: $ref: '#/components/schemas/Quote' '404': description: Quote not found components: schemas: Quote: type: object required: - id - quote properties: id: type: integer quote: type: string
Enter fullscreen mode Exit fullscreen mode
I only needed to make one manual update—making sure the id
and quote
properties were required for the Quote
schema. And that’s only because I forgot to mention this constraint to ChatGPT in my original prompt.
With that, we’re ready to develop the new service using an API-First approach.
Building the Spring Boot Service Using API-First
For this example, I’ll use the Spring Boot CLI to create a new project. Here’s how you can install the CLI using Homebrew:
<span>$ </span>brew tap spring-io/tap<span>$ </span>brew <span>install </span>spring-boot<span>$ </span>brew tap spring-io/tap <span>$ </span>brew <span>install </span>spring-boot$ brew tap spring-io/tap $ brew install spring-boot
Enter fullscreen mode Exit fullscreen mode
Create a new Spring Boot Service
We’ll call the project quotes
, creating it with the following command:
<span>$ </span>spring init <span>--dependencies</span><span>=</span>web quotes<span>$ </span>spring init <span>--dependencies</span><span>=</span>web quotes$ spring init --dependencies=web quotes
Enter fullscreen mode Exit fullscreen mode
Let’s examine the contents of the quotes
folder:
<span>$ </span><span>cd </span>quotes <span>&&</span> <span>ls</span> <span>-la</span>total 72drwxr-xr-x@ 11 jvester 352 Mar 1 10:57 <span>.</span>drwxrwxrwx@ 90 jvester 2880 Mar 1 10:57 ..<span>-rw-r--r--</span>@ 1 jvester 54 Mar 1 10:57 .gitattributes<span>-rw-r--r--</span>@ 1 jvester 444 Mar 1 10:57 .gitignore<span>-rw-r--r--</span>@ 1 jvester 960 Mar 1 10:57 HELP.md<span>-rw-r--r--</span>@ 1 jvester 545 Mar 1 10:57 build.gradledrwxr-xr-x@ 3 jvester 96 Mar 1 10:57 gradle<span>-rwxr-xr-x</span>@ 1 jvester 8762 Mar 1 10:57 gradlew<span>-rw-r--r--</span>@ 1 jvester 2966 Mar 1 10:57 gradlew.bat<span>-rw-r--r--</span>@ 1 jvester 28 Mar 1 10:57 settings.gradledrwxr-xr-x@ 4 jvester 128 Mar 1 10:57 src<span>$ </span><span>cd </span>quotes <span>&&</span> <span>ls</span> <span>-la</span> total 72 drwxr-xr-x@ 11 jvester 352 Mar 1 10:57 <span>.</span> drwxrwxrwx@ 90 jvester 2880 Mar 1 10:57 .. <span>-rw-r--r--</span>@ 1 jvester 54 Mar 1 10:57 .gitattributes <span>-rw-r--r--</span>@ 1 jvester 444 Mar 1 10:57 .gitignore <span>-rw-r--r--</span>@ 1 jvester 960 Mar 1 10:57 HELP.md <span>-rw-r--r--</span>@ 1 jvester 545 Mar 1 10:57 build.gradle drwxr-xr-x@ 3 jvester 96 Mar 1 10:57 gradle <span>-rwxr-xr-x</span>@ 1 jvester 8762 Mar 1 10:57 gradlew <span>-rw-r--r--</span>@ 1 jvester 2966 Mar 1 10:57 gradlew.bat <span>-rw-r--r--</span>@ 1 jvester 28 Mar 1 10:57 settings.gradle drwxr-xr-x@ 4 jvester 128 Mar 1 10:57 src$ cd quotes && ls -la total 72 drwxr-xr-x@ 11 jvester 352 Mar 1 10:57 . drwxrwxrwx@ 90 jvester 2880 Mar 1 10:57 .. -rw-r--r--@ 1 jvester 54 Mar 1 10:57 .gitattributes -rw-r--r--@ 1 jvester 444 Mar 1 10:57 .gitignore -rw-r--r--@ 1 jvester 960 Mar 1 10:57 HELP.md -rw-r--r--@ 1 jvester 545 Mar 1 10:57 build.gradle drwxr-xr-x@ 3 jvester 96 Mar 1 10:57 gradle -rwxr-xr-x@ 1 jvester 8762 Mar 1 10:57 gradlew -rw-r--r--@ 1 jvester 2966 Mar 1 10:57 gradlew.bat -rw-r--r--@ 1 jvester 28 Mar 1 10:57 settings.gradle drwxr-xr-x@ 4 jvester 128 Mar 1 10:57 src
Enter fullscreen mode Exit fullscreen mode
Next, we edit the build.gradle
file as shown below to adopt the API-First approach.
<span>plugins</span> <span>{</span><span>id</span> <span>'</span><span>java</span><span>'</span><span>id</span> <span>'</span><span>org</span><span>.</span><span>springframework</span><span>.</span><span>boot</span><span>'</span> <span>version</span> <span>'</span><span>3.4</span><span>.</span><span>3</span><span>'</span><span>id</span> <span>'</span><span>io</span><span>.</span><span>spring</span><span>.</span><span>dependency</span><span>-</span><span>management</span><span>'</span> <span>version</span> <span>'</span><span>1.1</span><span>.</span><span>7</span><span>'</span><span>id</span> <span>'</span><span>org</span><span>.</span><span>openapi</span><span>.</span><span>generator</span><span>'</span> <span>version</span> <span>'</span><span>7.12</span><span>.</span><span>0</span><span>'</span><span>}</span><span>openApiGenerate</span> <span>{</span><span>generatorName</span> <span>=</span> <span>"spring"</span><span>inputSpec</span> <span>=</span> <span>"$rootDir/src/main/resources/static/openapi.yaml"</span><span>outputDir</span> <span>=</span> <span>"$buildDir/generated"</span><span>apiPackage</span> <span>=</span> <span>"com.example.api"</span><span>modelPackage</span> <span>=</span> <span>"com.example.model"</span><span>configOptions</span> <span>=</span> <span>[</span><span>dateLibrary:</span> <span>"java8"</span><span>,</span><span>interfaceOnly:</span> <span>"true"</span><span>,</span><span>useSpringBoot3:</span> <span>"true"</span><span>,</span><span>useBeanValidation:</span> <span>"true"</span><span>,</span><span>skipDefaultInterface:</span> <span>"true"</span><span>]</span><span>}</span><span>group</span> <span>=</span> <span>'</span><span>com</span><span>.</span><span>example</span><span>'</span><span>version</span> <span>=</span> <span>'</span><span>0.0</span><span>.</span><span>1</span><span>-</span><span>SNAPSHOT</span><span>'</span><span>java</span> <span>{</span><span>toolchain</span> <span>{</span><span>languageVersion</span> <span>=</span> <span>JavaLanguageVersion</span><span>.</span><span>of</span><span>(</span><span>17</span><span>)</span><span>}</span><span>}</span><span>repositories</span> <span>{</span><span>mavenCentral</span><span>()</span><span>}</span><span>dependencies</span> <span>{</span><span>implementation</span> <span>'</span><span>org</span><span>.</span><span>springframework</span><span>.</span><span>boot</span><span>:</span><span>spring</span><span>-</span><span>boot</span><span>-</span><span>starter</span><span>-</span><span>web</span><span>'</span><span>implementation</span> <span>'</span><span>org</span><span>.</span><span>springframework</span><span>.</span><span>boot</span><span>:</span><span>spring</span><span>-</span><span>boot</span><span>-</span><span>starter</span><span>-</span><span>validation</span><span>'</span><span>implementation</span> <span>'</span><span>org</span><span>.</span><span>openapitools</span><span>:</span><span>jackson</span><span>-</span><span>databind</span><span>-</span><span>nullable:</span><span>0.2</span><span>.</span><span>6</span><span>'</span><span>implementation</span> <span>'</span><span>io</span><span>.</span><span>swagger</span><span>.</span><span>core</span><span>.</span><span>v3</span><span>:</span><span>swagger</span><span>-</span><span>annotations:</span><span>2.2</span><span>.</span><span>20</span><span>'</span><span>annotationProcessor</span> <span>'</span><span>org</span><span>.</span><span>projectlombok</span><span>:</span><span>lombok</span><span>'</span><span>testImplementation</span> <span>'</span><span>org</span><span>.</span><span>springframework</span><span>.</span><span>boot</span><span>:</span><span>spring</span><span>-</span><span>boot</span><span>-</span><span>starter</span><span>-</span><span>test</span><span>'</span><span>testRuntimeOnly</span> <span>'</span><span>org</span><span>.</span><span>junit</span><span>.</span><span>platform</span><span>:</span><span>junit</span><span>-</span><span>platform</span><span>-</span><span>launcher</span><span>'</span><span>}</span><span>sourceSets</span> <span>{</span><span>main</span> <span>{</span><span>java</span> <span>{</span><span>srcDirs</span> <span>+=</span> <span>"$buildDir/generated/src/main/java"</span><span>}</span><span>}</span><span>}</span><span>compileJava</span><span>.</span><span>dependsOn</span> <span>tasks</span><span>.</span><span>openApiGenerate</span><span>tasks</span><span>.</span><span>named</span><span>(</span><span>'</span><span>test</span><span>'</span><span>)</span> <span>{</span><span>useJUnitPlatform</span><span>()</span><span>}</span><span>plugins</span> <span>{</span> <span>id</span> <span>'</span><span>java</span><span>'</span> <span>id</span> <span>'</span><span>org</span><span>.</span><span>springframework</span><span>.</span><span>boot</span><span>'</span> <span>version</span> <span>'</span><span>3.4</span><span>.</span><span>3</span><span>'</span> <span>id</span> <span>'</span><span>io</span><span>.</span><span>spring</span><span>.</span><span>dependency</span><span>-</span><span>management</span><span>'</span> <span>version</span> <span>'</span><span>1.1</span><span>.</span><span>7</span><span>'</span> <span>id</span> <span>'</span><span>org</span><span>.</span><span>openapi</span><span>.</span><span>generator</span><span>'</span> <span>version</span> <span>'</span><span>7.12</span><span>.</span><span>0</span><span>'</span> <span>}</span> <span>openApiGenerate</span> <span>{</span> <span>generatorName</span> <span>=</span> <span>"spring"</span> <span>inputSpec</span> <span>=</span> <span>"$rootDir/src/main/resources/static/openapi.yaml"</span> <span>outputDir</span> <span>=</span> <span>"$buildDir/generated"</span> <span>apiPackage</span> <span>=</span> <span>"com.example.api"</span> <span>modelPackage</span> <span>=</span> <span>"com.example.model"</span> <span>configOptions</span> <span>=</span> <span>[</span> <span>dateLibrary:</span> <span>"java8"</span><span>,</span> <span>interfaceOnly:</span> <span>"true"</span><span>,</span> <span>useSpringBoot3:</span> <span>"true"</span><span>,</span> <span>useBeanValidation:</span> <span>"true"</span><span>,</span> <span>skipDefaultInterface:</span> <span>"true"</span> <span>]</span> <span>}</span> <span>group</span> <span>=</span> <span>'</span><span>com</span><span>.</span><span>example</span><span>'</span> <span>version</span> <span>=</span> <span>'</span><span>0.0</span><span>.</span><span>1</span><span>-</span><span>SNAPSHOT</span><span>'</span> <span>java</span> <span>{</span> <span>toolchain</span> <span>{</span> <span>languageVersion</span> <span>=</span> <span>JavaLanguageVersion</span><span>.</span><span>of</span><span>(</span><span>17</span><span>)</span> <span>}</span> <span>}</span> <span>repositories</span> <span>{</span> <span>mavenCentral</span><span>()</span> <span>}</span> <span>dependencies</span> <span>{</span> <span>implementation</span> <span>'</span><span>org</span><span>.</span><span>springframework</span><span>.</span><span>boot</span><span>:</span><span>spring</span><span>-</span><span>boot</span><span>-</span><span>starter</span><span>-</span><span>web</span><span>'</span> <span>implementation</span> <span>'</span><span>org</span><span>.</span><span>springframework</span><span>.</span><span>boot</span><span>:</span><span>spring</span><span>-</span><span>boot</span><span>-</span><span>starter</span><span>-</span><span>validation</span><span>'</span> <span>implementation</span> <span>'</span><span>org</span><span>.</span><span>openapitools</span><span>:</span><span>jackson</span><span>-</span><span>databind</span><span>-</span><span>nullable:</span><span>0.2</span><span>.</span><span>6</span><span>'</span> <span>implementation</span> <span>'</span><span>io</span><span>.</span><span>swagger</span><span>.</span><span>core</span><span>.</span><span>v3</span><span>:</span><span>swagger</span><span>-</span><span>annotations:</span><span>2.2</span><span>.</span><span>20</span><span>'</span> <span>annotationProcessor</span> <span>'</span><span>org</span><span>.</span><span>projectlombok</span><span>:</span><span>lombok</span><span>'</span> <span>testImplementation</span> <span>'</span><span>org</span><span>.</span><span>springframework</span><span>.</span><span>boot</span><span>:</span><span>spring</span><span>-</span><span>boot</span><span>-</span><span>starter</span><span>-</span><span>test</span><span>'</span> <span>testRuntimeOnly</span> <span>'</span><span>org</span><span>.</span><span>junit</span><span>.</span><span>platform</span><span>:</span><span>junit</span><span>-</span><span>platform</span><span>-</span><span>launcher</span><span>'</span> <span>}</span> <span>sourceSets</span> <span>{</span> <span>main</span> <span>{</span> <span>java</span> <span>{</span> <span>srcDirs</span> <span>+=</span> <span>"$buildDir/generated/src/main/java"</span> <span>}</span> <span>}</span> <span>}</span> <span>compileJava</span><span>.</span><span>dependsOn</span> <span>tasks</span><span>.</span><span>openApiGenerate</span> <span>tasks</span><span>.</span><span>named</span><span>(</span><span>'</span><span>test</span><span>'</span><span>)</span> <span>{</span> <span>useJUnitPlatform</span><span>()</span> <span>}</span>plugins { id 'java' id 'org.springframework.boot' version '3.4.3' id 'io.spring.dependency-management' version '1.1.7' id 'org.openapi.generator' version '7.12.0' } openApiGenerate { generatorName = "spring" inputSpec = "$rootDir/src/main/resources/static/openapi.yaml" outputDir = "$buildDir/generated" apiPackage = "com.example.api" modelPackage = "com.example.model" configOptions = [ dateLibrary: "java8", interfaceOnly: "true", useSpringBoot3: "true", useBeanValidation: "true", skipDefaultInterface: "true" ] } group = 'com.example' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.openapitools:jackson-databind-nullable:0.2.6' implementation 'io.swagger.core.v3:swagger-annotations:2.2.20' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } sourceSets { main { java { srcDirs += "$buildDir/generated/src/main/java" } } } compileJava.dependsOn tasks.openApiGenerate tasks.named('test') { useJUnitPlatform() }
Enter fullscreen mode Exit fullscreen mode
Finally, we place the generated OpenAPI specification into the resources/static
folder as openapi.yaml
.
Generate the API and Model objects
After opening the project in IntelliJ, I executed the following command to build the API stubs and model objects.
./gradlew clean build./gradlew clean build./gradlew clean build
Enter fullscreen mode Exit fullscreen mode
Now, we can see the api
and model
objects created from our OpenAPI specification. Here’s the QuotesAPI.java
file:
Add the business logic
With the base service ready and already adhering to our OpenAPI contract, we start adding some business logic to the service.
First, we create a QuotesRepository
class which returns the data for our service. As noted above, this would normally be stored in some dedicated persistence layer. For this example, hard-coding five quotes’ worth of data works just fine, and it keeps us focused.
<span>@Repository</span><span>public</span> <span>class</span> <span>QuotesRepository</span> <span>{</span><span>public</span> <span>static</span> <span>final</span> <span>List</span><span><</span><span>Quote</span><span>></span> <span>QUOTES</span> <span>=</span> <span>List</span><span>.</span><span>of</span><span>(</span><span>new</span> <span>Quote</span><span>()</span><span>.</span><span>id</span><span>(</span><span>1</span><span>)</span><span>.</span><span>quote</span><span>(</span><span>"The greatest glory in living lies not in never falling, but in rising every time we fall."</span><span>),</span><span>new</span> <span>Quote</span><span>()</span><span>.</span><span>id</span><span>(</span><span>2</span><span>)</span><span>.</span><span>quote</span><span>(</span><span>"The way to get started is to quit talking and begin doing."</span><span>),</span><span>new</span> <span>Quote</span><span>()</span><span>.</span><span>id</span><span>(</span><span>3</span><span>)</span><span>.</span><span>quote</span><span>(</span><span>"Your time is limited, so don't waste it living someone else's life."</span><span>),</span><span>new</span> <span>Quote</span><span>()</span><span>.</span><span>id</span><span>(</span><span>4</span><span>)</span><span>.</span><span>quote</span><span>(</span><span>"If life were predictable it would cease to be life, and be without flavor."</span><span>),</span><span>new</span> <span>Quote</span><span>()</span><span>.</span><span>id</span><span>(</span><span>5</span><span>)</span><span>.</span><span>quote</span><span>(</span><span>"If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success."</span><span>)</span><span>);</span><span>public</span> <span>List</span><span><</span><span>Quote</span><span>></span> <span>getAllQuotes</span><span>()</span> <span>{</span><span>return</span> <span>QUOTES</span><span>;</span><span>}</span><span>public</span> <span>Optional</span><span><</span><span>Quote</span><span>></span> <span>getQuoteById</span><span>(</span><span>Integer</span> <span>id</span><span>)</span> <span>{</span><span>return</span> <span>Optional</span><span>.</span><span>ofNullable</span><span>(</span><span>QUOTES</span><span>.</span><span>stream</span><span>().</span><span>filter</span><span>(</span><span>quote</span> <span>-></span> <span>quote</span><span>.</span><span>getId</span><span>().</span><span>equals</span><span>(</span><span>id</span><span>)).</span><span>findFirst</span><span>().</span><span>orElse</span><span>(</span><span>null</span><span>));</span><span>}</span><span>}</span><span>@Repository</span> <span>public</span> <span>class</span> <span>QuotesRepository</span> <span>{</span> <span>public</span> <span>static</span> <span>final</span> <span>List</span><span><</span><span>Quote</span><span>></span> <span>QUOTES</span> <span>=</span> <span>List</span><span>.</span><span>of</span><span>(</span> <span>new</span> <span>Quote</span><span>()</span> <span>.</span><span>id</span><span>(</span><span>1</span><span>)</span> <span>.</span><span>quote</span><span>(</span><span>"The greatest glory in living lies not in never falling, but in rising every time we fall."</span><span>),</span> <span>new</span> <span>Quote</span><span>()</span> <span>.</span><span>id</span><span>(</span><span>2</span><span>)</span> <span>.</span><span>quote</span><span>(</span><span>"The way to get started is to quit talking and begin doing."</span><span>),</span> <span>new</span> <span>Quote</span><span>()</span> <span>.</span><span>id</span><span>(</span><span>3</span><span>)</span> <span>.</span><span>quote</span><span>(</span><span>"Your time is limited, so don't waste it living someone else's life."</span><span>),</span> <span>new</span> <span>Quote</span><span>()</span> <span>.</span><span>id</span><span>(</span><span>4</span><span>)</span> <span>.</span><span>quote</span><span>(</span><span>"If life were predictable it would cease to be life, and be without flavor."</span><span>),</span> <span>new</span> <span>Quote</span><span>()</span> <span>.</span><span>id</span><span>(</span><span>5</span><span>)</span> <span>.</span><span>quote</span><span>(</span><span>"If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success."</span><span>)</span> <span>);</span> <span>public</span> <span>List</span><span><</span><span>Quote</span><span>></span> <span>getAllQuotes</span><span>()</span> <span>{</span> <span>return</span> <span>QUOTES</span><span>;</span> <span>}</span> <span>public</span> <span>Optional</span><span><</span><span>Quote</span><span>></span> <span>getQuoteById</span><span>(</span><span>Integer</span> <span>id</span><span>)</span> <span>{</span> <span>return</span> <span>Optional</span><span>.</span><span>ofNullable</span><span>(</span><span>QUOTES</span><span>.</span><span>stream</span><span>().</span><span>filter</span><span>(</span><span>quote</span> <span>-></span> <span>quote</span><span>.</span><span>getId</span><span>().</span><span>equals</span><span>(</span><span>id</span><span>)).</span><span>findFirst</span><span>().</span><span>orElse</span><span>(</span><span>null</span><span>));</span> <span>}</span> <span>}</span>@Repository public class QuotesRepository { public static final List<Quote> QUOTES = List.of( new Quote() .id(1) .quote("The greatest glory in living lies not in never falling, but in rising every time we fall."), new Quote() .id(2) .quote("The way to get started is to quit talking and begin doing."), new Quote() .id(3) .quote("Your time is limited, so don't waste it living someone else's life."), new Quote() .id(4) .quote("If life were predictable it would cease to be life, and be without flavor."), new Quote() .id(5) .quote("If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success.") ); public List<Quote> getAllQuotes() { return QUOTES; } public Optional<Quote> getQuoteById(Integer id) { return Optional.ofNullable(QUOTES.stream().filter(quote -> quote.getId().equals(id)).findFirst().orElse(null)); } }
Enter fullscreen mode Exit fullscreen mode
Next, we create a QuotesService
which will interact with the QuotesRepository
. Taking this approach will keep the data separate from the business logic.
<span>@RequiredArgsConstructor</span><span>@Service</span><span>public</span> <span>class</span> <span>QuotesService</span> <span>{</span><span>private</span> <span>final</span> <span>QuotesRepository</span> <span>quotesRepository</span><span>;</span><span>public</span> <span>List</span><span><</span><span>Quote</span><span>></span> <span>getAllQuotes</span><span>()</span> <span>{</span><span>return</span> <span>quotesRepository</span><span>.</span><span>getAllQuotes</span><span>();</span><span>}</span><span>public</span> <span>Optional</span><span><</span><span>Quote</span><span>></span> <span>getQuoteById</span><span>(</span><span>Integer</span> <span>id</span><span>)</span> <span>{</span><span>return</span> <span>quotesRepository</span><span>.</span><span>getQuoteById</span><span>(</span><span>id</span><span>);</span><span>}</span><span>public</span> <span>Quote</span> <span>getRandomQuote</span><span>()</span> <span>{</span><span>List</span><span><</span><span>Quote</span><span>></span> <span>quotes</span> <span>=</span> <span>quotesRepository</span><span>.</span><span>getAllQuotes</span><span>();</span><span>return</span> <span>quotes</span><span>.</span><span>get</span><span>(</span><span>ThreadLocalRandom</span><span>.</span><span>current</span><span>().</span><span>nextInt</span><span>(</span><span>quotes</span><span>.</span><span>size</span><span>()));</span><span>}</span><span>}</span><span>@RequiredArgsConstructor</span> <span>@Service</span> <span>public</span> <span>class</span> <span>QuotesService</span> <span>{</span> <span>private</span> <span>final</span> <span>QuotesRepository</span> <span>quotesRepository</span><span>;</span> <span>public</span> <span>List</span><span><</span><span>Quote</span><span>></span> <span>getAllQuotes</span><span>()</span> <span>{</span> <span>return</span> <span>quotesRepository</span><span>.</span><span>getAllQuotes</span><span>();</span> <span>}</span> <span>public</span> <span>Optional</span><span><</span><span>Quote</span><span>></span> <span>getQuoteById</span><span>(</span><span>Integer</span> <span>id</span><span>)</span> <span>{</span> <span>return</span> <span>quotesRepository</span><span>.</span><span>getQuoteById</span><span>(</span><span>id</span><span>);</span> <span>}</span> <span>public</span> <span>Quote</span> <span>getRandomQuote</span><span>()</span> <span>{</span> <span>List</span><span><</span><span>Quote</span><span>></span> <span>quotes</span> <span>=</span> <span>quotesRepository</span><span>.</span><span>getAllQuotes</span><span>();</span> <span>return</span> <span>quotes</span><span>.</span><span>get</span><span>(</span><span>ThreadLocalRandom</span><span>.</span><span>current</span><span>().</span><span>nextInt</span><span>(</span><span>quotes</span><span>.</span><span>size</span><span>()));</span> <span>}</span> <span>}</span>@RequiredArgsConstructor @Service public class QuotesService { private final QuotesRepository quotesRepository; public List<Quote> getAllQuotes() { return quotesRepository.getAllQuotes(); } public Optional<Quote> getQuoteById(Integer id) { return quotesRepository.getQuoteById(id); } public Quote getRandomQuote() { List<Quote> quotes = quotesRepository.getAllQuotes(); return quotes.get(ThreadLocalRandom.current().nextInt(quotes.size())); } }
Enter fullscreen mode Exit fullscreen mode
Finally, we just need to implement the QuotesApi
generated from our API-First approach:
<span>@Controller</span><span>@RequiredArgsConstructor</span><span>public</span> <span>class</span> <span>QuotesController</span> <span>implements</span> <span>QuotesApi</span> <span>{</span><span>private</span> <span>final</span> <span>QuotesService</span> <span>quotesService</span><span>;</span><span>@Override</span><span>public</span> <span>ResponseEntity</span><span><</span><span>List</span><span><</span><span>Quote</span><span>>></span> <span>getAllQuotes</span><span>()</span> <span>{</span><span>return</span> <span>new</span> <span>ResponseEntity</span><span><>(</span><span>quotesService</span><span>.</span><span>getAllQuotes</span><span>(),</span> <span>HttpStatus</span><span>.</span><span>OK</span><span>);</span><span>}</span><span>@Override</span><span>public</span> <span>ResponseEntity</span><span><</span><span>Quote</span><span>></span> <span>getQuoteById</span><span>(</span><span>Integer</span> <span>id</span><span>)</span> <span>{</span><span>return</span> <span>quotesService</span><span>.</span><span>getQuoteById</span><span>(</span><span>id</span><span>)</span><span>.</span><span>map</span><span>(</span><span>quote</span> <span>-></span> <span>new</span> <span>ResponseEntity</span><span><>(</span><span>quote</span><span>,</span> <span>HttpStatus</span><span>.</span><span>OK</span><span>))</span><span>.</span><span>orElseGet</span><span>(()</span> <span>-></span> <span>new</span> <span>ResponseEntity</span><span><>(</span><span>HttpStatus</span><span>.</span><span>NOT_FOUND</span><span>));</span><span>}</span><span>@Override</span><span>public</span> <span>ResponseEntity</span><span><</span><span>Quote</span><span>></span> <span>getRandomQuote</span><span>()</span> <span>{</span><span>return</span> <span>new</span> <span>ResponseEntity</span><span><>(</span><span>quotesService</span><span>.</span><span>getRandomQuote</span><span>(),</span> <span>HttpStatus</span><span>.</span><span>OK</span><span>);</span><span>}</span><span>}</span><span>@Controller</span> <span>@RequiredArgsConstructor</span> <span>public</span> <span>class</span> <span>QuotesController</span> <span>implements</span> <span>QuotesApi</span> <span>{</span> <span>private</span> <span>final</span> <span>QuotesService</span> <span>quotesService</span><span>;</span> <span>@Override</span> <span>public</span> <span>ResponseEntity</span><span><</span><span>List</span><span><</span><span>Quote</span><span>>></span> <span>getAllQuotes</span><span>()</span> <span>{</span> <span>return</span> <span>new</span> <span>ResponseEntity</span><span><>(</span><span>quotesService</span><span>.</span><span>getAllQuotes</span><span>(),</span> <span>HttpStatus</span><span>.</span><span>OK</span><span>);</span> <span>}</span> <span>@Override</span> <span>public</span> <span>ResponseEntity</span><span><</span><span>Quote</span><span>></span> <span>getQuoteById</span><span>(</span><span>Integer</span> <span>id</span><span>)</span> <span>{</span> <span>return</span> <span>quotesService</span><span>.</span><span>getQuoteById</span><span>(</span><span>id</span><span>)</span> <span>.</span><span>map</span><span>(</span><span>quote</span> <span>-></span> <span>new</span> <span>ResponseEntity</span><span><>(</span><span>quote</span><span>,</span> <span>HttpStatus</span><span>.</span><span>OK</span><span>))</span> <span>.</span><span>orElseGet</span><span>(()</span> <span>-></span> <span>new</span> <span>ResponseEntity</span><span><>(</span><span>HttpStatus</span><span>.</span><span>NOT_FOUND</span><span>));</span> <span>}</span> <span>@Override</span> <span>public</span> <span>ResponseEntity</span><span><</span><span>Quote</span><span>></span> <span>getRandomQuote</span><span>()</span> <span>{</span> <span>return</span> <span>new</span> <span>ResponseEntity</span><span><>(</span><span>quotesService</span><span>.</span><span>getRandomQuote</span><span>(),</span> <span>HttpStatus</span><span>.</span><span>OK</span><span>);</span> <span>}</span> <span>}</span>@Controller @RequiredArgsConstructor public class QuotesController implements QuotesApi { private final QuotesService quotesService; @Override public ResponseEntity<List<Quote>> getAllQuotes() { return new ResponseEntity<>(quotesService.getAllQuotes(), HttpStatus.OK); } @Override public ResponseEntity<Quote> getQuoteById(Integer id) { return quotesService.getQuoteById(id) .map(quote -> new ResponseEntity<>(quote, HttpStatus.OK)) .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND)); } @Override public ResponseEntity<Quote> getRandomQuote() { return new ResponseEntity<>(quotesService.getRandomQuote(), HttpStatus.OK); } }
Enter fullscreen mode Exit fullscreen mode
At this point, we have a fully-functional Motivational Quotes API, complete with a small collection of responses.
Some final items
Spring Boot gives us the option for a web-based Swagger Docs user interface via the springdoc-openapi-starter-webmvc-ui
dependency.
<span>dependencies</span> <span>{</span><span>...</span><span>implementation</span> <span>'</span><span>org</span><span>.</span><span>springdoc</span><span>:</span><span>springdoc</span><span>-</span><span>openapi</span><span>-</span><span>starter</span><span>-</span><span>webmvc</span><span>-</span><span>ui:</span><span>2.8</span><span>.</span><span>5</span><span>'</span><span>...</span><span>}</span><span>dependencies</span> <span>{</span> <span>...</span> <span>implementation</span> <span>'</span><span>org</span><span>.</span><span>springdoc</span><span>:</span><span>springdoc</span><span>-</span><span>openapi</span><span>-</span><span>starter</span><span>-</span><span>webmvc</span><span>-</span><span>ui:</span><span>2.8</span><span>.</span><span>5</span><span>'</span> <span>...</span> <span>}</span>dependencies { ... implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.5' ... }
Enter fullscreen mode Exit fullscreen mode
While the framework allows engineers to use simple annotations to describe their API, we can use our existing openapi.yaml
file in the resources/static
folder.
We can implement this approach in the application-properties.yaml
file, along with a few other minor configuration updates:
<span>server</span><span>:</span><span>port</span><span>:</span> <span>${PORT:8080}</span><span>spring</span><span>:</span><span>application</span><span>:</span><span>name</span><span>:</span> <span>quotes</span><span>springdoc</span><span>:</span><span>swagger-ui</span><span>:</span><span>path</span><span>:</span> <span>/swagger-docs</span><span>url</span><span>:</span> <span>openapi.yaml</span><span>server</span><span>:</span> <span>port</span><span>:</span> <span>${PORT:8080}</span> <span>spring</span><span>:</span> <span>application</span><span>:</span> <span>name</span><span>:</span> <span>quotes</span> <span>springdoc</span><span>:</span> <span>swagger-ui</span><span>:</span> <span>path</span><span>:</span> <span>/swagger-docs</span> <span>url</span><span>:</span> <span>openapi.yaml</span>server: port: ${PORT:8080} spring: application: name: quotes springdoc: swagger-ui: path: /swagger-docs url: openapi.yaml
Enter fullscreen mode Exit fullscreen mode
Just for fun, let’s add a banner.txt
file for use when the service starts. We place this file into the resources
folder.
<span>${</span><span>AnsiColor</span><span>.BLUE</span><span>}</span>___ _ _ _ ___ | |_ ___ ___/ _<span>`</span> | | | |/ _ <span>\|</span> __/ _ <span>\/</span> __|| <span>(</span>_| | |_| | <span>(</span>_<span>)</span> | <span>||</span> __/<span>\_</span>_ <span>\</span><span>\_</span>_, |<span>\_</span>_,_|<span>\_</span>__/ <span>\_</span>_<span>\_</span>__||___/|_|<span>${</span><span>AnsiColor</span><span>.DEFAULT</span><span>}</span>:: Running Spring Boot <span>${</span><span>AnsiColor</span><span>.BLUE</span><span>}${</span><span>spring</span><span>-boot.version</span><span>}${</span><span>AnsiColor</span><span>.DEFAULT</span><span>}</span> :: Port <span>#${AnsiColor.BLUE}${server.port}${AnsiColor.DEFAULT} ::</span><span>${</span><span>AnsiColor</span><span>.BLUE</span><span>}</span> _ __ _ _ _ ___ | |_ ___ ___ / _<span>`</span> | | | |/ _ <span>\|</span> __/ _ <span>\/</span> __| | <span>(</span>_| | |_| | <span>(</span>_<span>)</span> | <span>||</span> __/<span>\_</span>_ <span>\</span> <span>\_</span>_, |<span>\_</span>_,_|<span>\_</span>__/ <span>\_</span>_<span>\_</span>__||___/ |_| <span>${</span><span>AnsiColor</span><span>.DEFAULT</span><span>}</span> :: Running Spring Boot <span>${</span><span>AnsiColor</span><span>.BLUE</span><span>}${</span><span>spring</span><span>-boot.version</span><span>}${</span><span>AnsiColor</span><span>.DEFAULT</span><span>}</span> :: Port <span>#${AnsiColor.BLUE}${server.port}${AnsiColor.DEFAULT} ::</span>${AnsiColor.BLUE} _ __ _ _ _ ___ | |_ ___ ___ / _` | | | |/ _ \| __/ _ \/ __| | (_| | |_| | (_) | || __/\__ \ \__, |\__,_|\___/ \__\___||___/ |_| ${AnsiColor.DEFAULT} :: Running Spring Boot ${AnsiColor.BLUE}${spring-boot.version}${AnsiColor.DEFAULT} :: Port #${AnsiColor.BLUE}${server.port}${AnsiColor.DEFAULT} ::
Enter fullscreen mode Exit fullscreen mode
Now, when we start the service locally, we can see the banner:
Once started, we can validate the Swagger Docs are working by visiting the /swagger-docs
endpoint.
Finally, we’ll create a new Git-based repository so that we can track any future changes:
<span>$ </span>git init<span>$ </span>git add <span>.</span><span>$ </span>git commit <span>-m</span> <span>"Initial commit for the Motivational Quotes API"</span><span>$ </span>git init <span>$ </span>git add <span>.</span> <span>$ </span>git commit <span>-m</span> <span>"Initial commit for the Motivational Quotes API"</span>$ git init $ git add . $ git commit -m "Initial commit for the Motivational Quotes API"
Enter fullscreen mode Exit fullscreen mode
Now, let’s see how quickly we can deploy our service.
Using Heroku to Finish the Journey
So far, the primary focus for introducing my new idea has been creating an OpenAPI specification and writing some business logic for my service. Spring Boot handled everything else for me.
When it comes to running my service, I prefer to use Heroku because it’s a great fit for Spring Boot services. I can deploy my services quickly without getting bogged down with cloud infrastructure concerns. Heroku also makes it easy to pass in configuration values for my Java-based applications.
To match the Java version we’re using, we create a system.properties
file in the root folder of the project. The file has one line:
java.runtime.version <span>=</span> 17java.runtime.version <span>=</span> 17java.runtime.version = 17
Enter fullscreen mode Exit fullscreen mode
Then, I create a Procfile
in the same location for customizing the deployment behavior. This file also has one line:
web: java <span>-jar</span> build/libs/quotes-0.0.1-SNAPSHOT.jarweb: java <span>-jar</span> build/libs/quotes-0.0.1-SNAPSHOT.jarweb: java -jar build/libs/quotes-0.0.1-SNAPSHOT.jar
Enter fullscreen mode Exit fullscreen mode
It’s time to deploy. With the Heroku CLI, I can deploy the service using a few simple commands. First, I authenticate the CLI and then create a new Heroku app.
<span>$ </span>heroku login<span>$ </span>heroku createCreating app... <span>done</span>, vast-crag-43256https://vast-crag-43256-bb5e35ea87de.herokuapp.com/ | https://git.heroku.com/vast-crag-43256.git<span>$ </span>heroku login <span>$ </span>heroku create Creating app... <span>done</span>, vast-crag-43256 https://vast-crag-43256-bb5e35ea87de.herokuapp.com/ | https://git.heroku.com/vast-crag-43256.git$ heroku login $ heroku create Creating app... done, vast-crag-43256 https://vast-crag-43256-bb5e35ea87de.herokuapp.com/ | https://git.heroku.com/vast-crag-43256.git
Enter fullscreen mode Exit fullscreen mode
My Heroku app instance is named vast-crag-43256
(I could have passed in a specified name), and the service will run at https://vast-crag-43256-bb5e35ea87de.herokuapp.com/.
The last thing to do is deploy the service by using a Git command to push the code to Heroku:
<span>$ </span>git push heroku master<span>$ </span>git push heroku master$ git push heroku master
Enter fullscreen mode Exit fullscreen mode
Once this command is complete, we can validate a successful deployment via the Heroku dashboard:
Now, we’re ready to take our new service for a test drive!
Motivational Quotes in Action
With the Motivational Quotes service running on Heroku, we can validate everything is working as expected using a series of curl
commands.
First, let’s get a complete list of all five motivational quotes:
<span>$ </span>curl <span>\</span><span>--location</span> <span>'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes'</span><span>$ </span>curl <span>\</span> <span>--location</span> <span>'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes'</span>$ curl \ --location 'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes'
Enter fullscreen mode Exit fullscreen mode
<span>[</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span>1</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"The greatest glory in living lies not in never falling, but in rising every time we fall."</span><span> </span><span>},</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span>2</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"The way to get started is to quit talking and begin doing."</span><span> </span><span>},</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span>3</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"Your time is limited, so don't waste it living someone else's life."</span><span> </span><span>},</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span>4</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"If life were predictable it would cease to be life, and be without flavor."</span><span> </span><span>},</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span>5</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success."</span><span> </span><span>}</span><span> </span><span>]</span><span> </span><span>[</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span>1</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"The greatest glory in living lies not in never falling, but in rising every time we fall."</span><span> </span><span>},</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span>2</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"The way to get started is to quit talking and begin doing."</span><span> </span><span>},</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span>3</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"Your time is limited, so don't waste it living someone else's life."</span><span> </span><span>},</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span>4</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"If life were predictable it would cease to be life, and be without flavor."</span><span> </span><span>},</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span>5</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success."</span><span> </span><span>}</span><span> </span><span>]</span><span> </span>[ { "id":1, "quote":"The greatest glory in living lies not in never falling, but in rising every time we fall." }, { "id":2, "quote":"The way to get started is to quit talking and begin doing." }, { "id":3, "quote":"Your time is limited, so don't waste it living someone else's life." }, { "id":4, "quote":"If life were predictable it would cease to be life, and be without flavor." }, { "id":5, "quote":"If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success." } ]
Enter fullscreen mode Exit fullscreen mode
Let’s retrieve a single motivational quote by ID:
<span>$ </span>curl <span>\</span><span>--location</span> <span>'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes/3'</span><span>$ </span>curl <span>\</span> <span>--location</span> <span>'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes/3'</span>$ curl \ --location 'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes/3'
Enter fullscreen mode Exit fullscreen mode
<span>{</span><span> </span><span>"id"</span><span>:</span><span>3</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"Your time is limited, so don't waste it living someone else's life."</span><span> </span><span>}</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span>3</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"Your time is limited, so don't waste it living someone else's life."</span><span> </span><span>}</span><span> </span>{ "id":3, "quote":"Your time is limited, so don't waste it living someone else's life." }
Enter fullscreen mode Exit fullscreen mode
Let’s get a random motivational quote:
<span>$ </span>curl <span>--location</span> <span>\</span><span>'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes/random'</span><span>$ </span>curl <span>--location</span> <span>\</span> <span>'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes/random'</span>$ curl --location \ 'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes/random'
Enter fullscreen mode Exit fullscreen mode
<span>{</span><span> </span><span>"id"</span><span>:</span><span>5</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success."</span><span> </span><span>}</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span>5</span><span>,</span><span> </span><span>"quote"</span><span>:</span><span>"If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success."</span><span> </span><span>}</span><span> </span>{ "id":5, "quote":"If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success." }
Enter fullscreen mode Exit fullscreen mode
We can even browse the Swagger Docs too.
Conclusion
Time to market can make or break any idea. This is why startups are laser-focused on delivering their innovations as quickly as possible. The longer it takes to reach the finish line, the greater the risk of a competitor arriving before you.
My readers may recall my personal mission statement, which I feel can apply to any IT professional:
“Focus your time on delivering features/functionality that extends the value of your intellectual property. Leverage frameworks, products, and services for everything else.” — J. Vester
In this article, we saw how Spring Boot handled everything required to implement a RESTful API. Leveraging ChatGPT, we were even able to express in human words what we wanted our service to be, and it created an OpenAPI specification for us in a matter of seconds. This allowed us to leverage an API-First approach. Once ready, we were able to deliver our idea using Heroku by issuing a few CLI commands.
Spring Boot, ChatGPT, and Heroku provided the frameworks and services so that I could remain laser-focused on realizing my idea. As a result, I was able to adhere to my personal mission statement and, more importantly, deliver my idea quickly. All I had to do was focus on the business logic behind my idea—and that’s the way it should be!
If you’re interested, the source code for this article can be found on GitLab.
Have a really great day!
原文链接:How To Introduce a New API Quickly Using Spring Boot and Gradle
暂无评论内容