Coding Heaven logo

Coding Heaven

Food Delivery App with Java Spring Boot, RabitMQ & Microservices. Part 1

    Category: Microservices
Author's Photo

Author: Vlada

Date: 7/5/2026

Thumbnail Food Delivery App with Java Article

Food Delivery App

Hey fellow dev, long time no see! Sorry for being away for so long, but I’m back, and this summer is definitely heating up. I hope you’re enjoying it as much as I am.

Today, we’re going to start working on a food delivery application and learn more about microservices. If you’d like to understand the theory first, you can check out this article: Microservices 101. If you already have some familiarity with the concept, you can go ahead and start building the app with me.

We’ll be using Java Spring Boot for the server-side, an H2 in-memory database for persistence, Angular for the front end, and RabbitMQ as our message broker. In this particular article, we’re going to create one service and set it up as a producer.

So, grab some ice for your summer drink and let’s get started!

Setting Up Our Java Project and Dependencies

Before we create our first microservice, let's look at how our project should behave.

We are building a food delivery application that consists of three microservices. And they need to talk to each other, and this is why we are going to use RabbitMQ.
The first is the Order Service. This service allows users to browse a list of restaurants, add menu items to an order, and place the order.

The second is the Restaurant Service. After receiving an order from the Order Service, it verifies that the restaurant exists and that all requested menu items are valid. It then simulates accepting the order, updates its own database, and publishes an event to the third service, the Delivery Service.

The Delivery Service receives the event, assigns a delivery driver, and sends an order status update back to the Order Service.

Initializing our Order Service


Our first step will be initializing our Spring Boot application, as we did in Create Budget Tracker with Java Spring Boot.


selected dependencies

You can see that we've added Spring Web, Spring Data JPA, AMQP (RabbitMQ), H2 Database, Validation, and Lombok. We'll need all of these dependencies for our Service.

I created a new folder in my directory called microservices. Inside this folder, we're going to extract our newly created Java project.

Creating the Restaurant Endpoint and Service

Creating Restaurant API

We are going to have several controllers in our Order Service, so I’ll go ahead and create a new folder called API. Inside src/main/java/…/orderService, I’m going to create a new folder named api:


mkdir API

Inside it, I'm going to create a new class named RestaurantController.java. It will look like this:



package codingHeaven.microservices.orderService.API;

import codingHeaven.microservices.orderService.Models.Restaurant;
import codingHeaven.microservices.orderService.Services.RestaurantService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/restaurants")
public class RestaurantController {

    private final RestaurantService restuarantService;

    public RestaurantController(RestaurantService restuarantService) {
        this.restuarantService = restuarantService;
    }

    @GetMapping
    public List<Restaurant> getListOfRestuarants() {
        return restuarantService.getListOfRestuarants();
    }
}



We use the @RestController annotation to mark the RestaurantController class as a REST controller. We also set the base route to "/restaurants" using @RequestMapping.

Next, we use dependency injection to inject our service layer. Finally, we create a GET endpoint that returns a list of restaurants.


Setting Up Restaurant Service and Repository

As you can see, we've introduced a new variable named restaurantService. Now it's time to create it. This service will contain our business logic and interact with the database to retrieve restaurants from memory.

It's important to distinguish between services and microservices. A service within a project is part of the application's architecture and is responsible for implementing the business logic. In a typical layered architecture, we have the controller, service, and data access layers, and separating these responsibilities makes the code easier to maintain and test.

A microservice, on the other hand, is a completely separate project with its own codebase, database, and deployment. For example, our Order Service, Restaurant Service, and Delivery Service are all independent microservice projects that communicate with each other.

Now, I'm going to create a new folder where all of our service classes will be located.


mkdir Services

Inside the directory, I'm going to create two files: IRestaurantService.java and RestaurantService.java .

As you may remember, interfaces are contracts that classes implement, and they must fulfill all the methods defined in the interface. We use them to achieve cleaner code and better abstraction.


Place this into our interface:


// IRestaurantService.java

package codingHeaven.microservices.orderService.Services;
import codingHeaven.microservices.orderService.Models.Restaurant;
import java.util.List;

public interface IRestaurantService {
    public List<Restaurant> getListOfRestuarants();
}

