{"id":46803,"date":"2024-06-05T00:00:00","date_gmt":"2024-06-05T07:00:00","guid":{"rendered":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/"},"modified":"2025-11-13T12:57:01","modified_gmt":"2025-11-13T20:57:01","slug":"creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot","status":"publish","type":"post","link":"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/","title":{"rendered":"Creating a Dynamic Recipe and Ingredient Search Platform using Spring Boot"},"content":{"rendered":"<p><style>\ntable, thead, th, tr, td {\n border: 1px solid;\n}<\/p>\n<\/style>\n<p>In today&#8217;s busy world, making tasty meals at home can be hard. We all want different foods, but looking through cookbooks or lots of recipe websites can take a long time and be annoying. What if there was a better way to find new recipes based on what you already have or what you feel like eating?<\/p>\n<p>This blog dives into the development of a dynamic recipe and ingredient search platform built with Java Spring Boot and a NoSQL database. We&#8217;ll delve into the specifics of crafting a user-friendly experience that empowers you to discover exciting recipes by title or utilize the ingredients you already have in your pantry.<\/p>\n<p>Get ready to explore the technical aspects of building this powerful search engine for the kitchen. We&#8217;ll navigate the world of Spring Boot, a robust framework for web applications, and discover the flexibility offered by NoSQL databases in handling recipe data.<\/p>\n<h3>Requirements<\/h3>\n<p>We will have the following functional requirements:<\/p>\n<ol>\n<li>Import recipes and ingredients from CSV<\/li>\n<li>View and search recipes and ingredients<\/li>\n<\/ol>\n<p>The out of scope:<br \/>\n* User authentication and authorization<br \/>\n* User management (register, login, etc)<\/p>\n<h2>Project Setup<\/h2>\n<h3>What you need to install<\/h3>\n<ul>\n<li><a href=\"https:\/\/jdk.java.net\/21\/\">Java 17 or later<\/a>, <a href=\"https:\/\/maven.apache.org\/download.cgi\">Maven 3.5+<\/a>, <a href=\"https:\/\/docs.docker.com\/engine\/install\/\">Docker engine<\/a>, and your favorite text editor (<a href=\"https:\/\/spring.io\/guides\/gs\/intellij-idea\/\">Intellij IDEA<\/a>, or <a href=\"https:\/\/spring.io\/guides\/gs\/guides-with-vscode\/\">VSCode<\/a>)<\/li>\n<\/ul>\n<h3>Create a Spring Boot Project<\/h3>\n<p>Spring boot offers a fast way to build applications.<br \/>\nSpring Boot does not generate code to make edits to your files. Instead, when you start your application, Spring Boot dynamically wires up beans and settings and applies them to your application context.<br \/>\nWith Spring Boot, we can focus more on the business features and less on the infrastructure.<\/p>\n<p>Navigate to <a href=\"https:\/\/start.spring.io\/\">start.spring.io<\/a>. This service pulls in all the dependencies you need for an application and does most of the setup. Click generate, it will generate the Spring Boot project and download it as a zip. Now unzip this project and import it into any IDE.<\/p>\n<p>To interact with GridDB, we need to add a GridDB Java Client to this project. Add the following dependency into maven <code>pom.xml<\/code>.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-bash\">&lt;dependency&gt;\n  &lt;groupId&gt;com.github.griddb&lt;\/groupId&gt;\n  &lt;artifactId&gt;gridstore&lt;\/artifactId&gt;\n  &lt;version&gt;5.3.0&lt;\/version&gt;\n&lt;\/dependency&gt;\n&lt;dependency&gt;\n  &lt;groupId&gt;com.github.f4b6a3&lt;\/groupId&gt;\n  &lt;artifactId&gt;tsid-creator&lt;\/artifactId&gt;\n  &lt;version&gt;5.2.5&lt;\/version&gt;\n&lt;\/dependency&gt;\n&lt;dependency&gt;\n  &lt;groupId&gt;de.siegmar&lt;\/groupId&gt;\n  &lt;artifactId&gt;fastcsv&lt;\/artifactId&gt;\n  &lt;version&gt;3.1.0&lt;\/version&gt;\n&lt;\/dependency&gt;<\/code><\/pre>\n<\/div>\n<h2>Data Modeling<\/h2>\n<p>To support the search features mentioned, we can design a database schema with two main tables: one for storing recipes and another for storing ingredients. Here&#8217;s a suggested database design:<\/p>\n<h3>\u25c9 <code>Recipes Table<\/code><\/h3>\n<p>This table will store information about each ingredient, including its unique identifier, name, description, and any other relevant details.<br \/>\nUsers can search for recipe by querying this table based on recipe name.<\/p>\n<table>\n<thead>\n<tr>\n<th><code>Label<\/code><\/th>\n<th><code>Name<\/code><\/th>\n<th><code>Type<\/code><\/th>\n<th><code>Nullable<\/code><\/th>\n<th><code>Default<\/code><\/th>\n<th><code>Comment<\/code><\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Recipe ID<\/td>\n<td>id<\/td>\n<td>varchar<\/td>\n<td><code>No<\/code><\/td>\n<td><\/td>\n<td>System generated unique identifier<\/td>\n<\/tr>\n<tr>\n<td>Recipe Name<\/td>\n<td>name<\/td>\n<td>varchar<\/td>\n<td><code>No<\/code><\/td>\n<td><\/td>\n<td>Recipe Name<\/td>\n<\/tr>\n<tr>\n<td>Recipe description<\/td>\n<td>description<\/td>\n<td>varchar<\/td>\n<td><code>Yes<\/code><\/td>\n<td>&#8221;<\/td>\n<td>Description, ex: direction<\/td>\n<\/tr>\n<tr>\n<td>Create Time<\/td>\n<td>createdAt<\/td>\n<td>datetime<\/td>\n<td><code>No<\/code><\/td>\n<td>current_timestamp()<\/td>\n<td>Created Time<\/td>\n<\/tr>\n<tr>\n<td>Created By<\/td>\n<td>userId<\/td>\n<td>varchar<\/td>\n<td><code>No<\/code><\/td>\n<td>&#8221;<\/td>\n<td>Created By User<\/td>\n<\/tr>\n<tr>\n<td>Recipe Image<\/td>\n<td>image<\/td>\n<td>blob<\/td>\n<td><code>Yes<\/code><\/td>\n<td>&#8221;<\/td>\n<td>Blob image<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>\u25c9 <code>Ingredients Table<\/code><\/h3>\n<p>This table will store information about each recipe, including its unique identifier, name, and measurement.<br \/>\nEach record in this table will represent a specific ingredient used in a specific recipe.<br \/>\nUsers can search for recipes based on the ingredients they have on hand by querying this table based on ingredient name.<\/p>\n<table>\n<thead>\n<tr>\n<th><code>Label<\/code><\/th>\n<th><code>Name<\/code><\/th>\n<th><code>Type<\/code><\/th>\n<th><code>Nullable<\/code><\/th>\n<th><code>Default<\/code><\/th>\n<th><code>Comment<\/code><\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Ingredient ID<\/td>\n<td>id<\/td>\n<td>varchar<\/td>\n<td><code>No<\/code><\/td>\n<td><\/td>\n<td>System generated unique identifier<\/td>\n<\/tr>\n<tr>\n<td>Ingredient Name<\/td>\n<td>name<\/td>\n<td>varchar<\/td>\n<td><code>No<\/code><\/td>\n<td><\/td>\n<td>Name<\/td>\n<\/tr>\n<tr>\n<td>Ingredient measurement<\/td>\n<td>measurement<\/td>\n<td>varchar<\/td>\n<td><code>Yes<\/code><\/td>\n<td>&#8221;<\/td>\n<td>Measurement of the ingredient<\/td>\n<\/tr>\n<tr>\n<td>Create Time<\/td>\n<td>createdAt<\/td>\n<td>datetime<\/td>\n<td><code>No<\/code><\/td>\n<td>current_timestamp()<\/td>\n<td>Created Time<\/td>\n<\/tr>\n<tr>\n<td>Recipe Id<\/td>\n<td>recipeId<\/td>\n<td>varchar<\/td>\n<td><code>No<\/code><\/td>\n<td>&#8221;<\/td>\n<td>Belong to a Recipe<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Building the Web Application<\/h2>\n<h3>Data access with GridDB<\/h3>\n<p>First, we create Java POJO classes that represent the underlying table or container in GridDB.<br \/>\nWe annotate a class with Lombok @Data, which automatically generates getters for all fields, a useful toString method, and hashCode and equals implementations that check all non-transient fields. Also generate setters for all non-final fields, as well as a constructor.<\/p>\n<p>We will create the data access class according to the previous database design.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Data\npublic class User {\n    @RowKey\n    String id;\n    String email;\n    String fullName;\n    Date createdAt;\n}\n\n@Data\npublic class Recipe {\n    @RowKey\n    String id;\n    String name;\n    String description;\n    Blob image;\n    String userId;\n    Date createdAt;\n}\n\n@Data\npublic class Ingredient {\n    @RowKey\n    String id;\n    String name;\n    String measurement;\n    String recipeId;\n    Date createdAt;\n}<\/code><\/pre>\n<\/div>\n<p>Next, we create the <code>GridDBConfig<\/code> class as a central configuration for database operation.<br \/>\nThe class will do the following:<br \/>\n* Read environment variables for connecting to the GridDB database<br \/>\n* Create a GridStore class for managing database connection to the GridDB instance<br \/>\n* Create GridDB Collection&#8217;s container (Table) to manage a set of rows. The container is a rough equivalent of the table in a relational database.<br \/>\n* On creating\/updating the Collection we specify the name and object corresponding to the column layout of the collection.<br \/>\n  Also for each collection, we add an index for a column that is frequently searched and used in the condition of the WHERE section of TQL.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Configuration\npublic class GridDBConfig {\n\n  @Value(\"${GRIDDB_NOTIFICATION_MEMBER}\")\n  private String notificationMember;\n\n  @Value(\"${GRIDDB_CLUSTER_NAME}\")\n  private String clusterName;\n\n  @Value(\"${GRIDDB_USER}\")\n  private String user;\n\n  @Value(\"${GRIDDB_PASSWORD}\")\n  private String password;\n\n  @Bean\n  public GridStore gridStore() throws GSException {\n    \/\/ Acquiring a GridStore instance\n    Properties properties = new Properties();\n    properties.setProperty(\"notificationMember\", notificationMember);\n    properties.setProperty(\"clusterName\", clusterName);\n    properties.setProperty(\"user\", user);\n    properties.setProperty(\"password\", password);\n    GridStore store = GridStoreFactory.getInstance().getGridStore(properties);\n    return store;\n  }\n\n  @Bean\n    public Collection<String, User> userCollection(GridStore gridStore) throws GSException {\n        Collection<String, User> collection = gridStore.putCollection(\"users\", User.class);\n        collection.createIndex(\"email\");\n        return collection;\n    }\n\n    @Bean\n    public Collection<String, Recipe> recipeCollection(GridStore gridStore) throws GSException {\n        gridStore.dropCollection(AppConstant.RECIPES_CONTAINER);\n        Collection<String, Recipe> collection = gridStore.putCollection(AppConstant.RECIPES_CONTAINER, Recipe.class);\n        collection.createIndex(\"name\");\n        return collection;\n    }\n\n    @Bean\n    public Collection<String, Ingredient> ingredientCollection(GridStore gridStore) throws GSException {\n        gridStore.dropCollection(AppConstant.INGREDIENTS_CONTAINER);\n        Collection<String, Ingredient> collection =\n                gridStore.putCollection(AppConstant.INGREDIENTS_CONTAINER, Ingredient.class);\n        collection.createIndex(\"name\");\n        return collection;\n    }\n}<\/code><\/pre>\n<\/div>\n<h3>Seed recipes from CSV<\/h3>\n<p>Here is the CSV format we want to import:<\/p>\n<table>\n<thead>\n<tr>\n<th>Title<\/th>\n<th>Directions<\/th>\n<th>Quantity1<\/th>\n<th>Unit1<\/th>\n<th>Ingredient1<\/th>\n<th>Quantity2<\/th>\n<th>Unit2<\/th>\n<th>Ingredient2<\/th>\n<th>Quantity3<\/th>\n<th>Unit3<\/th>\n<th>Ingredient3<\/th>\n<th>Quantity4<\/th>\n<th>Unit4<\/th>\n<th>Ingredient4<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Baked Green Beans<\/td>\n<td><\/td>\n<td>1<\/td>\n<td>pound<\/td>\n<td>fresh green beans<\/td>\n<td>2-Jan<\/td>\n<td>cup<\/td>\n<td>water<\/td>\n<td>1<\/td>\n<td>tsp<\/td>\n<td>onion, minced<\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td>Egg Roll Filling<\/td>\n<td><\/td>\n<td>16<\/td>\n<td>ounces<\/td>\n<td>shrimp<\/td>\n<td>3<\/td>\n<td>tbsp<\/td>\n<td>oil<\/td>\n<td>2<\/td>\n<td>cups<\/td>\n<td>diced celery<\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4>Extract ingredients<\/h4>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">for (final NamedCsvRecord csvRecord : namedCsv) {\n  List<CreateIngredientRequest> ingredients = new ArrayList<>(19);\n  for (int idxOfIngredient = 1; idxOfIngredient <= 19; idxOfIngredient++) {\n      if (csvRecord.getField(\"Quantity\" + idxOfIngredient).isBlank()) {\n          break;\n      }\n      String quantityStr = csvRecord.getField(\"Quantity\" + idxOfIngredient);\n      String unitOfMeasure = csvRecord.getField(\"Unit\" + idxOfIngredient);\n      String ingredientName = csvRecord.getField(\"Ingredient\" + idxOfIngredient);\n      ingredients.add(CreateIngredientRequest.builder()\n              .name(ingredientName)\n              .measurement(quantityStr + \" \" + unitOfMeasure)\n              .build());\n  }\n}<\/code><\/pre>\n<\/div>\n<p>This code snippet creates a list of CreateIngredientRequest objects called ingredients. It iterates over a range of numbers from 1 to 19 and checks if a specific field in the csvRecord object is blank. If it is not blank, it extracts the quantity, unit of measure, and ingredient name from the csvRecord object and adds a new CreateIngredientRequest object to the ingredients list.<\/p>\n<h4>Extract &amp; Save Recipe<\/h4>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">for (final NamedCsvRecord csvRecord : namedCsv) {\n  List<String> fields = csvRecord.getFields();\n  String recipeName = fields.get(0);\n  String recipeDesc = fields.get(1);\n  try {\n      User user = userService.getRandomUser();\n      recipeService.createWithIngredients(\n              user,\n              CreateRecipeRequest.builder()\n                      .name(recipeName)\n                      .description(recipeDesc)\n                      .build(),\n              ingredients);\n  } catch (GSException e) {\n      log.info(\"Recipe: {} = {}\", recipeName, recipeDesc);\n      ingredients.forEach(ingredient -> {\n          log.info(\"{}\", ingredient);\n      });\n      e.printStackTrace();\n  }\n}<\/code><\/pre>\n<\/div>\n<p>Here's a breakdown of what it does:<\/p>\n<ol>\n<li>It retrieves the fields from the current CSV record using csvRecord.getFields().<\/li>\n<li>It assigns the first field to recipeName and the second field to recipeDesc.<\/li>\n<li>It calls userService.getRandomUser() to get a random user.<\/li>\n<li>It creates a new CreateRecipeRequest using the recipeName and recipeDesc.<\/li>\n<li>It calls recipeService.createWithIngredients() to create a recipe with the given user, request, and ingredients.<\/li>\n<li>This code assumes that csvRecord, userService, recipeService, and ingredients are properly initialized and accessible in the surrounding code.<\/li>\n<\/ol>\n<h3>Search recipe's code<\/h3>\n<h4>Search recipe UI<\/h4>\n<div class=\"clipboard\">\n<pre><code class=\"language-html\">&lt;div class=\"my-3\"&gt;\n    &lt;h2 class=\"text-center\"&gt;Recipes&lt;\/h2&gt;\n    &lt;\/p&gt;\n    &lt;form method=\"get\" th_action=\"@{\/recipes}\"&gt;\n        &lt;input name=\"keyword\" placeholder=\"Find by recipe name\" size=\"30\" th_value=\"${keyword}\" type=\"text\"&gt;\n        &lt;input name=\"keywordIngredient\" placeholder=\"Find by ingredient\" size=\"30\" th_value=\"${keywordIngredient}\" type=\"text\"&gt;\n        &lt;button class=\"btn btn-info\" type=\"submit\"&gt;Search&lt;\/button&gt;\n        &lt;a th_href=\"@{\/recipes}\" class=\"btn btn-info\"&gt;Reset&lt;\/a&gt;\n    &lt;\/form&gt;\n&lt;\/div&gt;<\/code><\/pre>\n<\/div>\n<p>This bit of HTML code shows a form with two places to type things and two buttons.<br \/>\nThe form uses the <code>get<\/code> method and goes to <code>\/recipes<\/code> when it's sent. When you click the button, it sends a message to the <code>\/recipes<\/code> place asking for information.<br \/>\nIn the first box, you can type a keyword like a recipe name. It's got a space to type in, and what you type is connected to the <code>${keyword}<\/code> thing. So, if you type something, it'll look for that thing.<br \/>\nThe second box is for ingredients. You can type in an ingredient, and it's connected to the <code>${keywordIngredient}<\/code> thing.<br \/>\nThe first button is a <code>submit<\/code> button. When you click it, it sends the form.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-html\">&lt;div th_if=\"${recipes.size() &gt; 0}\" class=\"container\"&gt;\n  &lt;h5 class=\"m-0 font-weight-bold\"&gt;Search result&lt;\/h6&gt;\n  &lt;table class=\"table table-hover table-responsive-xl table-striped\"&gt;\n      &lt;thead class=\"thead-light\"&gt;\n          &lt;tr&gt;\n              &lt;th scope=\"col\"&gt;Name&lt;\/th&gt;\n              &lt;th scope=\"col\"&gt;Description&lt;\/th&gt;\n              &lt;th scope=\"col\"&gt;Created At&lt;\/th&gt;\n          &lt;\/tr&gt;\n      &lt;\/thead&gt;\n      &lt;tbody class=\"table-group-divider\"&gt;\n          &lt;tr th_each=\"recipe : ${recipes}\"&gt;\n              &lt;td&gt;\n                  &lt;a class=\"small\" th_href=\"@{\/recipes\/{id}(id=${recipe.id})}\" th_text=\"${recipe.name}\"&gt;&lt;\/a&gt;\n              &lt;\/td&gt;\n              &lt;th scope=\"row\"&gt;[[${recipe.description}]]&lt;\/th&gt;\n              &lt;th id=\"createdAt\" scope=\"row\"&gt;[[${recipe.createdAt}]]&lt;\/th&gt;\n          &lt;\/tr&gt;\n      &lt;\/tbody&gt;\n  &lt;\/table&gt;\n&lt;\/div&gt;<\/code><\/pre>\n<\/div>\n<p>This piece of code is like a pattern for showing recipe search results on a webpage. First, it checks if there are any recipes to show. If there are, it arranges them nicely in a table. Each row in the table tells you the name of the recipe, what it's about, and when it was made. The <code>th:each<\/code> part goes through the list of recipes and shows each recipe's info in a row.<\/p>\n<h4>Search recipe's Web Controller<\/h4>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Controller\n@RequestMapping(\"\/recipes\")\n@RequiredArgsConstructor\npublic class RecipesController {\n  private final RecipeService recipeService;\n\n  @GetMapping\n  String recipes(Model model, String keyword, String keywordIngredient) {\n      List<Recipe> recipes = recipeService.fetchAll(keyword, keywordIngredient);\n      model.addAttribute(\"recipes\", recipes);\n      if (keywordIngredient != null && !keywordIngredient.isBlank()) {\n          model.addAttribute(\"keywordIngredient\", keywordIngredient);\n      } else {\n          model.addAttribute(\"keyword\", keyword);\n      }\n      return \"recipes\";\n  }\n}<\/code><\/pre>\n<\/div>\n<p><code>RecipesController<\/code> will receive data submitted by the search form. Then fetches all recipes from the database, optionally filtering by keyword and\/or keywordIngredient.<\/p>\n<h4>Search recipe's Service Layer<\/h4>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">public List<Recipe> fetchAll(String searchRecipe, String searchIngredient) {\n        List<Recipe> recipes = new ArrayList<>(0);\n        try {\n            if (searchIngredient != null && !searchIngredient.isBlank()) {\n                log.info(\"Search by ingredient: {}\", searchIngredient.toLowerCase());\n                Query<Ingredient> queryIngredient = ingredientCollection.query(\n                        \"SELECT * FROM \" + AppConstant.INGREDIENTS_CONTAINER + \" WHERE LOWER(name) LIKE '%\"\n                                + searchIngredient.toLowerCase() + \"%'\",\n                        Ingredient.class);\n                RowSet<Ingredient> rowIngredientSet = queryIngredient.fetch();\n                while (rowIngredientSet.hasNext()) {\n                    Recipe recipe = fetchOne(rowIngredientSet.next().getRecipeId());\n                    if (recipe != null) {\n                        recipes.add(recipe);\n                    }\n                }\n                return recipes;\n            }\n\n            String tql = \"SELECT * \";\n            if (searchRecipe != null && !searchRecipe.isBlank()) {\n                log.info(\"Search by recipe:{}\", searchRecipe.toLowerCase());\n                tql = \"SELECT * FROM \" + AppConstant.RECIPES_CONTAINER + \" WHERE LOWER(name) LIKE '%\"\n                        + searchRecipe.toLowerCase() + \"%'\";\n            }\n\n            Query<Recipe> query = recipeCollection.query(tql, Recipe.class);\n            RowSet<Recipe> rowSet = query.fetch();\n            while (rowSet.hasNext()) {\n                recipes.add(rowSet.next());\n            }\n        } catch (GSException e) {\n            log.error(\"Error search recipes\", e);\n        }\n        return recipes;\n    }<\/code><\/pre>\n<\/div>\n<p>This piece of code defines a function called fetchAll. This function takes two things as input: searchRecipe and searchIngredient, both of which are just text (strings).<br \/>\nFirst, it checks if you provided anything in the searchIngredient part. If you did, it searches for ingredients that match what you typed in that field (ignoring case, so \"apple\" and \"Apple\" would be the same). It then looks for recipes that use those ingredients and adds them to a list.<br \/>\nIf you didn't provide anything for searchIngredient, it checks the searchRecipe part instead. Here, it searches for recipes that match what you typed in the recipe name field (again, ignoring the case). Any recipes it finds are added to the list.<br \/>\nIf anything goes wrong while searching, the function writes a message about the error.<br \/>\nIn the end, the function returns the list of recipes it found (it might be empty if nothing matches your search).<\/p>\n<h3>Running the Project with Docker Compose<\/h3>\n<p>To spin up the project we will utilize Docker, a popular container engine.<br \/>\nBuild the docker image using the following command:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">  docker compose -f docker-compose-dev.yml build<\/code><\/pre>\n<\/div>\n<p>Run the app:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">  docker compose -f docker-compose-dev.yml up<\/code><\/pre>\n<\/div>\n<p>The website ready at http:\/\/localhost:8080<\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2024\/06\/rsz_1_searchbyrecipe.png\"><img fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2024\/06\/rsz_1_searchbyrecipe.png\" alt=\"\" width=\"556\" height=\"434\" class=\"aligncenter size-full wp-image-30140\" srcset=\"\/wp-content\/uploads\/2024\/06\/rsz_1_searchbyrecipe.png 556w, \/wp-content\/uploads\/2024\/06\/rsz_1_searchbyrecipe-300x234.png 300w\" sizes=\"(max-width: 556px) 100vw, 556px\" \/><\/a><\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2024\/06\/rsz_3_view_recipe_ingredients.png\"><img decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2024\/06\/rsz_3_view_recipe_ingredients.png\" alt=\"\" width=\"548\" height=\"336\" class=\"aligncenter size-full wp-image-30141\" srcset=\"\/wp-content\/uploads\/2024\/06\/rsz_3_view_recipe_ingredients.png 548w, \/wp-content\/uploads\/2024\/06\/rsz_3_view_recipe_ingredients-300x184.png 300w\" sizes=\"(max-width: 548px) 100vw, 548px\" \/><\/a><\/p>\n<h2>Conclusion<\/h2>\n<p>In this blog post, we've explored the steps to create a dynamic recipe search platform. We saw how Spring Boot, a powerful framework for web applications, and GridDB, can join forces to create a user-friendly search experience.<\/p>\n<p>This is just the beginning, and you can extend this platform further. You could add features like user accounts, recipe ratings and reviews, or even integrate with external APIs to display nutritional information or recipe images.<br \/>\nThe entire code for the web application is available on <a href=\"https:\/\/github.com\/alifruliarso?tab=repositories\">Github<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In today&#8217;s busy world, making tasty meals at home can be hard. We all want different foods, but looking through cookbooks or lots of recipe websites can take a long time and be annoying. What if there was a better way to find new recipes based on what you already have or what you feel [&hellip;]<\/p>\n","protected":false},"author":41,"featured_media":30139,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[121],"tags":[],"class_list":["post-46803","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Creating a Dynamic Recipe and Ingredient Search Platform using Spring Boot | GridDB: Open Source Time Series Database for IoT<\/title>\n<meta name=\"description\" content=\"In today&#039;s busy world, making tasty meals at home can be hard. We all want different foods, but looking through cookbooks or lots of recipe websites can\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Creating a Dynamic Recipe and Ingredient Search Platform using Spring Boot | GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"og:description\" content=\"In today&#039;s busy world, making tasty meals at home can be hard. We all want different foods, but looking through cookbooks or lots of recipe websites can\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/\" \/>\n<meta property=\"og:site_name\" content=\"GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/griddbcommunity\/\" \/>\n<meta property=\"article:published_time\" content=\"2024-06-05T07:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-13T20:57:01+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.griddb.net\/wp-content\/uploads\/2024\/06\/recipe-finder-main-image1.jpeg\" \/>\n\t<meta property=\"og:image:width\" content=\"1018\" \/>\n\t<meta property=\"og:image:height\" content=\"861\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"griddb-admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:site\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"griddb-admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/\"},\"author\":{\"name\":\"griddb-admin\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\"},\"headline\":\"Creating a Dynamic Recipe and Ingredient Search Platform using Spring Boot\",\"datePublished\":\"2024-06-05T07:00:00+00:00\",\"dateModified\":\"2025-11-13T20:57:01+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/\"},\"wordCount\":1440,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2024\/06\/recipe-finder-main-image1.jpeg\",\"articleSection\":[\"Blog\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/\",\"url\":\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/\",\"name\":\"Creating a Dynamic Recipe and Ingredient Search Platform using Spring Boot | GridDB: Open Source Time Series Database for IoT\",\"isPartOf\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2024\/06\/recipe-finder-main-image1.jpeg\",\"datePublished\":\"2024-06-05T07:00:00+00:00\",\"dateModified\":\"2025-11-13T20:57:01+00:00\",\"description\":\"In today's busy world, making tasty meals at home can be hard. We all want different foods, but looking through cookbooks or lots of recipe websites can\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/#primaryimage\",\"url\":\"\/wp-content\/uploads\/2024\/06\/recipe-finder-main-image1.jpeg\",\"contentUrl\":\"\/wp-content\/uploads\/2024\/06\/recipe-finder-main-image1.jpeg\",\"width\":1018,\"height\":861},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.griddb.net\/en\/#website\",\"url\":\"https:\/\/www.griddb.net\/en\/\",\"name\":\"GridDB: Open Source Time Series Database for IoT\",\"description\":\"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL\",\"publisher\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.griddb.net\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\",\"name\":\"Fixstars\",\"url\":\"https:\/\/www.griddb.net\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"contentUrl\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"width\":200,\"height\":83,\"caption\":\"Fixstars\"},\"image\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/griddbcommunity\/\",\"https:\/\/x.com\/GridDBCommunity\",\"https:\/\/www.linkedin.com\/company\/griddb-by-toshiba\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\",\"name\":\"griddb-admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g\",\"caption\":\"griddb-admin\"},\"url\":\"https:\/\/www.griddb.net\/en\/author\/griddb-admin\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Creating a Dynamic Recipe and Ingredient Search Platform using Spring Boot | GridDB: Open Source Time Series Database for IoT","description":"In today's busy world, making tasty meals at home can be hard. We all want different foods, but looking through cookbooks or lots of recipe websites can","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/","og_locale":"en_US","og_type":"article","og_title":"Creating a Dynamic Recipe and Ingredient Search Platform using Spring Boot | GridDB: Open Source Time Series Database for IoT","og_description":"In today's busy world, making tasty meals at home can be hard. We all want different foods, but looking through cookbooks or lots of recipe websites can","og_url":"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/","og_site_name":"GridDB: Open Source Time Series Database for IoT","article_publisher":"https:\/\/www.facebook.com\/griddbcommunity\/","article_published_time":"2024-06-05T07:00:00+00:00","article_modified_time":"2025-11-13T20:57:01+00:00","og_image":[{"width":1018,"height":861,"url":"https:\/\/www.griddb.net\/wp-content\/uploads\/2024\/06\/recipe-finder-main-image1.jpeg","type":"image\/jpeg"}],"author":"griddb-admin","twitter_card":"summary_large_image","twitter_creator":"@GridDBCommunity","twitter_site":"@GridDBCommunity","twitter_misc":{"Written by":"griddb-admin","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/#article","isPartOf":{"@id":"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/"},"author":{"name":"griddb-admin","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233"},"headline":"Creating a Dynamic Recipe and Ingredient Search Platform using Spring Boot","datePublished":"2024-06-05T07:00:00+00:00","dateModified":"2025-11-13T20:57:01+00:00","mainEntityOfPage":{"@id":"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/"},"wordCount":1440,"commentCount":0,"publisher":{"@id":"https:\/\/www.griddb.net\/en\/#organization"},"image":{"@id":"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2024\/06\/recipe-finder-main-image1.jpeg","articleSection":["Blog"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/","url":"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/","name":"Creating a Dynamic Recipe and Ingredient Search Platform using Spring Boot | GridDB: Open Source Time Series Database for IoT","isPartOf":{"@id":"https:\/\/www.griddb.net\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/#primaryimage"},"image":{"@id":"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2024\/06\/recipe-finder-main-image1.jpeg","datePublished":"2024-06-05T07:00:00+00:00","dateModified":"2025-11-13T20:57:01+00:00","description":"In today's busy world, making tasty meals at home can be hard. We all want different foods, but looking through cookbooks or lots of recipe websites can","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.griddb.net\/en\/blog\/creating-a-dynamic-recipe-and-ingredient-search-platform-using-spring-boot\/#primaryimage","url":"\/wp-content\/uploads\/2024\/06\/recipe-finder-main-image1.jpeg","contentUrl":"\/wp-content\/uploads\/2024\/06\/recipe-finder-main-image1.jpeg","width":1018,"height":861},{"@type":"WebSite","@id":"https:\/\/www.griddb.net\/en\/#website","url":"https:\/\/www.griddb.net\/en\/","name":"GridDB: Open Source Time Series Database for IoT","description":"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL","publisher":{"@id":"https:\/\/www.griddb.net\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.griddb.net\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.griddb.net\/en\/#organization","name":"Fixstars","url":"https:\/\/www.griddb.net\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/","url":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","contentUrl":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","width":200,"height":83,"caption":"Fixstars"},"image":{"@id":"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/griddbcommunity\/","https:\/\/x.com\/GridDBCommunity","https:\/\/www.linkedin.com\/company\/griddb-by-toshiba"]},{"@type":"Person","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233","name":"griddb-admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g","caption":"griddb-admin"},"url":"https:\/\/www.griddb.net\/en\/author\/griddb-admin\/"}]}},"_links":{"self":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/46803","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/comments?post=46803"}],"version-history":[{"count":1,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/46803\/revisions"}],"predecessor-version":[{"id":51465,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/46803\/revisions\/51465"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/media\/30139"}],"wp:attachment":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/media?parent=46803"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/categories?post=46803"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/tags?post=46803"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}