Spring Boot API Tutorial: Build, Document, and Secure a REST API

This article is written by Alvaro, a member of the Zuplo community and longtime API builder. You can check out more of his work here. All opinions expressed are his own.

If you're keeping up with Zuplo's blog—and you should, given its great insights—you might have come across my previous posts on building APIs with Ruby and Python.

This time, we're diving into Java and the popular Spring Boot Framework 🤓

Previously, we used quotes from Dune with MongoDB Atlas for a consumer API and built a simple blog API using data classes—without a database. This time, we’re combining both approaches to create a blog API backed by MongoDB Atlas.

We’ll build a straightforward Blog Post CRUD API, handling everything from creating and reading posts to updating and deleting them. Let’s get started!

What are we going to do today?#

  1. Creating the project
  2. Adding the required packages
  3. Creating a MongoDB Atlas Account
  4. Creating the API
  5. Testing our API
  6. Hosting our API for the world to see
  7. Creating a project on Zuplo
  8. Adding Rate Limiting
  9. Setting Up API Key Authentication
  10. Configuring Geographic Request Filtering
  11. Developer Documentation Portal
  12. Wrapping Up

Creating the project#

Since we’re building a Spring Boot project, we’ll use spring initializer to get started.

Spring intializer

When we are done, we can press the Generate button to download the project on a .zip file

We can then open it using IntelliJ IDEA or the IDE of your choice.

Adding the required packages#

spring initializer will add most of the packages that we need for this project, so instead of displaying the pom.xml file, we're going to show the packages that we need to use:

org.springframework.boot --> spring-boot-starter-data-mongodb
org.springframework.boot --> spring-boot-starter-web
org.springframework.boot --> spring-boot-starter-test
org.springdoc --> springdoc-openapi-starter-webmvc-ui --> 2.5.0
com.jayway.jsonpath --> json-path
me.paulschwarz --> spring-dotenv --> 3.0.0

Creating a MongoDB Atlas account#

Create your MongoDB Atlas free account. It’s straightforward but here’s a little guide just in case.

In the end, you’re going to receive a string like this one:

mongodb+srv://<USER>:<PASSWORD>@blagcluster.<URL>.mongodb.net/quotes?retryWrites=true&w=majority&appName=BlagCluster

We need to keep it safe as we're going to use it later on in our project.

Something important to keep in mind is that in order to access MongoDB Atlas from our local machine, we need to authorize our IP Address. Otherwise, we will have auth problems. Head to network and press Add Current IP Address.

Add IP address

Creating the API#

First things first, create a .env file on your project root with the following information:

USERNAME=<YOUR_USERNAME>
PASSWORD=<YOUR_PASSWORD>

Then open application.properties which should be located on the src/main/java/resources folder and add the following:

spring.data.mongodb.uri=mongodb+srv://${USERNAME:World}:${PASSWORD:World}@blagcluster.b9jxy.mongodb.net/posts
spring.data.mongodb.database=posts

# OpenAPI Config
springdoc.api-docs.path=/api-docs
springdoc.swagger-ui.path=/swagger-ui.html
springdoc.version=1.0.0
springdoc.title=Blog Post API
springdoc.description=API for managing blog posts

This will connect our application with MongoDB and it will also generate the Swagger or OpenAPI Spec.

We need to create a package named model and put it inside a file named BlogPost with the following code:

package com.blog.posts.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document("BlogPost")
public class BlogPost {
    @Id
    private String id;
    private String title;
    private String description;

