<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cosmas Gikunju</title>
    <description>The latest articles on DEV Community by Cosmas Gikunju (@itscosmas).</description>
    <link>https://dev.to/itscosmas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F131759%2F00f68a45-87f6-4b02-a098-a1e577cfb91c.png</url>
      <title>DEV Community: Cosmas Gikunju</title>
      <link>https://dev.to/itscosmas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/itscosmas"/>
    <language>en</language>
    <item>
      <title>Consuming and Testing third party API's using Spring Webclient</title>
      <dc:creator>Cosmas Gikunju</dc:creator>
      <pubDate>Mon, 19 Feb 2024 16:33:06 +0000</pubDate>
      <link>https://dev.to/itscosmas/consuming-and-testing-third-party-apis-using-spring-webclient-26lj</link>
      <guid>https://dev.to/itscosmas/consuming-and-testing-third-party-apis-using-spring-webclient-26lj</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkabl7p88aukr3i7kz9eq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkabl7p88aukr3i7kz9eq.jpg" alt="API's everywhere Buzz Lightyear and Woody Toy Story meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Introduction
&lt;/h4&gt;

&lt;p&gt;More often than not developer's build and call API's. Therefore it's guaranteed that you are going to have to build or consume an API sometime in your day to day development. In this article I will show you how you can build a springboot service that calls a third-party api and most importantly we are going to &lt;strong&gt;write test&lt;/strong&gt; to guarantee our consuming service works as expected. &lt;/p&gt;

&lt;h4&gt;
  
  
  Getting Started
&lt;/h4&gt;

&lt;p&gt;Start a new Spring Boot Project on &lt;a href="https://start.spring.io/" rel="noopener noreferrer"&gt;Spring Initializr&lt;/a&gt; here or just create from the command line using a http request to Spring Initializr  as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

curl --location 'https://start.spring.io/starter.zip?type=maven-project&amp;amp;language=java&amp;amp;bootVersion=3.2.2&amp;amp;baseDir=ms-xcoffee&amp;amp;groupId=com.xcoffee&amp;amp;artifactId=ms-xcoffee&amp;amp;name=ms-xcoffee&amp;amp;description=Demo%20project%20for%20Spring%20Boot&amp;amp;packageName=com.xcoffee.ms-xcoffee&amp;amp;packaging=jar&amp;amp;javaVersion=21&amp;amp;dependencies=webflux%2Clombok%2Cvalidation' | tar -xzvf -


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add Spring Webflux, Validation and Lombok as the starting dependencies. (Above is your first first API interaction). &lt;/p&gt;

&lt;h4&gt;
  
  
  Building
&lt;/h4&gt;

&lt;p&gt;We are going to use &lt;a href="https://sampleapis.com/api-list/coffee" rel="noopener noreferrer"&gt;https://sampleapis.com/api-list/coffee&lt;/a&gt; as our third party API. They have provided us a very nice API endpoint that accepts a GET request and responds with a list of JSON representation of the coffee data. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

GET /coffee/iced HTTP/1.1
Host: api.sampleapis.com


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Curl Request:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

curl --location 'https://api.sampleapis.com/coffee/iced'


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Response:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[
    {
        "title": "Iced Coffee",
        "description": "A coffee with ice, typically served with a dash of milk, cream or sweetener—iced coffee is really as simple as that.",
        "ingredients": [
            "Coffee",
            "Ice",
            "Sugar*",
            "Cream*"
        ],
        "image": "https://upload.wikimedia.org/wikipedia/commons/d/d8/Blue_Bottle%2C_Kyoto_Style_Ice_Coffee_%285909775445%29.jpg",
        "id": 1
    }, ...
]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next we will build a POJO(plain old java object) representation of the json response.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

package com.xcoffee.msxcoffee.schema.pojos;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;


