{"id":46795,"date":"2024-03-27T00:00:00","date_gmt":"2024-03-27T07:00:00","guid":{"rendered":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/blog\/voting-web-platform\/"},"modified":"2025-11-13T12:56:55","modified_gmt":"2025-11-13T20:56:55","slug":"voting-web-platform","status":"publish","type":"post","link":"https:\/\/www.griddb.net\/en\/blog\/voting-web-platform\/","title":{"rendered":"Building a Blog Voting Web Platform using Spring Boot and GridDB"},"content":{"rendered":"<p>An online voting system is a software platform that allows people to cast their votes electronically with intuitive and secure online interfaces. These systems leverage cutting-edge technologies to facilitate the entire voting process. Online voting systems come in various forms, from secure web portals to mobile applications.<\/p>\n<p>Online voting systems shine in situations where accessibility is a challenge, enabling individuals who might face mobility issues or are geographically distant to participate seamlessly. Moreover, online voting systems are invaluable in large-scale elections, expediting the voting process and significantly reducing the time and resources required for manual counting.<\/p>\n<h2>How to Follow Along<\/h2>\n<p>You can grab the source code from our repo:<\/p>\n<p>`$ git clone https:\/\/github.com\/alifruliarso\/spring-boot-blog-voting.git &#8211;branch voting-system<\/p>\n<h3>Where is online voting most useful?<\/h3>\n<p>It&#8217;s a good idea to use an online voting system to:<\/p>\n<ul>\n<li>Vote on rules and regulations by policy decisions<\/li>\n<li>Select award nomination<\/li>\n<li>Gather anonymous feedback from employees.<\/li>\n<\/ul>\n<h2>What We&#8217;re Building<\/h2>\n<p>In this guide, we will embark on a journey to create a simple voting up and down system using Spring Boot, and NoSQL, and packaging the application into a Docker container. We aim to empower users to express their opinions on blog posts, fostering a vibrant community of discussion and feedback.<\/p>\n<h3>Requirements<\/h3>\n<p>Based on the previous overview, we will have the following functional requirements:<\/p>\n<ol>\n<li>Create a blog in the simple form<\/li>\n<li>Cast votes (upvote or downvote) on blog posts. No login\/logout, we will generate random users<\/li>\n<li>Visualize vote results in a static and real-time chart<\/li>\n<\/ol>\n<p>The out of scope: * Reverse\/change the votes * Change users * User management (register, login, etc) * Blog management (create, edit, delete)<\/p>\n<h3>The database<\/h3>\n<p>The choice between SQL and NoSQL databases depends on specific requirements. However, here are some reasons why NoSQL databases are preferred over SQL databases for this system:<\/p>\n<ol>\n<li>Horizontal scalability: NoSQL databases typically provide easy horizontal scalability.<\/li>\n<li>Query Structure: In this case, there is no need for complex relational operations such as table joins.<\/li>\n<li>NoSQL databases are typically built with a distributed and decentralized architecture, enabling easy scaling by adding more nodes to the cluster.<\/li>\n<\/ol>\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. Spring 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. With 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>. xml <dependency> <groupId>com.github.griddb<\/groupId> <artifactId>gridstore<\/artifactId> <version>5.3.0<\/version> <\/dependency><\/p>\n<h3>Database design<\/h3>\n<p>Now, let&#8217;s write down the database schema according to the functional requirements. We will have the following entity classes:<\/p>\n<ul>\n<li>\n<p>User<\/p>\n<p>This holds the user&#8217;s information in the system. It has the following attributes:<\/p>\n<ul>\n<li><code>String id<\/code>: System generated unique identifier. It is the primary key.<\/li>\n<li><code>String email<\/code><\/li>\n<li><code>String fullName<\/code><\/li>\n<\/ul>\n<\/li>\n<li>\n<p>Blog<\/p>\n<p>This entity stores the blog posts. It has the following attributes:<\/p>\n<ul>\n<li><code>String id<\/code>: System generated unique identifier. It is the primary key.<\/li>\n<li><code>String title<\/code>: The title of the blog post<\/li>\n<li><code>Integer voteUpCount<\/code>: The count of vote up<\/li>\n<li><code>Integer voteDownCount<\/code>: The count of votes down<\/li>\n<li><code>Date createdAt<\/code>: System-generated timestamp when the blog post created<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>VoteMetrics<\/p>\n<p>This entity captures the voting history. It has the following attributes:<\/p>\n<ul>\n<li><code>Date timestamp<\/code>: System generated. It is the primary key for the time series container (table).<\/li>\n<li><code>String blogId<\/code>: The blog ID voted by the user<\/li>\n<li><code>String userId<\/code>: The user ID makes voting<\/li>\n<li><code>Integer voteType<\/code>: The voting type, 1: Vote Up, 0: Vote Down <\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h4>Data access with GridDB<\/h4>\n<p>First, we create Java POJO classes that represent the underlying table or container in GridDB. We 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\">\n@Data\npublic class User {\n    @RowKey\n    String id;\n    String email;\n    String fullName;\n    Date createdAt;\n}\n\n@Data\npublic class Blog {\n    @RowKey\n    String id;\n    String title;\n    Integer voteUpCount;\n    Integer voteDownCount;\n    Date createdAt;\n}\n\n@Data\npublic class VoteMetrics {\n    @RowKey\n    Date timestamp;\n    String blogId;\n    String userId;\n    Integer voteType;\n}<\/code><\/pre>\n<\/div>\n<p>Next, we create the <code>GridDBConfig<\/code> class as a central configuration for database operation. The class will do the following: * Read environment variables for connecting to the GridDB database * Create a GridStore class for managing database connection to the GridDB instance * 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. * On creating\/updating the Collection we specify the name and object corresponding to the column layout of the collection. 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\">\n@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&lt;String, User> userCollection(GridStore gridStore) throws GSException {\n        Collection&lt;String, User> collection = gridStore.putCollection(\"users\", User.class);\n        collection.createIndex(\"email\");\n        return collection;\n    }\n\n    @Bean\n    public Collection&lt;String, Blog> blogCollection(GridStore gridStore) throws GSException {\n        Collection&lt;String, Blog> collection = gridStore.putCollection(\"blogs\", Blog.class);\n        collection.createIndex(\"title\");\n        return collection;\n    }\n\n    @Bean\n    public TimeSeries&lt;votemetrics> voteMetricContainer(GridStore gridStore) throws GSException {\n        TimeSeries&lt;\/votemetrics>&lt;votemetrics> timeSeries = gridStore.putTimeSeries(Constant.VOTEMETRICS_CONTAINER, VoteMetrics.class);\n        timeSeries.createIndex(\"blogId\");\n        timeSeries.createIndex(\"userId\");\n        return timeSeries;\n    }\n}&lt;\/votemetrics><\/code><\/pre>\n<\/div>\n<h4>Service class<\/h4>\n<p>Next, we create a service class with @Service annotation for handling business logic and convert the Entity class into DTO.<\/p>\n<p><code>BlogService.java<\/code> is responsible for finding, and creating blog posts in GridDB.<\/p>\n<ul>\n<li><code>create<\/code>: Creating a new blog.<\/li>\n<\/ul>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">\npublic Blog create(CreateBlogRequest createBlogRequest) {\n    Blog blog = new Blog();\n    blog.setId(KeyGenerator.next(\"bl\"));\n    blog.setTitle(createBlogRequest.getTitle());\n    blog.setVoteDownCount(0);\n    blog.setVoteUpCount(0);\n    blog.setCreatedAt(new Date());\n    try {\n        blogCollection.put(blog);\n        blogCollection.commit();\n    } catch (GSException e) {\n        log.error(\"Error create blog\", e);\n    }\n    return blog;\n}<\/code><\/pre>\n<\/div>\n<ul>\n<li><code>fetchAll<\/code>: Fetching all blog posts.<\/li>\n<\/ul>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">\n  public List&lt;blog> fetchAll() {\n      List&lt;\/blog>&lt;blog> blogs = new ArrayList&lt;>(0);\n      try {\n          Query&lt;\/blog>&lt;blog> query = blogCollection.query(\"SELECT *\", Blog.class);\n          RowSet&lt;\/blog>&lt;blog> rowSet = query.fetch();\n          while (rowSet.hasNext()) {\n              blogs.add(rowSet.next());\n          }\n      } catch (GSException e) {\n          log.error(\"Error fetchAll\", e);\n      }\n      return blogs;\n  }&lt;\/blog><\/code><\/pre>\n<\/div>\n<ul>\n<li><code>updateVoteUp<\/code>: to save the vote-up action. In this method, on fetching row, we use the <code>get(key, forUpdate)<\/code> method to lock the row we want to update making other update operations wait until the transaction completes or a timeout occurs.<\/li>\n<\/ul>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">\n  public void updateVoteUp(String blogId) throws GSException {\n      blogCollection.setAutoCommit(false);\n      Blog blog = blogCollection.get(blogId, true);\n      blog.setVoteUpCount(blog.getVoteUpCount() + 1);\n      blogCollection.put(blog);\n      blogCollection.commit();\n  }<\/code><\/pre>\n<\/div>\n<p>Example of vote metrics rows.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">\nVoteMetrics(timestamp=Sun Jan 21 03:48:10 GMT 2024, blogId=bl_0EWG3KRE8H95G, userId=us_0EWQES1BVRJAN, voteType=1)\nVoteMetrics(timestamp=Sun Jan 21 03:48:03 GMT 2024, blogId=bl_0EWG3KRDCHBEM, userId=us_0EWQER8KKRHDF, voteType=1)\nVoteMetrics(timestamp=Sun Jan 21 03:48:02 GMT 2024, blogId=bl_0EWQ49HPKRKWB, userId=us_0EWQER2KVRJCJ, voteType=1)\nVoteMetrics(timestamp=Sun Jan 21 03:48:00 GMT 2024, blogId=bl_0EWG3KRE8H95G, userId=us_0EWQEQV6FRKKV, voteType=1)<\/code><\/pre>\n<\/div>\n<h3>Serving Web Content with Spring MVC<\/h3>\n<p>Spring&#8217;s web MVC framework is, like many other web MVC frameworks, request-driven, designed around a central Servlet that dispatches requests to controllers and offers other functionality that facilitates the development of web applications. By using Spring MVC Framework we got the following advantages: * The Spring MVC separates each role, where the model object, controller, command object, view resolver, etc. can be fulfilled by a specialized object. * It uses a lightweight servlet container to develop and deploy your application. * It provides a robust configuration for both framework and application classes that includes easy referencing across contexts, such as from web controllers to business objects. * It provides the specific annotations that easily redirect the page.<\/p>\n<p>In this tutorial, we will follow a standard MVC architecture. We will have a controller (<code>VotesController<\/code> class), views (<code>votes.html<\/code> Thymeleaf template), and a model (a Java map object) for passing data into the view. Every method of the controller is mapped to a URI.<\/p>\n<h4>Votes page<\/h4>\n<p>In the following example, <code>VotesController<\/code>, method <code>votes<\/code> handles GET requests for <code>\/votes<\/code> by returning the name of a View (in this case, <code>votes<\/code>), also adding attribute <code>blogs<\/code> to <code>Model<\/code> via its <code>addAttribute<\/code> method.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">\n@Controller\n@RequestMapping(\"\/votes\")\npublic class VotesController {\n    \n  @GetMapping\n  String votes(Model model) {\n      List&lt;blog> blogs = blogService.fetchAll();\n      model.addAttribute(\"blogs\", blogs);\n      return \"votes\";\n  }\n\n  @GetMapping(\"\/up\/{id}\")\n    public String voteUp(@PathVariable(\"id\") String blogId, Model model, RedirectAttributes redirectAttributes) {\n        try {\n            String userId = KeyGenerator.next(\"us\");\n            voteService.voteUp(blogId, userId);\n            redirectAttributes.addFlashAttribute(\"message\", \"Voting successful!\");\n        } catch (Exception e) {\n            redirectAttributes.addFlashAttribute(\"message\", \"Oh no!\");\n        }\n        return \"redirect:\/votes\";\n    }\n}\n&lt;\/blog><\/code><\/pre>\n<\/div>\n<p>After creating the controller class, we need to define the template for the views to be generated. We are using <code>Thymeleaf<\/code>, a modern server-side Java template engine for both web and standalone environments. The HTML templates written in Thymeleaf still look and work like HTML.<\/p>\n<p>We define an HTML table for displaying a list of blog posts and use <code>th:each<\/code> tag attribute to iterate over a collection of blog posts, and <code>th:text<\/code> tag for displaying the value.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-html\">\n  &lt;tbody&gt;\n      &lt;tr th_each=\"blog : ${blogs}\"&gt;\n          &lt;th scope=\"row\"&gt;[[${blog.title}]]&lt;\/th&gt;\n          &lt;td&gt;\n              &lt;a th_href=\"@{'\/votes\/up\/' + ${blog.id}}\" title=\"Vote Up\" class=\"btn btn-outline-success\"\n                  role=\"button\"&gt;\n                  &lt;i class=\"fa fa-thumbs-up\"&gt;&lt;\/i&gt;\n                  &lt;span type=\"text\" th_text=\"${blog.voteUpCount}\" class=\"btn-label\"&gt;&lt;\/span&gt;\n              &lt;\/a&gt;\n          &lt;\/td&gt;\n          &lt;td&gt;\n              &lt;a th_href=\"@{'\/votes\/down\/' + ${blog.id}}\" title=\"Vote Down\" class=\"btn btn-outline-danger\"\n                  role=\"button\"&gt;\n                  &lt;i class=\"fa fa-thumbs-down\"&gt;&lt;\/i&gt;\n                  &lt;span type=\"text\" th_text=\"${blog.voteDownCount}\" class=\"btn-label\"&gt;&lt;\/span&gt;\n              &lt;\/a&gt;\n          &lt;\/td&gt;\n\n          &lt;td&gt;[[${blog.createdAt}]]&lt;\/td&gt;\n      &lt;\/tr&gt;\n  &lt;\/tbody&gt;<\/code><\/pre>\n<\/div>\n<p>Here is a preview of the votes page. Users should be able to click the <code>thumbs-up\/down<\/code> icon to vote a post up or down.<\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2024\/03\/votes_page.png\"><img fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2024\/03\/votes_page.png\" alt=\"\" width=\"1448\" height=\"795\" class=\"aligncenter size-full wp-image-30026\" srcset=\"\/wp-content\/uploads\/2024\/03\/votes_page.png 1448w, \/wp-content\/uploads\/2024\/03\/votes_page-300x165.png 300w, \/wp-content\/uploads\/2024\/03\/votes_page-1024x562.png 1024w, \/wp-content\/uploads\/2024\/03\/votes_page-768x422.png 768w, \/wp-content\/uploads\/2024\/03\/votes_page-600x329.png 600w\" sizes=\"(max-width: 1448px) 100vw, 1448px\" \/><\/a><\/p>\n<h4>Dashboard page<\/h4>\n<p>For the real-time dashboard, we use Server-Sent Event. With server-sent events, the server can send new data to a web page at any time, by pushing messages to the web page. These incoming messages can be treated as Events + data inside the web page.<\/p>\n<p>To open a connection to the server to begin receiving events from it, create a new EventSource object with the URL(<code>\/votes\/chart-data<\/code>) of a script that generates the events.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-javascript\">script\nconst ctx = document.getElementById('charts');\nconst voteChart = new Chart(ctx, config);\n\/* A small decorator for the JavaScript EventSource API that automatically reconnects *\/\nconst eventSource = new ReconnectingEventSource(\"\/votes\/chart-data\");<\/code><\/pre>\n<\/div>\n<p>Here the EventSource listens for incoming message events and updates the chart dataset.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-javascript\">script\neventSource.onmessage = function (event) {\n    console.log(\"Received event: \" + event.data);\n    const data = JSON.parse(event.data);\n    config.data.labels.splice(0, config.data.labels.length, ...data.map(row => row.label));\n    config.data.datasets[0].data.splice(0, config.data.datasets[0].data.length, ...data.map(row => row.count));\n    voteChart.update();\n};<\/code><\/pre>\n<\/div>\n<p>The server-side script that sends events must respond using the MIME-type text\/event-stream. Each notification is sent as a block of text terminated by a pair of newlines.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">\n@GetMapping(\"\/chart-data\")\npublic SseEmitter streamSseMvc(@RequestHeader Map&lt;String, String> headers) {\n    SseEmitter emitter = new SseEmitter(Duration.ofMinutes(15).toMillis());\n    ExecutorService sseMvcExecutor = Executors.newSingleThreadExecutor();\n    sseMvcExecutor.execute(() -> {\n        try {\n            for (int i = 0; true; i++) {\n                SseEventBuilder event = SseEmitter.event()\n                        .data(voteMetricService.getVoteAggregateOverTime())\n                        .id(eventId);\n                emitter.send(event);\n                Thread.sleep(Duration.ofSeconds(30).toMillis());\n            }\n        } catch (Exception ex) {\n            emitter.completeWithError(ex);\n        }\n        emitter.complete();\n    });\n    return emitter;\n}<\/code><\/pre>\n<\/div>\n<p>In this project, we sent the event forever until SseEmitter got a timeout. The server will send new data every 30 seconds.<\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2024\/03\/votescount_dashboard_realtime.png\"><img decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2024\/03\/votescount_dashboard_realtime.png\" alt=\"\" width=\"1441\" height=\"778\" class=\"aligncenter size-full wp-image-30027\" srcset=\"\/wp-content\/uploads\/2024\/03\/votescount_dashboard_realtime.png 1441w, \/wp-content\/uploads\/2024\/03\/votescount_dashboard_realtime-300x162.png 300w, \/wp-content\/uploads\/2024\/03\/votescount_dashboard_realtime-1024x553.png 1024w, \/wp-content\/uploads\/2024\/03\/votescount_dashboard_realtime-768x415.png 768w, \/wp-content\/uploads\/2024\/03\/votescount_dashboard_realtime-600x324.png 600w\" sizes=\"(max-width: 1441px) 100vw, 1441px\" \/><\/a><\/p>\n<p>Response from the SSE endpoint:<\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2024\/03\/sse_eventstream.png\"><img decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2024\/03\/sse_eventstream.png\" alt=\"\" width=\"1481\" height=\"211\" class=\"aligncenter size-full wp-image-30025\" srcset=\"\/wp-content\/uploads\/2024\/03\/sse_eventstream.png 1481w, \/wp-content\/uploads\/2024\/03\/sse_eventstream-300x43.png 300w, \/wp-content\/uploads\/2024\/03\/sse_eventstream-1024x146.png 1024w, \/wp-content\/uploads\/2024\/03\/sse_eventstream-768x109.png 768w, \/wp-content\/uploads\/2024\/03\/sse_eventstream-600x85.png 600w\" sizes=\"(max-width: 1481px) 100vw, 1481px\" \/><\/a><\/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. Containers help ensure that the application runs as expected on any machine. Docker gives us access to Docker Compose, a tool we can use to orchestrate multiple containerized applications together.<\/p>\n<p>First, create dockerfile: <code>dev.Dockerfile<\/code>. We use a Maven docker image with JDK21<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">\nFROM maven:3.9.5-eclipse-temurin-21-alpine\nRUN mkdir \/app\nWORKDIR \/app\n\nCOPY pom.xml .\/\nRUN mvn dependency:go-offline\n\nCOPY docker-entrypoint-dev.sh .\/\nCOPY src .\/src<\/code><\/pre>\n<\/div>\n<p>Next, create a docker entry-point file: <code>docker-entrypoint-dev.sh<\/code>. With this script, we want to compile the code every time the code changes<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">\n#!\/bin\/bash\nexport TERM=xterm\necho \"wait 5s\"\nsleep 5\n\nmvn spring-boot:run -Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005\" &\n\nwhile true; do\n    watch -d -t -g \"ls -lR . | sha1sum\" && mvn compile\ndone<\/code><\/pre>\n<\/div>\n<p>Finally, create the docker-compose file: <code>docker-compose-dev.yml<\/code><\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">\nversion: '3.3'\nservices:\n  app-dev:\n    container_name: blogvotingapp-dev\n    build:\n      context: .\/\n      dockerfile: dev.Dockerfile\n    volumes:\n      - .\/src:\/app\/src\n      - .\/.m2:\/root\/.m2\n    environment:\n      - GRIDDB_NOTIFICATION_MEMBER=griddb-dev:10001\n      - GRIDDB_CLUSTER_NAME=dockerGridDB\n      - GRIDDB_USER=admin\n      - GRIDDB_PASSWORD=admin\n      - spring.thymeleaf.prefix=file:src\/main\/resources\/templates\/\n    command: sh .\/docker-entrypoint-dev.sh\n    ports:\n      - 8080:8080\n      - 35729:35729\n      - 5005:5005\n    networks:\n      - griddbvoting-dev-net\n    depends_on:\n      - griddb-dev\n  griddb-dev:\n    container_name: griddbvoting-dev\n    build:\n      context: .\/griddbdocker\n      dockerfile: Dockerfile531\n    volumes:\n      - griddbvoting-dev-vol:\/var\/lib\/gridstore\n    ports:\n      - 10001:10001\n      - 20001:20001\n    networks:\n      - griddbvoting-dev-net\n\nnetworks:\n  griddbvoting-dev-net:\nvolumes:\n  griddbvoting-dev-vol:<\/code><\/pre>\n<\/div>\n<p>Let&#8217;s build the docker image using the following command:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">\n  docker compose -f docker-compose-dev.yml build<\/code><\/pre>\n<\/div>\n<p>After building the docker image, now run it:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">\n  docker compose -f docker-compose-dev.yml up up<\/code><\/pre>\n<\/div>\n<p>The website ready at http:\/\/localhost:8080<\/p>\n<h2>Conclusion<\/h2>\n<p>We have learned how to make a voting platform platform using Spring Boot and GridDB as a database.<\/p>\n<p>We also learned how to develop Spring Boot applications using Docker Compose. Dockerizing a Spring Boot application greatly simplifies the deployment process and ensures a consistent environment across various applications. Also encapsulates the application and its dependencies into a Docker image reducing potential consistencies and conflicts significantly.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>An online voting system is a software platform that allows people to cast their votes electronically with intuitive and secure online interfaces. These systems leverage cutting-edge technologies to facilitate the entire voting process. Online voting systems come in various forms, from secure web portals to mobile applications. Online voting systems shine in situations where accessibility [&hellip;]<\/p>\n","protected":false},"author":41,"featured_media":30028,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[121],"tags":[],"class_list":["post-46795","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>Building a Blog Voting Web Platform using Spring Boot and GridDB | GridDB: Open Source Time Series Database for IoT<\/title>\n<meta name=\"description\" content=\"An online voting system is a software platform that allows people to cast their votes electronically with intuitive and secure online interfaces. These\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Blog Voting Web Platform using Spring Boot and GridDB | GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"og:description\" content=\"An online voting system is a software platform that allows people to cast their votes electronically with intuitive and secure online interfaces. These\" \/>\n<meta property=\"og:url\" content=\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/\" \/>\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-03-27T07:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-13T20:56:55+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.griddb.net\/wp-content\/uploads\/2024\/03\/chrome_EKcj8OPSD4.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1794\" \/>\n\t<meta property=\"og:image:height\" content=\"1794\" \/>\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=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/\"},\"author\":{\"name\":\"griddb-admin\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\"},\"headline\":\"Building a Blog Voting Web Platform using Spring Boot and GridDB\",\"datePublished\":\"2024-03-27T07:00:00+00:00\",\"dateModified\":\"2025-11-13T20:56:55+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/\"},\"wordCount\":1518,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2024\/03\/chrome_EKcj8OPSD4.jpg\",\"articleSection\":[\"Blog\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/\",\"url\":\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/\",\"name\":\"Building a Blog Voting Web Platform using Spring Boot and GridDB | GridDB: Open Source Time Series Database for IoT\",\"isPartOf\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2024\/03\/chrome_EKcj8OPSD4.jpg\",\"datePublished\":\"2024-03-27T07:00:00+00:00\",\"dateModified\":\"2025-11-13T20:56:55+00:00\",\"description\":\"An online voting system is a software platform that allows people to cast their votes electronically with intuitive and secure online interfaces. These\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/#primaryimage\",\"url\":\"\/wp-content\/uploads\/2024\/03\/chrome_EKcj8OPSD4.jpg\",\"contentUrl\":\"\/wp-content\/uploads\/2024\/03\/chrome_EKcj8OPSD4.jpg\",\"width\":1794,\"height\":1794},{\"@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":"Building a Blog Voting Web Platform using Spring Boot and GridDB | GridDB: Open Source Time Series Database for IoT","description":"An online voting system is a software platform that allows people to cast their votes electronically with intuitive and secure online interfaces. These","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:\/\/griddb.net\/en\/blog\/voting-web-platform\/","og_locale":"en_US","og_type":"article","og_title":"Building a Blog Voting Web Platform using Spring Boot and GridDB | GridDB: Open Source Time Series Database for IoT","og_description":"An online voting system is a software platform that allows people to cast their votes electronically with intuitive and secure online interfaces. These","og_url":"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/","og_site_name":"GridDB: Open Source Time Series Database for IoT","article_publisher":"https:\/\/www.facebook.com\/griddbcommunity\/","article_published_time":"2024-03-27T07:00:00+00:00","article_modified_time":"2025-11-13T20:56:55+00:00","og_image":[{"width":1794,"height":1794,"url":"https:\/\/www.griddb.net\/wp-content\/uploads\/2024\/03\/chrome_EKcj8OPSD4.jpg","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":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/#article","isPartOf":{"@id":"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/"},"author":{"name":"griddb-admin","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233"},"headline":"Building a Blog Voting Web Platform using Spring Boot and GridDB","datePublished":"2024-03-27T07:00:00+00:00","dateModified":"2025-11-13T20:56:55+00:00","mainEntityOfPage":{"@id":"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/"},"wordCount":1518,"commentCount":0,"publisher":{"@id":"https:\/\/www.griddb.net\/en\/#organization"},"image":{"@id":"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2024\/03\/chrome_EKcj8OPSD4.jpg","articleSection":["Blog"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/griddb.net\/en\/blog\/voting-web-platform\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/","url":"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/","name":"Building a Blog Voting Web Platform using Spring Boot and GridDB | GridDB: Open Source Time Series Database for IoT","isPartOf":{"@id":"https:\/\/www.griddb.net\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/#primaryimage"},"image":{"@id":"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2024\/03\/chrome_EKcj8OPSD4.jpg","datePublished":"2024-03-27T07:00:00+00:00","dateModified":"2025-11-13T20:56:55+00:00","description":"An online voting system is a software platform that allows people to cast their votes electronically with intuitive and secure online interfaces. These","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/griddb.net\/en\/blog\/voting-web-platform\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/griddb.net\/en\/blog\/voting-web-platform\/#primaryimage","url":"\/wp-content\/uploads\/2024\/03\/chrome_EKcj8OPSD4.jpg","contentUrl":"\/wp-content\/uploads\/2024\/03\/chrome_EKcj8OPSD4.jpg","width":1794,"height":1794},{"@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\/46795","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=46795"}],"version-history":[{"count":1,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/46795\/revisions"}],"predecessor-version":[{"id":51457,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/46795\/revisions\/51457"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/media\/30028"}],"wp:attachment":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/media?parent=46795"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/categories?post=46795"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/tags?post=46795"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}