So our RestaurantService should implement only one method: getListOfRestaurants().
Let’s go ahead and create the actual class and its implementation.


// RestaurantService.java
package codingHeaven.microservices.orderService.Services;
import codingHeaven.microservices.orderService.Models.Restaurant;
import codingHeaven.microservices.orderService.Repositories.RestaurantRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class RestaurantService implements IRestaurantService {

    private final RestaurantRepository restaurantRepo;

    public RestaurantService(RestaurantRepository restaurantRepo) {
        this.restaurantRepo = restaurantRepo;
    }
    public List<Restaurant> getListOfRestuarants(){

        return restaurantRepo.findAll();
    }
}


Our RestaurantService implements the interface, and the getListOfRestaurants() method calls our repository’s findAll() method. This is part of Java JPA and is known as the repository pattern . It is already included in the library, so we don't have to write our own SQL queries.

As you can see, we have a RestaurantRepository variable called restaurantRepo, which is very similar to what we saw in the controller, where we injected the service dependency and called its method. The same concept applies here.

And you probably already know what we're going to do next, yes, we’re going to create the RestaurantRepository. Luckily, it’s going to be pretty simple.


Creating Restaurant Repository and Models

We are going to create two more folders that will be used for our repositories.


mkdir Repositories
mkdir Models


Inside Models folder we are going to create a new file named Restaurant.java place this code, this is representation of our database table that holds data about Restaurants.


// Models folder

package codingHeaven.microservices.orderService.Models;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import lombok.Data;

import java.util.List;
import java.util.UUID;

@Data
@Entity
public class Restaurant {

    @Id
    @GeneratedValue
    private UUID id;
    private String name;
    @OneToMany(mappedBy = "restaurant")
    private List<Food> listOfItems;
}


Make sure to include the @Data and @Entity annotations. @Data allows Lombok to automatically generate getters, setters, and other boilerplate code, while @Entity tells JPA to treat the class as a database entity.

You also might see that we use the Food class here, so we'll need to create it as well in our models folder. It is going to look like this:


// Models folder
// Food.java
package codingHeaven.microservices.orderService.Models;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.Data;

import java.util.UUID;

@Data
@Entity
public class Food {

    @Id
    @GeneratedValue
    private UUID id;
    private String name;
    private double price;
    @ManyToOne
    @JoinColumn(name = "restaurant_id")
    @JsonIgnore
    private Restaurant restaurant;
}


We have a similar concept here as well.
Our last step is to create a RestaurantRepository.java file in our repository folder and place the following code inside:



// RestaurantRepository.java
package codingHeaven.microservices.orderService.Repositories;

import codingHeaven.microservices.orderService.Models.Restaurant;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;

public interface RestaurantRepository extends JpaRepository <Restaurant, UUID> {}


As you can see, we didn't write any code here, and that's one of the best parts of the JPA repository pattern. Most common methods are already provided by the JPA library, so we don't need to implement them ourselves.

If you need more sophisticated queries, you can create those as well, but for our project, we won't need them


Summary

So, we've created the complete setup for our Restaurant API, including the API, Service, and Data layers. We use it to retrieve the list of restaurants, and now we can follow the same approach for our Order API.

Creating Order API, Service and Data layer

The Order API will be responsible for creating orders, and we will also create an endpoint to retrieve the list of orders. This endpoint won't be intended for users but rather for us, for testing purposes during development.

Creating Order API

We start similarly by creating a new Java class in the API folder called OrderController.java. We define two endpoints: createOrder() and getOrders().


package codingHeaven.microservices.orderService.API;

import codingHeaven.microservices.orderService.Models.Order;
import codingHeaven.microservices.orderService.Services.OrderService;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/orders")
public class OrderController {

    private final OrderService orderService;

    public OrderController (OrderService service){
        this.orderService = service;
    }
    @PostMapping
    public boolean CreateOrder(@RequestBody Order order){

        return orderService.postOrder(order);
    }

    @GetMapping
    public List<Order> getOrders(){
        return orderService.getOrders();
    }
}



Our base route here is /orders, and we also declare a variable of type OrderService. Now we can go ahead and create the service and its interface, where we will define two methods for retrieving and saving data through our repository layer.

Creating Service Layer