    public BlogPost(String title, String description) {
        super();
        this.title = title;
        this.description = description;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

This will create our database or collection inside MongoDB. We're providing getter and setter functions to access our DB properties.

Now, we need to create a package named posts and put it inside a file named PostRepository with the following code:

package com.blog.posts.posts;

import org.springframework.data.mongodb.repository.MongoRepository;
import com.blog.posts.model.BlogPost;

public interface PostRepository extends MongoRepository<BlogPost, String> {

}

This is just an interface to be able to interact with our model.

Here's the meat of our API. Create a file named BlogController with the following code:

package com.blog.posts;

import com.blog.posts.model.BlogPost;
import com.blog.posts.posts.PostRepository;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api")
@Tag(name = "Blog Posts", description = "Operations for blog posts")
public class BlogController {
    @Autowired
    private PostRepository blogPost;

    @GetMapping("/")
    public String welcome() {
        return "Welcome to the Simple Blog API!";
    }

    @Operation(summary = "Get all posts")
    @GetMapping("/posts")
    public List<BlogPost> getAllPosts() {
        return blogPost.findAll();
    }

    @Operation(summary = "Get a post")
    @GetMapping("/posts/{id}")
    public Optional<BlogPost> getPost(@PathVariable String id){
        return blogPost.findById(id);
    }

    @Operation(summary = "Create new post")
    @PostMapping("/posts")
    public BlogPost addPost(@RequestBody BlogPost post) {
        return blogPost.save(post);
    }

    @Operation(summary = "Delete post")
    @DeleteMapping("/posts/{id}")
    public ResponseEntity<?> deletePost(@PathVariable String id) {
        return blogPost.findById(id)
                .map(post -> {
                    blogPost.delete(post);
                    return ResponseEntity.ok().build();
                })
                .orElse(ResponseEntity.notFound().build());
    }

    @Operation(summary = "Update post")
    @PutMapping("/posts/{id}")
    public BlogPost updatePost(@PathVariable String id, @RequestBody BlogPost post) {
        BlogPost existingPost = blogPost.findById(id)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found"));
        existingPost.setTitle(post.getTitle());
        existingPost.setDescription(post.getDescription());
        return blogPost.save(existingPost);
    }
}

We're defining endpoints to read, create, update, and delete posts, but we're also adding Swagger information, which will help us generate our OpenAPI Spec file.

And last but no least, the file that will allow us to run our application in the first place, PostsApplication

package com.blog.posts;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

@SpringBootApplication
@EnableMongoRepositories
public class PostsApplication {

  public static void main(String[] args) {
    SpringApplication.run(PostsApplication.class, args);
  }
}

And our application should be ready to roll.

Testing our API#

To run our application, we can head to the terminal window and type the following:

mvn spring-boot:run

Running Spring boot

Open your favorite web browser and navigate to http://localhost:8080/api/:

Default spring page response

Success! It’s working as expected, although we have only tested the presentation endpoint 😅 Let's add some entries:

curl -X POST "http://127.0.0.1:8080/api/posts" \
-H "Content-Type: application/json" \
-d '{"title": "Zuplo and Ruby," "description": "Zuplo, Ruby, and Sinatra."}'
curl -X POST "http://127.0.0.1:8080/api/posts" \
-H "Content-Type: application/json" \
-d '{"title": "Zuplo and Python," "description": "Zuplo, Python, and Flask."}'
curl -X POST "http://127.0.0.1:8080/api/posts" \
-H "Content-Type: application/json" \
-d '{"title": "Zuplo and Java," "description": "Zuplo, Java, and Spring."}'

We should have three new entries, let's check them out.

Navigate to http://localhost:8080/api/posts:

Posts response

Great news! It works as expected, but only on our local machine. Wouldn’t it be amazing to share it with the world?

First, we need to upload our project to GitHub.

But before we do that, we need to change something on our application.properties file:

#Use this locally
#spring.data.mongodb.uri=mongodb+srv://${USERNAME:World}:${PASSWORD:World}@blagcluster.b9jxy.mongodb.net/posts
#Use this for Koyeb
spring.data.mongodb.uri=mongodb+srv://${USERNAME}:${PASSWORD}@blagcluster.b9jxy.mongodb.net/posts

On Koyeb, we cannot use the .env file as we did locally, and it should not be uploaded to Github either. So, you'd better add it to your .gitignore file.

Hosting our API for the world to see#

There are plenty of options available for hosting an API. We’ll use Koyeb for this particular API.

We want to create a new web service and select the repository we want to use—in this case, SimpleBlogAPI_Spring.

Choose github

Create a new service

Let's choose CPU Eco and select whatever region is close to us.

Choose CPU Eco

Here is where we define our environment variables, so consider this a replacement for the .env file:

Setting environment variable

We're now ready to deploy our service:

Deploying to Koyeb

And it should be ready. We need to copy the URL and open it on a browser.

API deployed

Although it will not work, for the same reason, our local host didn't work at first. We need to add Koyeb's Outbound IP Address to the Network section of MongoDB Atlas.

To do this, we need to run either of the following commands on the terminal window, depending on the region we choose.

dig all-was1.infra.prod.koyeb.com

dig all-fra1.infra.prod.koyeb.com

Copy each IP Address from the response, and add it to the network section.

Add IP access list entry

Once we're done, we can call up our service.

Remote hosted response

As we have created our API with Swagger UI, we can take advantage of it 😎

In our browser, we need to navigate to:

https://modest-leonie-atejada-c8e59588.koyeb.app/swagger-ui/index.html

Swagger UI

It might not look helpful, but if you click the arrow on the right-hand side, you can expand the endpoints and see everything in action.

Playing with Swagger UI

But that's not all we can do, we can also download this as a file and reuse it later.

https://modest-leonie-atejada-c8e59588.koyeb.app/api-docs

Accessing the raw OpenAPI file

Download the file using .json and keep it safe.

Are we done? Yes and no. If we’re satisfied with something simple, unsecured, and somehow rough around the edges, then sure, we’re good. But if we want to improve it with minimal effort, we can bring in Zuplo and take things up a notch.

Creating a project on Zuplo#

After creating a Zuplo account, we’ll need to set up a new project. We’ll name it simple-blog-api-spring, select an empty project, and then create it. Wondering why Zuplo? Imagine building rate limits, authentication, monetization, and other features entirely on your own. That’s a lot of work and hassle. Zuplo simplifies all of that, making it a breeze.

Create a Zuplo project

Once the project is created, we’ll be greeted with this screen. From here, press Start Building:

Start building

To start enhancing our API, click on routes.oas.json:

routes.oas.json

Next, we need to add a route, which Zuplo will manage:

OpenAPI designer

In the Ruby post, we went with Add Route, but in the Python post, we selected Import OpenAPI instead, so let's do the same here. Now, you might be wondering—how exactly do we do that? No worries, it’s easier than you think. 🤓

Let's upload the OpenAPI file that we downloaded earlier.

Import OpenAPI

All of our endpoints will be imported automagically 🤩

OpenAPI imported

Nothing else to do here, everything is ready for testing.

View routes

Choose any endpoint like Get all posts, and press the Test button.

Rest endpoints

Success! 🥳🥳🥳 Our Zuplo integration is working perfectly!

Adding Rate Limiting#

We don’t want people abusing our API or attempting to take down our server with DDoS attacks. While coding a rate limit policy isn’t difficult, there are many factors to consider—so why bother? Let’s have Zuplo manage that for us.

Add inbound policy

We need to add a new policy on the request side. Since we’ll be dealing with many policies, simply type rate and select Rate Limiting:

Choosing rate limit policy

All we need to do here is press Ok. Easy, right?

Applying the rate limiting policy

Our rate limit has been added. Now, we need to save and run the test three times. On the third attempt, we’ll hit the rate limit. Of course, we can adjust this in the policy, as shown in the image above, where requestsAllowed is set to 2. We only need to save for the change to be applied.

Save the policy

We can do that for all the other endpoints if we want to.

Exceeding the request limit will temporarily block further data requests. We’ll need to wait a minute before trying again.

Rate limit response

So far, so good—but what if we want to prevent unauthorized access to our API? We’ll need to implement some degree of authentication. While it’s not overly complicated to build, it involves multiple steps, configurations, and testing. Wouldn’t it be great to let Zuplo handle those details for us? That way, we can focus solely on our API.

Setting Up API Key Authentication#

We need to navigate back to the Policies section and add a new policy—this time, API Key Authentication:

API key authentication policy

There’s not much to do here—just press OK.

API key auth policy configuration

It’s fundamental to move the api-key-inbound policy to the top:

Move API key policy first

If we save it and try testing the API, access will be denied:

Unauthorized response

At the top of the screen, click Services, then select Configure under API Key Service:

Services tab

We need to create a consumer, which will function as a token:

Creating a consumer

We need to set a name for the subject and specify the email or emails associated with the key:

Configuring a consumer

Once we click Save consumer, an API key will be generated. We can either view it or copy it:

Save the consumer

Now, we can go back to Coderoutes.oas.json to test our API. Here, we need to add the authorization header and pass the Bearer token along with the API key:

Calling the API with an API key

Success once again! It’s working as expected! 🥳🥳🥳

If you rather code it, then read this:

API key API

And that’s how you enhance your API with Zuplo 😎 But wait, there’s more!

Configuring Geographic Request Filtering#

Zuplo provides many interesting policies, so I wanted to test that I have never tested before, and Geo Location Inbound looked interesting enough.

Let's say we want to restrict access to our API from a particular country, province, or even internet provider.

Living in Lima, Perú, I will restrict myself 🤣 and my Internet provider.

Look for Geo-location filtering and add it.

Geo-filtering policy

Let's change the options section with this:

{
  "export": "GeoFilterInboundPolicy",
  "module": "$import(@zuplo/runtime)",
  "options": {
    "block": {
      "asns": "AS6147",
      "countries": "PE",
      "regionCodes": "LIM"
    },
    "ignoreUnknown": true
  }
}

Save it and we can test it.

Block Peru

Developer Documentation Portal#

If we click on Gateway at the bottom of the screen, we’ll see a link to the Developer Portal:

Developer portal link

Here, we can see that every route we create will be added to the Developer Portal:

API developer portal

Wrapping Up#

If you’d like to check the source code for the API, you can see it on my Github repo SimpleBlogAPI_Spring.

If you want to learn more about the tools I used - here are some quick documentation links:

Now, what kind of API will you build with Spring Boot? 😎

Questions? Let's chatOPEN DISCORD
0members online

Designed for Developers, Made for the Edge