@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CoffeeResponse {
    @JsonProperty("title")
    private String title;
    @JsonProperty("description")
    private String description;
    @JsonProperty("ingredients")
    private List&amp;lt;String&amp;gt; ingredients;
    @JsonProperty("image")
    private String image;
    @JsonProperty("id")
    private int id;
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The above class maps the json data to a java object we can work with. We use &lt;a href="https://projectlombok.org/" rel="noopener noreferrer"&gt;Lombok&lt;/a&gt; to generate constructors, getters and setters for our code and the &lt;a href="https://github.com/FasterXML/jackson" rel="noopener noreferrer"&gt;Jackson Project&lt;/a&gt; to handle serialization and deserialization of json to pojo . We know the response is an array of objects representing the coffee and so above data structure is fit for this.&lt;/p&gt;

&lt;h4&gt;
  
  
  Webclient Action
&lt;/h4&gt;

&lt;p&gt;We will build an interface in which we will base our api consumer implementation. The interface supports &lt;a href="https://www.w3schools.com/java/java_polymorphism.asp" rel="noopener noreferrer"&gt;Polymorphism&lt;/a&gt; and allows us to use different implementations for different purposes e.g. testing. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

package com.xcoffee.msxcoffee.services;

import com.xcoffee.msxcoffee.schema.pojos.CoffeeResponse;
import reactor.core.publisher.Mono;

import java.util.List;

public interface CoffeeService {
    Mono&amp;lt;List&amp;lt;CoffeeResponse&amp;gt;&amp;gt; getHotCoffees();
    Mono&amp;lt;List&amp;lt;CoffeeResponse&amp;gt;&amp;gt; getIcedCoffees();
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next we dive into the actual implementation.&lt;br&gt;
We will create a class called &lt;code&gt;CoffeeServiceImpl.java&lt;/code&gt; that implements our interface methods.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

package com.xcoffee.msxcoffee.services.impl;

import com.xcoffee.msxcoffee.schema.pojos.CoffeeResponse;
import com.xcoffee.msxcoffee.services.CoffeeService;
import reactor.core.publisher.Mono;

public class CoffeeServiceImpl implements CoffeeService {
    @Override
    public Mono&amp;lt;List&amp;lt;CoffeeResponse&amp;gt;&amp;gt; getHotCoffees() {
        return null;
    }

    @Override
    public Mono&amp;lt;List&amp;lt;CoffeeResponse&amp;gt;&amp;gt; getIcedCoffees() {
        return null;
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we build our api consumption logic.&lt;/p&gt;

&lt;p&gt;But wait, we need to configure WebClient , here's the implementation.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

package com.xcoffee.msxcoffee.config;

import io.netty.channel.ChannelOption;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;

import javax.net.ssl.SSLException;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient getWebClient() throws SSLException {

        ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
                .codecs(configurer -&amp;gt; configurer.defaultCodecs()
                        .maxInMemorySize(1048576)) // Set buffer size to 1 MB
                .build();

        // Disable ssl verification
        SslContext context = SslContextBuilder.forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE)
                .build();

        HttpClient httpClient = HttpClient.create()
                .secure(t -&amp;gt; t.sslContext(context))
                // .proxyWithSystemProperties() // Use JVM level System proxy
                .responseTimeout(Duration.ofSeconds(30))
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30 * 1000)
                .doOnConnected(conn -&amp;gt; conn
                        .addHandlerLast(new ReadTimeoutHandler(30,
                                TimeUnit.SECONDS))
                        .addHandlerLast(new WriteTimeoutHandler(30)));

        ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);

        return WebClient
                .builder()
                .exchangeStrategies(exchangeStrategies)
                .clientConnector(connector)
                .build();
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we can use WebClient in our Service &lt;code&gt;Impl&lt;/code&gt; class:&lt;/p&gt;

&lt;p&gt;Here's the code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

package com.xcoffee.msxcoffee.services.impl;

import com.xcoffee.msxcoffee.config.AppConfig;
import com.xcoffee.msxcoffee.schema.pojos.CoffeeResponse;
import com.xcoffee.msxcoffee.services.CoffeeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.List;

@Service
@RequiredArgsConstructor
@Slf4j
public class CoffeeServiceImpl implements CoffeeService {

    private final WebClient webClient;
    private final AppConfig cfg;

    @Override
    public Mono&amp;lt;List&amp;lt;CoffeeResponse&amp;gt;&amp;gt; getIcedCoffees() {
        return webClient.method(HttpMethod.GET)
                .uri(cfg.getCoffeeURL() + "/iced")
                .contentType(MediaType.APPLICATION_JSON)
                .retrieve()
                .onStatus(HttpStatusCode::isError, response -&amp;gt; {
                    log.error("An Error Occurred Getting Iced Coffees");
                    return Mono.just(new Exception("An Error Occurred Getting Iced Coffees"));
                })
                .bodyToFlux(CoffeeResponse.class)
                .collectList();
    }

    @Override
    public Mono&amp;lt;List&amp;lt;CoffeeResponse&amp;gt;&amp;gt; getHotCoffees() {
        return webClient.method(HttpMethod.GET)
                .uri(cfg.getCoffeeURL() + "/hot")
                .contentType(MediaType.APPLICATION_JSON)
                .retrieve()
                .onStatus(HttpStatusCode::isError, response -&amp;gt; {
                    log.error("An Error Occurred Getting Hot Coffees");
                    return Mono.just(new Exception("An Error Occurred Getting Hot Coffees"));
                })
                .bodyToFlux(CoffeeResponse.class)
                .collectList();
    }
}



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The code above uses &lt;code&gt;org.springframework.web.reactive.function.client.WebClient&lt;/code&gt; to perform GET request to the respective coffee endpoints and then map the responses to the &lt;code&gt;List&amp;lt;CoffeeResponse&lt;/code&gt; object which is a List of Coffees.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@RequiredArgsConstructor&lt;/code&gt; creates a webclient and config constructor for the service class.&lt;/p&gt;

&lt;p&gt;We also created a config class to externalize configurations such as API URLS :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

package com.xcoffee.msxcoffee.config;


import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppConfig {
    private String coffeeURL;
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Unit Testing and Mocking
&lt;/h4&gt;

&lt;p&gt;Suppose the HTTP calls to the coffee endpoints perform a data update or a paid transaction, then we cannot perform test by calling the real endpoint. So here comes &lt;strong&gt;Mocking&lt;/strong&gt; .&lt;/p&gt;

&lt;p&gt;We will use &lt;a href="https://square.github.io/okhttp/" rel="noopener noreferrer"&gt;Square’s Mock Webserver&lt;/a&gt; to spin up a mock server which we can use to simulate real api's request to the get coffee endpoint.&lt;/p&gt;

&lt;p&gt;Add the dependency as follows:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;com.squareup.okhttp3&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;mockwebserver&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;5.0.0-alpha.12&amp;lt;/version&amp;gt;
        &amp;lt;/dependency&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;com.squareup.okhttp3&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;okhttp&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;5.0.0-alpha.12&amp;lt;/version&amp;gt;
        &amp;lt;/dependency&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Checkout the latest version here on maven central &lt;a href="https://central.sonatype.com/artifact/com.squareup.okhttp3/mockwebserver/5.0.0-alpha.12" rel="noopener noreferrer"&gt;https://central.sonatype.com/artifact/com.squareup.okhttp3/mockwebserver/5.0.0-alpha.12&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then we can write our test as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

package com.xcoffee.msxcoffee.services.impl;

import com.xcoffee.msxcoffee.config.AppConfig;
import com.xcoffee.msxcoffee.schema.pojos.CoffeeResponse;
import com.xcoffee.msxcoffee.services.CoffeeService;
import jakarta.validation.constraints.NotNull;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.io.IOException;
import java.util.List;
import java.util.Objects;

class CoffeeServiceImplTest {


    private static MockWebServer mockWebServer;
    private static CoffeeService coffeeService;

    private static final String COFFEE_API_URL = "dummy";

    @BeforeAll
    static void setUp() {
        AppConfig cfg = new AppConfig();
        mockWebServer = new MockWebServer();
        WebClient mockedWebClient = WebClient.builder()
                .baseUrl(mockWebServer.url(COFFEE_API_URL).toString())
                .build();

        coffeeService = new CoffeeServiceImpl(mockedWebClient,cfg);

    }

    @AfterAll
    static void tearDown() throws IOException {
        mockWebServer.close();
    }

    @Test
    @DisplayName("Unit | Get Hot Coffees Success")
    void getHotCoffeeSuccess() {
        // Enqueue a successful response
        mockWebServer.enqueue(
                new MockResponse().setResponseCode(HttpStatus.OK.value())
                        .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                        .setBody(getCoffeeSuccess()));

        // Test the success case
        Mono&amp;lt;List&amp;lt;CoffeeResponse&amp;gt;&amp;gt; responseMono = coffeeService.getHotCoffees();

        StepVerifier.create(responseMono)
                .assertNext(response -&amp;gt; {
                    Assertions.assertInstanceOf(List.class, response);
                })
                .verifyComplete();

    }

    @Test
    @DisplayName("Unit | Get Hot Coffees Failure")
    void getHotCoffeeFailure() {
        // Enqueue a failed response
        mockWebServer.enqueue(new MockResponse()
                .setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.value())
                .setBody("{}")
        );

        // Test the failure case
        Mono&amp;lt;List&amp;lt;CoffeeResponse&amp;gt;&amp;gt; hotCoffees = coffeeService.getHotCoffees();

        StepVerifier.create(hotCoffees)
                .expectErrorMatches(throwable -&amp;gt; throwable instanceof Exception
                        &amp;amp;&amp;amp; Objects.equals(throwable.getMessage(),
                        "An Error Occurred Getting Hot Coffees"))
                .verify();

    }

    @NotNull
    private String getCoffeeSuccess() {
        return """
                [
                     {
                         "title": "Iced Coffee",
                         "description": "A coffee with ice, typically served with a dash of milk, cream or sweetener—iced coffee is really as simple as that.",
                         "ingredients": [
                             "Coffee",
                             "Ice",
                             "Sugar*",
                             "Cream*"
                         ],
                         "image": "https://upload.wikimedia.org/wikipedia/commons/d/d8/Blue_Bottle%2C_Kyoto_Style_Ice_Coffee_%285909775445%29.jpg",
                         "id": 1
                     }
                 ]
                """;
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We initialize the components to be mocked via &lt;code&gt;setUp()&lt;/code&gt; method annotated with &lt;code&gt;@BeforeAll&lt;/code&gt;, here we also configure the mock server and mock webclient and inject to our &lt;code&gt;coffeeService&lt;/code&gt; , remember the interface we created? That helps us easily &lt;em&gt;modify&lt;/em&gt; the implementation of coffee service and this is what achieves mocking. &lt;/p&gt;

&lt;p&gt;We Enqueue success and failure responses respectively inside the test methods and finally use project reactor &lt;a href="https://projectreactor.io/docs/test/release/api/reactor/test/StepVerifier.html" rel="noopener noreferrer"&gt;Step Verifier&lt;/a&gt; to test the events that happen upon subscription. &lt;/p&gt;

&lt;p&gt;Finally run the test with IntelliJ, VS Code, Maven or tool of your choice they run as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;IntelliJ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fza73qalmhmq5e44rplqd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fza73qalmhmq5e44rplqd.png" alt="IntelliJ Screenshots of tests passing"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maven&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mvn test&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ka80nawmkfl0lvn91kh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ka80nawmkfl0lvn91kh.png" alt="Maven Command Line Screenshot of passing tests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;In this article we learnt how we can consume API's using non blocking WebClient and mock API calls using MockServer.&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>java</category>
      <category>unittest</category>
      <category>api</category>
    </item>
    <item>
      <title>How to add SonarQube Code Coverage to Spring Boot</title>
      <dc:creator>Cosmas Gikunju</dc:creator>
      <pubDate>Wed, 07 Feb 2024 12:34:21 +0000</pubDate>
      <link>https://dev.to/itscosmas/how-to-add-sonarqube-code-coverage-to-spring-boot-1lgk</link>
      <guid>https://dev.to/itscosmas/how-to-add-sonarqube-code-coverage-to-spring-boot-1lgk</guid>
      <description>&lt;h3&gt;
  
  
  1. Overview
&lt;/h3&gt;

&lt;p&gt;SonarQube is a self-managed static code analysis tool for continuous codebase inspection provided by SonarSource.&lt;/p&gt;

&lt;p&gt;It's a popular choice used by organizations to :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finding and fix bugs and security vulnerabilities in code.&lt;/li&gt;
&lt;li&gt;Analyze code with Static Application Security Testing (SAST).&lt;/li&gt;
&lt;li&gt;Detect a broad range of security issues such as SQL injection vulnerabilities, cross-site scripting (XSS) code injection attacks, buffer overflows, authentication issues, cloud secrets detection and much more.&lt;/li&gt;
&lt;li&gt;Perform branch analysis to spot and eliminate bugs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can read more at &lt;a href="https://www.sonarsource.com/lp/products/sonarqube/static-code-analysis/" rel="noopener noreferrer"&gt;https://www.sonarsource.com/lp/products/sonarqube/static-code-analysis/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article we will look at how to add Coverage to your Spring Boot and Java application.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Integrating Sonarqube to your spring boot project
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add JaCoCo plugin to your dependencies on the &lt;code&gt;pom.xml&lt;/code&gt; file as follows:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.jacoco&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jacoco-maven-plugin&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;0.8.11&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Work with the version of choice , you can search at Maven Central &lt;a href="https://central.sonatype.com/artifact/org.jacoco/jacoco-maven-plugin" rel="noopener noreferrer"&gt;https://central.sonatype.com/artifact/org.jacoco/jacoco-maven-plugin&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then add the following under build plugins:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&amp;lt;build&amp;gt;
   &amp;lt;plugins&amp;gt;
      &amp;lt;plugin&amp;gt;
         &amp;lt;groupId&amp;gt;org.jacoco&amp;lt;/groupId&amp;gt;
         &amp;lt;artifactId&amp;gt;jacoco-maven-plugin&amp;lt;/artifactId&amp;gt;
         &amp;lt;version&amp;gt;0.8.11&amp;lt;/version&amp;gt;
         &amp;lt;executions&amp;gt;
            &amp;lt;execution&amp;gt;
               &amp;lt;id&amp;gt;prepare-agent&amp;lt;/id&amp;gt;
               &amp;lt;goals&amp;gt;
                  &amp;lt;goal&amp;gt;prepare-agent&amp;lt;/goal&amp;gt;
               &amp;lt;/goals&amp;gt;
            &amp;lt;/execution&amp;gt;
            &amp;lt;execution&amp;gt;
               &amp;lt;id&amp;gt;report&amp;lt;/id&amp;gt;
               &amp;lt;goals&amp;gt;
                  &amp;lt;goal&amp;gt;report&amp;lt;/goal&amp;gt;
               &amp;lt;/goals&amp;gt;
            &amp;lt;/execution&amp;gt;
         &amp;lt;/executions&amp;gt;
      &amp;lt;/plugin&amp;gt;
   &amp;lt;/plugins&amp;gt;
&amp;lt;/build&amp;gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;There is a very good post at &lt;a href="https://community.sonarsource.com/t/coverage-test-data-importing-jacoco-coverage-report-in-xml-format/12151" rel="noopener noreferrer"&gt;https://community.sonarsource.com/t/coverage-test-data-importing-jacoco-coverage-report-in-xml-format/12151&lt;/a&gt; that explains importing JaCoCo coverage report in XML format.&lt;/p&gt;

&lt;p&gt;And voila, that's all you need to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Testing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Download and run sonarqube via docker:
&lt;code&gt;docker run -d -p 9000:9000 sonarqube&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then access the dashboard at : &lt;a href="http://localhost:9000" rel="noopener noreferrer"&gt;http://localhost:9000&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Back at your project directory run &lt;code&gt;mvn clean install&lt;/code&gt; to build your code then &lt;code&gt;mvn sonar:sonar&lt;/code&gt; to sync to sonarqube.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Back at your sonar dashboard you will see your coverage info as follows:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6fk37jfw4e79g383cev.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6fk37jfw4e79g383cev.png" alt="Sonar Dashboard Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Caveat
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;To exclude packages or files from the coverage add them as following in the properties section of your &lt;code&gt;pom.xml&lt;/code&gt; :&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&amp;lt;properties&amp;gt;
   &amp;lt;java.version&amp;gt;21&amp;lt;/java.version&amp;gt;
   &amp;lt;jacoco.version&amp;gt;0.8.11&amp;lt;/jacoco.version&amp;gt;
   &amp;lt;sonar.exclusions&amp;gt;**/schemas/**,**/config/**&amp;lt;/sonar.exclusions&amp;gt;
   &amp;lt;sonar.coverage.exclusions&amp;gt;**/schemas/**,**/config/**&amp;lt;/sonar.coverage.exclusions&amp;gt;
&amp;lt;/properties&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Run &lt;code&gt;mvn clean install&lt;/code&gt; then &lt;code&gt;mvn sonar:sonar&lt;/code&gt; and your coverage will update. If a devops pipeline is set, just push your changes and you will see them at your sonarqube dashboard.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can also add the Sonarlint plugin/extension to your IDE or Code Editor to allow you catch most of the issues before you commit or build.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>springboot</category>
      <category>java</category>
      <category>maven</category>
      <category>sonarqube</category>
    </item>
    <item>
      <title>How to Set Up a Local Development Workflow with Docker for your Go Apps (with MongoDB and Mongo Express)</title>
      <dc:creator>Cosmas Gikunju</dc:creator>
      <pubDate>Fri, 15 May 2020 14:59:42 +0000</pubDate>
      <link>https://dev.to/itscosmas/how-to-set-up-a-local-development-workflow-with-docker-for-your-go-apps-with-mongodb-and-mongo-express-f99</link>
      <guid>https://dev.to/itscosmas/how-to-set-up-a-local-development-workflow-with-docker-for-your-go-apps-with-mongodb-and-mongo-express-f99</guid>
      <description>&lt;p&gt;Cover Photo by Etienne Girardet on Unsplash&lt;/p&gt;

&lt;p&gt;Setting up a development workflow can be time consuming and frustrating especially when there is a need to install a lot of tools. But with the advent of containers this became easy, way easy. In this article I'll explain how I set up my dev workflow for Go Apps being my primary working tool. &lt;/p&gt;

&lt;p&gt;So in setting up a new project I start out by listing out the tooling I need for maximum productivity along the way. In this guide, I'll build an extremely easy Go api with just two endpoints, one that receives a &lt;code&gt;GET Request&lt;/code&gt; to the base endpoint &lt;code&gt;/&lt;/code&gt; and returns a welcome message and another one that simply receives a book as &lt;code&gt;json&lt;/code&gt; object from a &lt;code&gt;POST Request&lt;/code&gt; to the api endpoint &lt;code&gt;/books&lt;/code&gt; then saves this book in a MongoDB collection. So to work on this project I need the following tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Golang and the apps dependencies&lt;/li&gt;
&lt;li&gt;Mongo DB as the database&lt;/li&gt;
&lt;li&gt;Mongo Express (optional) for my database admin interface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To start things of, open your &lt;code&gt;terminal&lt;/code&gt; or &lt;code&gt;cmd&lt;/code&gt; window and navigate to your desired working directory. In my case I have a folder named &lt;code&gt;projects&lt;/code&gt; in my home directory where I organize my projects.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Clone this repository &lt;a href="https://github.com/ItsCosmas/docker-go-dev"&gt;https://github.com/ItsCosmas/docker-go-dev&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ItsCosmas/docker-go-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navigate to the newly created directory&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;docker-go-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once Inside the &lt;code&gt;docker-go-dev&lt;/code&gt; directory, open with your favorite editor. In my case VS Code.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make Sure your directory tree looks like this.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xLubwhkg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/9udt0ry62rwer7is8260.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xLubwhkg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/9udt0ry62rwer7is8260.png" alt="Tree directory Screenshot" width="476" height="616"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open the &lt;code&gt;go-app&lt;/code&gt; folder and Look at the &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Pull base image
FROM golang:1.18-alpine

# Install git
RUN apk update &amp;amp;&amp;amp; apk add --no-cache git

# Where our files will be in the docker container 
WORKDIR /opt/go-app

# Copy the source from the current directory to the working Directory inside the container 
# Source also contains go.mod and go.sum which are dependency files
COPY . .

# Get Dependency
RUN go mod download

# Install Air for hot reload
RUN go install github.com/cosmtrek/air@latest

# The ENTRYPOINT defines the command that will be ran when the container starts up
# In this case air command for hot reload go apps on file changes
ENTRYPOINT air

&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I have well commented the Dockerfile but I will explain a few bits.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A Dockerfile is a text document that contains all the commands a &lt;br&gt;
user could call on the command line to assemble an image.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;We start by pulling the base go image from the docker hub. The alpine tag indicates the alpine version which is bit lightweight and should work well in development, I won't go to much optimization for the moment. &lt;/li&gt;
&lt;li&gt;Then install git which is a dependency to get some packages.&lt;/li&gt;
&lt;li&gt;Then we set the work directory inside the container in my case it is &lt;code&gt;/opt/go-app&lt;/code&gt; that is where our files will live inside the container.&lt;/li&gt;
&lt;li&gt;Then we copy the source from the current directory to the working Directory set above. Our Source Code also contains &lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt; which are dependency files&lt;/li&gt;
&lt;li&gt;After that we get the app service dependencies using &lt;code&gt;go mod download&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;We will need Live Reload for our app so we install &lt;code&gt;air&lt;/code&gt; &lt;a href="https://github.com/cosmtrek/air"&gt;https://github.com/cosmtrek/air&lt;/a&gt; which is a live reload utility for Go apps.&lt;/li&gt;
&lt;li&gt;Finally we pass an &lt;code&gt;ENTRYPOINT&lt;/code&gt; which defines the command that will be ran when the container starts up, in our case its &lt;code&gt;air&lt;/code&gt; the Live reload utility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it for the &lt;code&gt;go-app&lt;/code&gt; service now we move a directory up to the &lt;code&gt;docker-compose.yml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;This is the content of the compose file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.7'
services:
    # The Go App
    go-app:
        build: ./go-app
        container_name: go_app
        depends_on:
            - 'mongo'
        environment:
            - PORT=8000
        ports:
            - '8000:8000'
        volumes:
            - './go-app:/opt/go-app:cached'
    # MongoDB
    mongo:
        image: 'mongo:4.4'
        container_name: mongo
        ports:
            - '27017:27017'
        volumes:
            - ./mongodata:/data/db
        restart: always
    # Mongo Express Web-based MongoDB admin interface
    mongo_express:
        image: 'mongo-express:0.54.0'
        container_name: mongo_express
        depends_on:
            - 'mongo'
        ports:
            - '8081:8081'
        restart: always

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll explain the core parts of this file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To learn more about &lt;code&gt;docker-compose.yml&lt;/code&gt; file head over to the official docs at &lt;a href="https://docs.docker.com/compose/"&gt;https://docs.docker.com/compose/&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First we define our version tag for the compose file in our case &lt;code&gt;3.7&lt;/code&gt; as at the time of writing. &lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  go-app service
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Next We will define the services needed for our application. We start with the &lt;code&gt;go-app&lt;/code&gt; service which we just created above, remember that &lt;code&gt;Dockerfile&lt;/code&gt;? &lt;/li&gt;
&lt;li&gt;The go-app service uses an image build from the &lt;code&gt;Dockerfile&lt;/code&gt; inside the &lt;code&gt;go-app&lt;/code&gt; directory. &lt;/li&gt;
&lt;li&gt;We give it a custom name in our case &lt;code&gt;go_app&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Then We indicate that the service is dependent on another service called &lt;code&gt;mongo&lt;/code&gt; which will be our database service.&lt;/li&gt;
&lt;li&gt;Finally We pass in some environment variables for use inside the container. In our case &lt;code&gt;PORT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Last We bind the container and the host machine to the exposed port &lt;code&gt;8000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finally we define a volume which maps our source directory to the work directory inside the container, that way changes in our source code from our host machine will reflect inside the container.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Mongo Service
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;First we define the image that shall be used in our &lt;code&gt;mongo&lt;/code&gt; service, in our case an official image with the bionic tag.&lt;/li&gt;
&lt;li&gt;Then we give it a custom name &lt;code&gt;mongo&lt;/code&gt; which is the default, could be anything.&lt;/li&gt;
&lt;li&gt;Then We bind the container and the host machine to the exposed port &lt;code&gt;27017&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then We define a volume which maps a local directory &lt;code&gt;mongodata&lt;/code&gt; to the data directory inside the container, that way we shall have persistent storage even if our container fails.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lastly we add a &lt;code&gt;restart:always&lt;/code&gt; flag to always restart the container if it stops.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Mongo Express (Optional)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A Web-based MongoDB admin interface that will allows to manage our mongo database in the browser.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;First We define the image , in our case the official &lt;code&gt;mongo-express&lt;/code&gt; image from docker hub.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We give it a custom container name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We add a &lt;code&gt;depends_on&lt;/code&gt; flag so that the container only runs if &lt;code&gt;mongo&lt;/code&gt; service is running.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then We bind the container and the host machine to the exposed port &lt;code&gt;8081&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lastly we add a &lt;code&gt;restart:always&lt;/code&gt; flag to always restart the container if it stops.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Action
&lt;/h1&gt;

&lt;p&gt;Now that everything is set up, it's time to see our containers in action.To start our containers we run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is correct docker will build the containers and you should see this output in your terminal indicating your containers were built and started successfully:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5WBHkf1W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/0st1ju76w1xn6at05f7k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5WBHkf1W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/0st1ju76w1xn6at05f7k.png" alt="Containers Created" width="671" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's obviously more output but I'm focusing on this. Once that's done you can hit the base endpoint &lt;code&gt;http://localhost:8000&lt;/code&gt; to verify its working. You can use a tool like postman &lt;a href="https://www.postman.com/"&gt;https://www.postman.com/&lt;/a&gt; or just &lt;code&gt;curl&lt;/code&gt; on your terminal.&lt;/p&gt;

&lt;p&gt;Open a new Terminal and input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8000&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your app worked, you should see similar output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"Welcome to your App on Docker"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;GET&lt;/code&gt; endpoint actually works.&lt;br&gt;
Now let us test the &lt;code&gt;POST&lt;/code&gt; Endpoint. On your terminal enter this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "name":"My Best Book Ever",
    "pages": 12
}'&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/books&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it works you should see this Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"code"&lt;/span&gt;:200,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"Book Published"&lt;/span&gt;,&lt;span class="s2"&gt;"data"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="s2"&gt;"My Best Book Ever"&lt;/span&gt;,&lt;span class="s2"&gt;"pages"&lt;/span&gt;:12&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;POST&lt;/code&gt; Endpoint on Postman:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y2KChVOb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/4v7spcs27xrvvcvaalpu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y2KChVOb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/4v7spcs27xrvvcvaalpu.png" alt="Postman Post" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If success, you should see this output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ELOT9CfM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/tj66ey01n225cjwf8rv8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ELOT9CfM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/tj66ey01n225cjwf8rv8.png" alt="A successful Request on Postman" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  To Check Live Reload, Just Edit a file e.g. The &lt;code&gt;app/app.go&lt;/code&gt; :
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // API Home
    r.GET("/", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "Welcome to your App on Docker",
        })
    })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can change the message to whatever you want and once you save your file you should see a similar output on your terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Mc1OtmXd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/w5wxrdkeja5r5k25ewos.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Mc1OtmXd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/w5wxrdkeja5r5k25ewos.png" alt="Live Reload" width="800" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  To check whether the data was saved to Mongo We'll use Mongo-Express:
&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open &lt;a href="http://localhost:8081/"&gt;http://localhost:8081/&lt;/a&gt; in your browser. You should see the database &lt;code&gt;books-db&lt;/code&gt; and if not just refresh your browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yzyzQGm6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/gwlm2o1eko2q3l7jgg6k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yzyzQGm6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/gwlm2o1eko2q3l7jgg6k.png" alt="Mongo Client" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click on &lt;code&gt;books-db&lt;/code&gt; and it should redirect to the Database Page as seen below::&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8VVOMneF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/xx7d5ibnlbqr99znscpr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8VVOMneF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/xx7d5ibnlbqr99znscpr.png" alt="Collections Page" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click on the Collection you wish, in this case &lt;code&gt;book&lt;/code&gt; collection and the records from the collection should be displayed as below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AtHO3wOE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/gapl629yvo3njcgh4z4k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AtHO3wOE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/gapl629yvo3njcgh4z4k.png" alt="Records" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can also view a Single Document if you wish by just clicking on it and it should redirect to a document page similar to this:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QHP-T6Ff--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/3h1ae3v5wkelep065d0s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QHP-T6Ff--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/3h1ae3v5wkelep065d0s.png" alt="Single Document Page" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So In this article I have shown you how I set up my local dev environment and workflow for go apps, Mongo and Mongo Express. The process is almost similar for other stacks, the key is to have a standardized dev environment for maximum productivity. By the way shipping to production is very easy with &lt;code&gt;docker&lt;/code&gt; you require just a few optimizations and that's all, no OS dependency issues ever.&lt;/p&gt;

&lt;p&gt;I have updated the article and the github repository so that this tutorial can be helpful even now as at 2022.&lt;/p&gt;

&lt;p&gt;And that's it for the article. PS: Trying to do some regular writing 🙂.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>go</category>
      <category>mongodb</category>
    </item>
  </channel>
</rss>