In the service folder, we're going to create two files: IOrderService.java and OrderService.java. The first is an interface, and the second is the class that implements it. Inside IOrderService.java, place the following code:


//IOrderService.java
package codingHeaven.microservices.orderService.Services;
import codingHeaven.microservices.orderService.Models.Order;
import java.util.List;

public interface IOrderService {

    public boolean postOrder(Order order);
    public List<Order> getOrders();

}


and inside OrderService.java:


//OrderService.java

package codingHeaven.microservices.orderService.Services;

import codingHeaven.microservices.orderService.Events.OrderProducer;
import codingHeaven.microservices.orderService.Models.Order;
import codingHeaven.microservices.orderService.Repositories.OrderRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class OrderService {

    private final OrderRepository orderRepo;

    public OrderService(OrderRepository repo,
                        OrderProducer producer){
        this.orderRepo = repo;
        this.orderProducer = producer;
    }

    public List<Order> getOrders() {

        return orderRepo.findAll();
    }

    public boolean postOrder(Order order){

        try{
            orderRepo.save(order);
            return true;
        }
        catch (Exception ex){
            System.out.println(ex.getMessage());
            return false;
        }
    }
}


So, we receive the Order object from our controller, which gets it from the request body sent by the front end, and then we save it to our database. However, we don't have the Order.java class or the repository yet. This will be our next step.

Creating Data Layer

In our Models folder, create a new file named Order.java. Inside it, we are going to add the following code:



// Order.java
package codingHeaven.microservices.orderService.Models;

import jakarta.persistence.*;
import lombok.Data;
import java.util.List;

import java.util.UUID;

@Data
@Entity
@Table(name = "orders")

public class Order {

    @Id
    @GeneratedValue
    private UUID id;

    private String userFullName;

    private String deliveryAddress;

    private String restaurantName;

    @OneToMany(cascade = CascadeType.ALL)
    private List<Food> orderItems;
    @Override
    public String toString() {

        StringBuilder items = new StringBuilder();

        if (orderItems != null && !orderItems.isEmpty()) {
            for (Food food : orderItems) {
                items.append(food.getName())
                        .append(" ($")
                        .append(food.getPrice())
                        .append("), ");
            }

            // Remove the last ", "
            items.setLength(items.length() - 2);
        }

        return "Order {" +
                "id=" + id +
                ", user='" + userFullName + '\'' +
                ", address='" + deliveryAddress + '\'' +
                ", restaurant='" + restaurantName + '\'' +
                ", items=[" + items + "]" +
                '}';
    }

}


We override Java's toString() method so that we can use it in our service bus as a message. This allows our other project to receive it as a string. Now, let's create the repository. In our repositories folder, create an OrderRepository.java file and add the following code:


package codingHeaven.microservices.orderService.Repositories;

import codingHeaven.microservices.orderService.Models.Order;
import codingHeaven.microservices.orderService.Models.Restaurant;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.UUID;

public interface OrderRepository extends JpaRepository<Order, UUID> {
}


Again, this is pretty simple and straightforward, with no custom queries needed. Now that our core project functionality is ready, we can move on to setting up our message broker and sending our first message.

Setting up RabitMQ and configure our producer

Our first step is to create a new folder named Events. Inside it, we are going to add two files: RabbitMQConfig.java and OrderProducer.java.
The RabbitMQConfig class is responsible for setting up our message broker configuration, and it will look like this:



package codingHeaven.microservices.orderService.Events;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    // receive the message and decide where will it go
    public static final String EXCHANGE = "order.exchange";
    // place where messages stored, and waiting to be picked up by other service
    public static final String QUEUE = "order.queue";
    // this is like label or address for the message
    public static final String ROUTING_KEY = "order.created";

    @Bean
    public TopicExchange exchange() {
        return new TopicExchange(EXCHANGE);
    }

    @Bean
    public Queue queue() {
        return new Queue(QUEUE);
    }

    @Bean
    public Binding binding(Queue queue, TopicExchange exchange) {
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with(ROUTING_KEY);
    }
}


Basically, what we are doing here is telling Spring that this is a configuration class using the @Configuration annotation. This means it should be created when the application starts. The binding method simply connects everything together, but this class only contains configuration, so there is no business logic here. We are going to set up our producer in another file called OrderProducer.java.


