{"id":16179131,"url":"https://github.com/jonashackt/microservice-api-spring-boot","last_synced_at":"2025-03-19T01:30:54.330Z","repository":{"id":38285911,"uuid":"362381941","full_name":"jonashackt/microservice-api-spring-boot","owner":"jonashackt","description":"Example project showing how to interact with a Nuxt.js / Vue.js based frontend building a Spring Boot microservice ","archived":false,"fork":false,"pushed_at":"2025-03-14T14:29:34.000Z","size":1620,"stargazers_count":19,"open_issues_count":1,"forks_count":7,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-17T01:35:12.996Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonashackt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-04-28T07:45:15.000Z","updated_at":"2025-03-14T14:29:37.000Z","dependencies_parsed_at":"2023-10-10T14:32:09.572Z","dependency_job_id":"8b679a71-867e-4a24-84d6-4b8ef1e02c17","html_url":"https://github.com/jonashackt/microservice-api-spring-boot","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fmicroservice-api-spring-boot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fmicroservice-api-spring-boot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fmicroservice-api-spring-boot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fmicroservice-api-spring-boot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonashackt","download_url":"https://codeload.github.com/jonashackt/microservice-api-spring-boot/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244336133,"owners_count":20436773,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-10T05:25:32.962Z","updated_at":"2025-03-19T01:30:54.031Z","avatar_url":"https://github.com/jonashackt.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# microservice-api-spring-boot\n[![Build Status](https://github.com/jonashackt/microservice-api-spring-boot/workflows/build-publish/badge.svg)](https://github.com/jonashackt/microservice-api-spring-boot/actions)\n[![renovateenabled](https://img.shields.io/badge/renovate-enabled-yellow)](https://renovatebot.com)\n[![versionspringboot](https://img.shields.io/badge/dynamic/xml?color=brightgreen\u0026url=https://raw.githubusercontent.com/jonashackt/microservice-api-spring-boot/master/pom.xml\u0026query=%2F%2A%5Blocal-name%28%29%3D%27project%27%5D%2F%2A%5Blocal-name%28%29%3D%27parent%27%5D%2F%2A%5Blocal-name%28%29%3D%27version%27%5D\u0026label=springboot)](https://github.com/spring-projects/spring-boot)\n\nExample project showing how to interact with a Nuxt.js / Vue.js based frontend (https://github.com/jonashackt/microservice-ui-nuxt-js) building a Spring Boot microservice\n```shell\n┌────────────────────────────────┐\n│                                │\n│                                │\n│    microservice-ui-nuxt-js     │\n│                                │\n│                                │\n└───────────────┬────────────────┘\n                │\n                │\n┌───────────────▼────────────────┐\n│                                │\n│                                │\n│  microservice-api-spring-boot  │\n│                                │\n│                                │\n└────────────────────────────────┘\n```\n\nMost of the source is simply copied from my project https://github.com/jonashackt/spring-boot-vuejs/tree/master/backend\n\n\n## Acting as API backend for Nuxt.js frontend\n\nWe have a slightly different deployment than the single one in https://github.com/jonashackt/spring-boot-vuejs here, where we deployed everything (including the frontend) into a Spring Boot embedded Tomcat.\n\nNow we deploy the frontend separately from the backend - which also has some implications on the backend, since it only acts as the API now.\n\n\n#### Combine CORS \u0026 Spring Security\n\nTherefore we can remove the [SpaRedirectFilterConfiguration.java](https://github.com/jonashackt/spring-boot-vuejs/blob/master/backend/src/main/java/de/jonashackt/springbootvuejs/configuration/SpaRedirectFilterConfiguration.java) and need to add a `org.springframework.web.bind.annotation.CrossOrigin` Annotation with `@CrossOrigin` to our [BackendController.java](src/main/java/de/jonashackt/springbootvuejs/controller/BackendController.java):\n\n```java\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.CrossOrigin;\nimport org.springframework.web.bind.annotation.RequestMapping;\n\n@Controller\n@RequestMapping(\"/api\")\n@CrossOrigin\npublic class BackendController {\n    ...\n}\n```\n\nhttps://stackoverflow.com/a/37610988/4964553 states:\n\n\u003e To make it work, you need to explicitly enable CORS support at Spring Security level as following, otherwise CORS enabled requests may be blocked by Spring Security before reaching Spring MVC.\n\nSo we need to mind the Spring Security integration! Using the `@CrossOrigin` annotation, we can simply add `and().cors()` to our [WebSecurityConfiguration.java](src/main/java/de/jonashackt/springbootvuejs/configuration/WebSecurityConfiguration.java):\n\n```java\n    http\n            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // No session will be created or used by spring security\n        .and()\n            .httpBasic()\n        .and()\n            .authorizeRequests()\n                .antMatchers(\"/api/hello\").permitAll()\n                .antMatchers(\"/api/user/**\").permitAll() // allow every URI, that begins with '/api/user/'\n                .antMatchers(\"/api/secured\").authenticated()\n                //.anyRequest().authenticated() // protect all other requests\n        .and()\n            .cors() // We need to add CORS support to Spring Security (see https://stackoverflow.com/a/37610988/4964553)\n        .and()\n            .csrf().disable(); // disable cross site request forgery, as we don't use cookies - otherwise ALL PUT, POST, DELETE will get HTTP 403!\n```\n\nSee https://stackoverflow.com/a/67583232/4964553 for a full explanation.\n\n\n## GitHub Actions: Build, create Container Image with Paketo.io \u0026 Publish to GitHub Container Registry\n\nOur [build-publish.yml](.github/workflows/build-publish.yml) already builds our Java/Spring Boot app using JDK 16. It also uses Paketo.io / Cloud Native Buildpacks to create a Container image (incl. a Paketo cache image to speed up builds, see https://stackoverflow.com/a/66598693/4964553) and publish it to GitHub Container Registry:\n\n```yaml\nname: build-publish\n\non: [push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Set up JDK 16\n        uses: actions/setup-java@v1\n        with:\n          java-version: 16\n\n      - name: Build with Maven\n        run: mvn package --batch-mode --no-transfer-progress\n\n  publish-to-gh-container-registry:\n    needs: build\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v1\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Install pack CLI via the official buildpack Action https://github.com/buildpacks/github-actions#setup-pack-cli-action\n        uses: buildpacks/github-actions/setup-pack@v4.1.0\n\n      # Caching Paketo Build see https://stackoverflow.com/a/66598693/4964553\n      # BP_OCI_SOURCE as --env creates the GitHub Container Registry \u003c-\u003e Repository link (https://paketo.io/docs/buildpacks/configuration/#applying-custom-labels)\n      # BP_JVM_VERSION 16, because we use Java 16 inside our Maven build but Paketo defaults to 11\n      - name: Build app with pack CLI \u0026 publish to bc Container Registry\n        run: |\n          pack build ghcr.io/jonashackt/microservice-api-spring-boot:latest \\\n              --builder paketobuildpacks/builder-jammy-base \\\n              --path . \\\n              --env \"BP_OCI_SOURCE=https://github.com/jonashackt/microservice-api-spring-boot\" \\\n              --env \"BP_JVM_VERSION=21\" \\\n              --cache-image ghcr.io/jonashackt/microservice-api-spring-boot-paketo-cache-image:latest \\\n              --publish\n```\n\n## Deploy Spring Boot App to AWS Fargate with Pulumi\n\nUsing Pulumi we can create our AWS resources using vendor neutral Infrastructure-as-Code. So let's create a Pulumi project using the Pulumi CLI:\n\n```shell\nmkdir deployment \u0026\u0026 cd deployment\npulumi new aws-typescript\n```\n\nThen inside our Pulumi program [index.ts](deployment/index.ts) we can use everything discovered in https://github.com/jonashackt/pulumi-typescript-aws-fargate to deploy our Spring Boot App, which is already packaged by Paketo.io and published at the GitHub Container Registry:\n\n```yaml\nimport * as awsx from \"@pulumi/awsx\";\n\n  // Create a load balancer to listen for requests and route them to the container.\nlet loadbalancer = new awsx.lb.ApplicationListener(\"alb\", { port: 8098, protocol: \"HTTP\" });\n\n  // Define Container image published to the GitHub Container Registry\n  let service = new awsx.ecs.FargateService(\"microservice-api-spring-boot\", {\ntaskDefinitionArgs: {\n  containers: {\n    microservice_api_spring_boot: {\n      image: \"ghcr.io/jonashackt/microservice-api-spring-boot:latest\",\n      memory: 768,\n      portMappings: [ loadbalancer ],\n    },\n  },\n},\ndesiredCount: 2,\n});\n\n  // Export the URL so we can easily access it.\n  export const apiUrl = loadbalancer.endpoint.hostname;\n```\n\nNow running a `pulumi up --stack dev` should launch our ECS Cluster, Security Groups, the Loadbalancer and our Fargate Service (incl. TaskDefinition etc.). This is a whole bunch of services, I prepared an Asciinema for this also:\n\n[![asciicast](https://asciinema.org/a/411968.svg)](https://asciinema.org/a/411968)\n\n\n## Solving the Restarting-Loop-Problem in Fargate with TargetGroup HealthChecks\n\nThe setup is great, but AWS keeps restarting my Tasks aka Containers all the time - so the Spring Boot Apps don't really get available, they are terminated all the time!\n\nThe problem is, we didn't define any healthchecks inside our Fargate Taskdefinitions. And the hello world examples like https://www.pulumi.com/docs/tutorials/aws/ecs-fargate/ also don't show us how to configure the correct health check urls.\n\nEven the documentation at https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/lb/ doesn't have an out-of-the-box example, although there are detailed docs about [Manually Configuring Target Groups](https://www.pulumi.com/docs/guides/crosswalk/aws/elb/#manually-configuring-target-groups).\n\nWe need to extend our Pulumi program slightly here because of the need to define the health check URL for our Fargate TaskDefinitions (see https://stackoverflow.com/questions/67405335/pulumi-aws-ecs-fargate-tasks-caught-in-restart-loop-how-to-configure-loadbala):\n\n```typescript\n// Spring Boot Apps port\nconst port = 8098;\n\n// Create a ApplicationLoadBalancer to listen for requests and route them to the container.\nconst alb = new awsx.lb.ApplicationLoadBalancer(\"fargateAlb\");\n\n// Create TargetGroup \u0026 Listener manually (see https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/lb/)\n// so that we can configure the TargetGroup HealthCheck as described in (https://www.pulumi.com/docs/guides/crosswalk/aws/elb/#manually-configuring-target-groups)\n// otherwise our Spring Boot Containers will be restarted every time, since the TargetGroup HealthChecks Status always\n// goes to unhealthy\nconst albTargetGroup = alb.createTargetGroup(\"fargateAlbTargetGroup\", {\n    port: port,\n    protocol: \"HTTP\",\n    healthCheck: {\n        // Use the default spring-boot-actuator health endpoint\n        path: \"/actuator/health\"\n    }\n});\n\nconst albListener = albTargetGroup.createListener(\"fargateAlbListener\", { port: port, protocol: \"HTTP\" });\n\n// Define Container image published to the GitHub Container Registry\nconst service = new awsx.ecs.FargateService(\"microservice-api-spring-boot\", {\n    taskDefinitionArgs: {\n        containers: {\n            microservice_api_spring_boot: {\n                image: \"ghcr.io/jonashackt/microservice-api-spring-boot:latest\",\n                memory: 768,\n                portMappings: [ albListener ]\n            },\n        },\n    },\n    desiredCount: 2,\n});\n```\n\nFirst we need to explicitely create an ApplicationLoadBalancer using `const alb = new awsx.lb.ApplicationLoadBalancer(\"fargateAlb\");`. From the `alb` variable we're able to call `alb.createTargetGroup()`, where we can configure a health check path (see this for more info https://www.pulumi.com/docs/guides/crosswalk/aws/elb/#manually-configuring-target-groups)!\n\nAfter having created the `ApplicationTargetGroup` we need to create an `ApplicationListener`, which we previously only needed to create in our hello world example. The `FargateService` stays the same - except of explicitely using the `ApplicationListener` inside the `portMappings` defintion.\n\nNow our Fargate Tasks stop beeing stopped and created over and over again. And inside our TargetGroup we should see our healthy Fargate Containers:\n\n![alb-targetGroup-health-checks-green-with-fargate-containers](screenshots/alb-targetGroup-health-checks-green-with-fargate-containers.png)\n\n\n\n## GitHub Actions: Deploy to AWS Fargate using Pulumi\n\nAs already described here: https://github.com/jonashackt/azure-training-pulumi#pulumi-with-github-actions there are some steps to take in order to use Pulumi with GitHub Actions.\n\nhttps://www.pulumi.com/docs/guides/continuous-delivery/github-actions/\n\nIt's really cool to see that there's a Pulumi GitHub action project https://github.com/pulumi/actions already ready for us.\n\n\n#### Create needed GitHub Repository Secrets\n\nFirst we need to create 5 new GitHub Repository Secrets (encrypted variables) in your repo under `Settings/Secrets`.\n\nWe should start to create a new Pulumi Access Token `PULUMI_ACCESS_TOKEN` at https://app.pulumi.com/jonashackt/settings/tokens\n\nNow we need to create the AWS specific variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` (they need to be exactly named like this, see https://www.pulumi.com/docs/intro/cloud-providers/aws/setup/#environment-variables). Create them all as GitHub Repository Secrets.\n\n\n#### Create GitHub Actions workflow\n\nLet's add a job `deploy-to-aws-fargate` to our GitHub Actions workflow [build-publish-deploy.yml](.github/workflows/build-publish-deploy.yml):\n\n```yaml\n  deploy-to-aws-fargate:\n    needs: publish-to-gh-container-registry\n    runs-on: ubuntu-latest\n    env:\n      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}\n      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n      PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}\n    # Create an GitHub environment for our Spring Boot Fargate Deployment\n    environment:\n      name: microservice-api-spring-boot-deployment\n      url: ${{ steps.pulumi-up.outputs.api_url }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@master\n\n      - name: Setup node env\n        uses: actions/setup-node@v2.1.2\n        with:\n          node-version: '14'\n\n      - name: Cache node_modules\n        uses: actions/cache@v2\n        with:\n          path: ~/.npm\n          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}\n          restore-keys: |\n            ${{ runner.os }}-node-\n\n      - name: Install Pulumi dependencies before npm run generate to prevent it from breaking the build\n        run: npm install\n        working-directory: ./deployment\n\n      - name: Install Pulumi CLI\n        uses: pulumi/action-install-pulumi-cli@v1.0.2\n\n      - name: Deploy Spring Boot App Container image to AWS Fargate with Pulumi\n        id: pulumi-up\n        run: |\n          pulumi stack select dev\n          pulumi preview\n          pulumi up -y\n          echo \"::set-output name=api_url::http://$(pulumi stack output apiUrl)\"\n        working-directory: ./deployment\n\n```\n\nWe use the possibility [to define the environment variables on the workflow's top level](https://docs.github.com/en/actions/reference/environment-variables) to reduce the 3 definition to one.\n\nAfter checking out our repo, we setup a node environment for Pulumi and also create a cache for all those npm modules. We then install the Pulumi TypeScript programs dependencies and also the Pulumi CLI using the great [pulumi/action-install-pulumi-cli](https://github.com/pulumi/action-install-pulumi-cli).\n\nFinally we create our Fargate Cluster and ApplicationLoadbalancers using Pulumi. Also we don't miss to create a GitHub Environment with a dynamic URL based on our Pulumi Fargate deployment.\n\nThis makes for a really nice GitHub Actions workflow in the UI:\n\n![github-actions-fullworkflow](screenshots/github-actions-fullworkflow.png)\n\n\n#### How to force Pulumi to deploy always the newest 'latest' Container image?\n\nIf we only use `ghcr.io/jonashackt/microservice-api-spring-boot:latest` inside our `awsx.ecs.FargateService` definition, Pulumi doesn't see any changes and thus doesn't deploy new versions of the same Container image tag anymore.\n\nBut there's a `--force-new-deployment` in the AWS CLI (see https://stackoverflow.com/a/48572274/4964553) - and also a boolean parameter `forceNewDeployment` inside the `aws.ecs.Service` (https://www.pulumi.com/docs/reference/pkg/aws/ecs/service/), which our `awsx.ecs.FargateService` inherits from:\n\n\u003e Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination (e.g. myimage:latest)\n\nSo let's use it inside our [deployment/index.ts](deployment/index.ts):\n\n```javascript\n// Define Container image published to the GitHub Container Registry\nconst service = new awsx.ecs.FargateService(\"microservice-api-spring-boot\", {\n    taskDefinitionArgs: {\n        containers: {\n            microservice_api_spring_boot: {\n                image: \"ghcr.io/jonashackt/microservice-api-spring-boot:latest\",\n                memory: 768,\n                portMappings: [ albListener ]\n            },\n        },\n    },\n    desiredCount: 2,\n    forceNewDeployment: true\n});\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonashackt%2Fmicroservice-api-spring-boot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonashackt%2Fmicroservice-api-spring-boot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonashackt%2Fmicroservice-api-spring-boot/lists"}