package codingHeaven.microservices.orderService.Events;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderProducer {

    private final RabbitTemplate rabbitTemplate;

    public OrderProducer(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    public void sendOrderCreated(String message) {
        rabbitTemplate.convertAndSend(
                RabbitMQConfig.EXCHANGE,
                RabbitMQConfig.ROUTING_KEY,
                message
        );
    }
}

We use another example of dependency injection, but this time we are using the Spring Boot dependency that we imported. We create a method called sendOrderCreated, where we actually send our message.

Do you remember our Order toString() method? This is the reason we converted our object to a string, because the sendOrderCreated method in RabbitMQ accepts a string parameter that will be sent to the broker.


Now, we will go back and update our OrderService.java.




@Service
public class OrderService {

    private final OrderRepository orderRepo;
    private final OrderProducer orderProducer;

    public OrderService(OrderRepository repo,
                        OrderProducer producer){
        this.orderRepo = repo;
        this.orderProducer = producer;
    }

    public List<Order> getOrders() {

        return orderRepo.findAll();
    }

    public boolean postOrder(Order order){

        try{
            orderRepo.save(order);
            orderProducer.sendOrderCreated(order.toString());
            return true;
        }
        catch (Exception ex){
            System.out.println(ex.getMessage());
            return false;
        }
    }
}



We added an OrderProducer property and used it inside the postOrder method. We take the order from our controller, convert it into a string, and then send it to RabbitMQ. And that's it, our producer is ready.

The last step is to create our initial data and run the application.
Inside the resources folder, create a data.sql file and place the following code inside:


INSERT INTO restaurant (id, name) VALUES
('11111111-1111-1111-1111-111111111111', 'Pizza Palace'),
('22222222-2222-2222-2222-222222222222', 'Burger House');

INSERT INTO food (id, name, price, restaurant_id) VALUES
-- Pizza Palace
('aaaaaaa1-1111-1111-1111-111111111111', 'Pepperoni Pizza', 12.99, '11111111-1111-1111-1111-111111111111'),
('aaaaaaa2-1111-1111-1111-111111111111', 'Cheese Pizza', 10.99, '11111111-1111-1111-1111-111111111111'),
('aaaaaaa3-1111-1111-1111-111111111111', 'Veggie Pizza', 11.49, '11111111-1111-1111-1111-111111111111'),

-- Burger House
('bbbbbbb1-2222-2222-2222-222222222222', 'Classic Burger', 9.99, '22222222-2222-2222-2222-222222222222'),
('bbbbbbb2-2222-2222-2222-222222222222', 'Cheeseburger', 10.99, '22222222-2222-2222-2222-222222222222'),
('bbbbbbb3-2222-2222-2222-222222222222', 'Chicken Burger', 11.99, '22222222-2222-2222-2222-222222222222');


And update the application.properties file with the following:


spring.application.name=orderService
spring.jpa.hibernate.ddl-auto=update
spring.sql.init.mode=always
spring.jpa.defer-datasource-initialization=true


spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

Run the app

Now, we are going to start our RabbitMQ Docker container and run our application. Make sure Docker is installed and running on your machine. Open the terminal and execute the following command:



docker run -d \ 
  --name rabbitmq \ 
  -p 5672:5672 \ 
  -p 15672:15672 \ 
  rabbitmq:3-management 


This will start RabbitMQ. Use the following login credentials:


username: guest  
password: guest 

You can also run your Java application. If everything is working correctly, you should see the JSON data for the restaurants. I am going to use Postman to call my localhost endpoint and create an order.

This is our JSON body that I am going to use for our post endpoint


{
  "restaurantName": "Pizza Palace",
  "userFullName": "John Smith",
  "deliveryAddress": "123 Main Street, Raleigh, NC",
  "orderItems": [
    {
      "name": "Margherita Pizza",
      "quantity": 1,
      "price": 12.99
    },
    {
      "name": "Coke",
      "quantity": 2,
      "price": 2.50
    }
  ]
}


API fetch
Now we can see that the order has been received, and RabbitMQ has the message as well.


API fetch
In our next article, we are going to create a Consumer that will read messages from the RabbitMQ queue, process them, and publish its own message.
Thank you for reading, and Happy Coding! 🚀

Comments